windows-development-sandbox 1.1.0 → 1.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (222) hide show
  1. package/.sandbox-config.example.json +2 -1
  2. package/README.md +3 -2
  3. package/config.example.json +39 -5
  4. package/dist/cli/args.d.ts +3 -0
  5. package/dist/cli/args.js +3 -1
  6. package/dist/cli/boot-progress.d.ts +22 -0
  7. package/dist/cli/boot-progress.js +59 -0
  8. package/dist/cli/commands/accounts.js +2 -2
  9. package/dist/cli/commands/agent-commands.js +1 -1
  10. package/dist/cli/commands/context.js +2 -1
  11. package/dist/cli/commands/data/clean.js +6 -1
  12. package/dist/cli/commands/data/collect.js +3 -3
  13. package/dist/cli/commands/index.d.ts +1 -1
  14. package/dist/cli/commands/index.js +1 -1
  15. package/dist/cli/commands/launch.d.ts +5 -2
  16. package/dist/cli/commands/launch.js +113 -25
  17. package/dist/cli/commands/maintenance.d.ts +2 -0
  18. package/dist/cli/commands/maintenance.js +70 -4
  19. package/dist/cli/commands/project.d.ts +10 -6
  20. package/dist/cli/commands/project.js +42 -3
  21. package/dist/cli/commands/restart.js +17 -7
  22. package/dist/cli/commands/sessions.js +5 -4
  23. package/dist/cli/commands/user-config.js +1 -1
  24. package/dist/cli/index.js +6 -4
  25. package/dist/cli/manifest.js +6 -1
  26. package/dist/cli/menu/apps.d.ts +21 -0
  27. package/dist/cli/menu/apps.js +122 -0
  28. package/dist/cli/menu/command-form.d.ts +28 -0
  29. package/dist/cli/menu/command-form.js +219 -0
  30. package/dist/cli/menu/index.d.ts +26 -0
  31. package/dist/cli/menu/index.js +180 -0
  32. package/dist/cli/menu/settings/categories.d.ts +9 -0
  33. package/dist/cli/menu/settings/categories.js +28 -0
  34. package/dist/cli/menu/settings/config-file.d.ts +40 -0
  35. package/dist/cli/menu/settings/config-file.js +71 -0
  36. package/dist/cli/menu/settings/editors.d.ts +30 -0
  37. package/dist/cli/menu/settings/editors.js +305 -0
  38. package/dist/cli/menu/settings/index.d.ts +32 -0
  39. package/dist/cli/menu/settings/index.js +136 -0
  40. package/dist/cli/menu/settings/schema-node.d.ts +62 -0
  41. package/dist/cli/menu/settings/schema-node.js +104 -0
  42. package/dist/cli/{menu-multi.d.ts → menu/several.d.ts} +10 -2
  43. package/dist/cli/menu/several.js +102 -0
  44. package/dist/cli/progress.d.ts +15 -0
  45. package/dist/cli/progress.js +52 -0
  46. package/dist/cli/tui/app.d.ts +39 -0
  47. package/dist/cli/tui/app.js +52 -0
  48. package/dist/cli/tui/dialogs.d.ts +57 -0
  49. package/dist/cli/tui/dialogs.js +162 -0
  50. package/dist/cli/tui/frame.d.ts +18 -0
  51. package/dist/cli/tui/frame.js +27 -0
  52. package/dist/cli/tui/index.d.ts +7 -0
  53. package/dist/cli/tui/index.js +8 -0
  54. package/dist/cli/tui/list-screen.d.ts +101 -0
  55. package/dist/cli/tui/list-screen.js +299 -0
  56. package/dist/cli/tui/terminal.d.ts +37 -0
  57. package/dist/cli/tui/terminal.js +89 -0
  58. package/dist/cli/tui/text.d.ts +20 -0
  59. package/dist/cli/tui/text.js +105 -0
  60. package/dist/cli/tui/theme.d.ts +45 -0
  61. package/dist/cli/tui/theme.js +30 -0
  62. package/dist/core/config.js +10 -7
  63. package/dist/core/local-config.d.ts +31 -1
  64. package/dist/core/local-config.js +72 -7
  65. package/dist/core/project-policy.d.ts +50 -3
  66. package/dist/core/project-policy.js +90 -4
  67. package/dist/core/schema/apps.d.ts +40 -0
  68. package/dist/core/schema/apps.js +27 -0
  69. package/dist/core/schema/config.d.ts +69 -3
  70. package/dist/core/schema/config.js +18 -6
  71. package/dist/core/schema/profile.d.ts +5 -0
  72. package/dist/core/schema/profile.js +4 -2
  73. package/dist/core/schema/projects.d.ts +1 -0
  74. package/dist/core/schema/projects.js +1 -0
  75. package/dist/core/schema/sandbox.d.ts +29 -1
  76. package/dist/core/schema/sandbox.js +29 -1
  77. package/dist/core/schema/toolchains.d.ts +3 -0
  78. package/dist/core/schema/toolchains.js +6 -1
  79. package/dist/core/types.d.ts +24 -1
  80. package/dist/core/util.d.ts +2 -0
  81. package/dist/core/util.js +2 -0
  82. package/dist/core/workspace.d.ts +21 -4
  83. package/dist/core/workspace.js +99 -21
  84. package/dist/guest/apps/index.d.ts +10 -0
  85. package/dist/guest/apps/index.js +206 -0
  86. package/dist/guest/apps/resolvers.d.ts +8 -0
  87. package/dist/guest/apps/resolvers.js +90 -0
  88. package/dist/guest/audit-parse.d.ts +7 -0
  89. package/dist/guest/audit-parse.js +24 -0
  90. package/dist/guest/audit.d.ts +4 -0
  91. package/dist/guest/audit.js +47 -0
  92. package/dist/guest/daemon/relays.d.ts +1 -0
  93. package/dist/guest/daemon/relays.js +18 -0
  94. package/dist/guest/daemon.js +4 -0
  95. package/dist/guest/env.d.ts +7 -2
  96. package/dist/guest/env.js +17 -3
  97. package/dist/guest/helpers.d.ts +1 -1
  98. package/dist/guest/main.js +20 -3
  99. package/dist/guest/netproxy.d.ts +1 -0
  100. package/dist/guest/netproxy.js +100 -0
  101. package/dist/guest/ptyhost.js +14 -4
  102. package/dist/guest/setup/claude.d.ts +0 -4
  103. package/dist/guest/setup/claude.js +48 -2
  104. package/dist/guest/setup/network.d.ts +9 -0
  105. package/dist/guest/setup/network.js +97 -4
  106. package/dist/guest/setup/user.js +4 -0
  107. package/dist/guest/toolchains/index.js +29 -3
  108. package/dist/guest/toolchains/native/mesa.js +13 -3
  109. package/dist/guest/toolchains/plan.d.ts +3 -1
  110. package/dist/guest/toolchains/plan.js +53 -9
  111. package/dist/guest/toolchains/python.js +26 -2
  112. package/dist/guest/toolchains/vscode-profile.d.ts +30 -0
  113. package/dist/guest/toolchains/vscode-profile.js +69 -0
  114. package/dist/guest/toolchains/vscode.d.ts +17 -0
  115. package/dist/guest/toolchains/vscode.js +87 -1
  116. package/dist/host/attach.js +2 -1
  117. package/dist/host/audit/collect.d.ts +34 -0
  118. package/dist/host/audit/collect.js +126 -0
  119. package/dist/host/audit/log.d.ts +46 -0
  120. package/dist/host/audit/log.js +143 -0
  121. package/dist/host/channel.d.ts +2 -2
  122. package/dist/host/channel.js +3 -2
  123. package/dist/host/generate.d.ts +7 -1
  124. package/dist/host/generate.js +23 -6
  125. package/dist/host/join.d.ts +7 -0
  126. package/dist/host/join.js +23 -9
  127. package/dist/host/launch/mappings.d.ts +7 -2
  128. package/dist/host/launch/mappings.js +68 -18
  129. package/dist/host/launch/projects.d.ts +2 -0
  130. package/dist/host/launch/projects.js +11 -5
  131. package/dist/host/launch/stage.js +1 -1
  132. package/dist/host/motw.d.ts +17 -0
  133. package/dist/host/motw.js +74 -0
  134. package/dist/host/platform.js +3 -0
  135. package/dist/host/port-forward.d.ts +8 -0
  136. package/dist/host/port-forward.js +37 -0
  137. package/dist/host/projects.d.ts +25 -2
  138. package/dist/host/projects.js +54 -5
  139. package/dist/host/repo-guard.d.ts +10 -3
  140. package/dist/host/repo-guard.js +142 -14
  141. package/dist/host/stops.d.ts +67 -0
  142. package/dist/host/stops.js +126 -0
  143. package/dist/host/terminal.d.ts +8 -0
  144. package/dist/host/terminal.js +45 -1
  145. package/dist/host/workspace-view/index.d.ts +20 -0
  146. package/dist/host/workspace-view/index.js +53 -0
  147. package/dist/host/workspace-view/rules.d.ts +26 -0
  148. package/dist/host/workspace-view/rules.js +93 -0
  149. package/dist/host/workspace-view/view.d.ts +50 -0
  150. package/dist/host/workspace-view/view.js +281 -0
  151. package/dist/runtime/daemon.js +1387 -558
  152. package/dist/runtime/db.js +199 -14
  153. package/dist/runtime/main.js +3082 -631
  154. package/dist/runtime/netproxy.js +25948 -0
  155. package/dist/runtime/view.js +531 -148
  156. package/dist/selftest/audit.test.d.ts +2 -0
  157. package/dist/selftest/audit.test.js +57 -0
  158. package/dist/selftest/channel.test.js +45 -2
  159. package/dist/selftest/cli.test.js +47 -17
  160. package/dist/selftest/fixtures.d.ts +47 -1
  161. package/dist/selftest/fixtures.js +3 -1
  162. package/dist/selftest/generate.test.js +7 -2
  163. package/dist/selftest/index.js +6 -2
  164. package/dist/selftest/live-network.test.d.ts +2 -0
  165. package/dist/selftest/live-network.test.js +63 -0
  166. package/dist/selftest/live-terminal.test.d.ts +2 -0
  167. package/dist/selftest/live-terminal.test.js +169 -0
  168. package/dist/selftest/local-config.test.js +42 -4
  169. package/dist/selftest/mirror.test.js +36 -7
  170. package/dist/selftest/network.test.js +12 -0
  171. package/dist/selftest/projects.test.js +77 -8
  172. package/dist/selftest/repo-guard.test.js +59 -1
  173. package/dist/selftest/sync.test.js +50 -10
  174. package/dist/selftest/toolchains.test.js +30 -0
  175. package/dist/selftest/workspace-view.test.d.ts +2 -0
  176. package/dist/selftest/workspace-view.test.js +89 -0
  177. package/dist/shared/apps.d.ts +49 -0
  178. package/dist/shared/apps.js +114 -0
  179. package/dist/shared/console-input.d.ts +11 -0
  180. package/dist/shared/console-input.js +61 -0
  181. package/dist/shared/download.d.ts +3 -0
  182. package/dist/shared/download.js +25 -2
  183. package/dist/shared/guest-paths.d.ts +27 -0
  184. package/dist/shared/guest-paths.js +68 -0
  185. package/dist/shared/netpolicy.d.ts +27 -0
  186. package/dist/shared/netpolicy.js +51 -0
  187. package/dist/shared/projects.d.ts +27 -4
  188. package/dist/shared/projects.js +72 -13
  189. package/dist/shared/pty-client.d.ts +26 -4
  190. package/dist/shared/pty-client.js +204 -25
  191. package/dist/shared/rundir.d.ts +2 -0
  192. package/dist/shared/rundir.js +2 -0
  193. package/dist/shared/schema.d.ts +34 -0
  194. package/dist/shared/schema.js +14 -0
  195. package/dist/shared/system.d.ts +1 -0
  196. package/dist/shared/system.js +1 -0
  197. package/dist/sync/claude-mem.js +3 -2
  198. package/dist/sync/history.d.ts +6 -0
  199. package/dist/sync/history.js +26 -11
  200. package/dist/sync/watcher.js +78 -6
  201. package/package.json +2 -1
  202. package/sandbox/cargoshim.cs +56 -0
  203. package/sandbox/cpucap.cs +33 -0
  204. package/sandbox/cpucap.ps1 +4 -35
  205. package/sandbox/logon.cmd +6 -0
  206. package/sandbox/memlog.ps1 +34 -0
  207. package/sandbox/pagefile.cs +31 -0
  208. package/sandbox/pagefile.ps1 +21 -0
  209. package/sandbox/ptyhost.cs +162 -7
  210. package/schemas/config.schema.json +318 -3
  211. package/schemas/sandbox-config.schema.json +129 -14
  212. package/dist/cli/menu-multi.js +0 -82
  213. package/dist/cli/menu.d.ts +0 -14
  214. package/dist/cli/menu.js +0 -290
  215. package/dist/cli/settings/edit.d.ts +0 -13
  216. package/dist/cli/settings/edit.js +0 -19
  217. package/dist/cli/settings/index.d.ts +0 -8
  218. package/dist/cli/settings/index.js +0 -131
  219. package/dist/cli/settings/prompts.d.ts +0 -7
  220. package/dist/cli/settings/prompts.js +0 -48
  221. package/dist/cli/settings/schema.d.ts +0 -33
  222. package/dist/cli/settings/schema.js +0 -29
@@ -8,11 +8,13 @@ export const DEFAULTS = {
8
8
  defaultHarness: 'claude',
9
9
  guestWorkspace: 'auto',
10
10
  readOnly: false,
11
+ workspaceFilter: { enabled: false, exclude: [], include: [] },
12
+ audit: { enabled: false, days: 14, maxMb: 100, processes: true, files: true },
11
13
  mappings: [],
12
- sandbox: { memoryMb: 'auto', maxMemoryMb: 32768, maxMemoryPercent: 80, cpuLimit: 80, reserveHostMb: 8192, vGpu: false, networking: true, clipboard: true, audioInput: false, videoInput: false, printerRedirection: false, protectedClient: true, headless: false },
13
- storage: { root: '%LOCALAPPDATA%\\AgentSandbox', persistAgentState: true, dataName: 'default' },
14
- network: { blockPrivateRanges: true },
15
- provisioning: { copyFromHost: true, fallbackToInstall: true, refresh: false, git: { enabled: true, access: 'full' }, terminal: { enabled: true } },
14
+ sandbox: { memoryMb: 'auto', maxMemoryMb: 32768, maxMemoryPercent: 80, cpuLimit: 80, pageFileMb: 16384, reserveHostMb: 8192, vGpu: false, networking: true, clipboard: true, audioInput: false, videoInput: false, printerRedirection: false, protectedClient: true, headless: false, keepAwake: true },
15
+ storage: { root: '%LOCALAPPDATA%\\AgentSandbox', persistAgentState: true, dataName: 'default', markSandboxFiles: true, warnDataGb: 50 },
16
+ network: { blockPrivateRanges: true, allowOnly: [], blockHosts: [], forwardPorts: [], dns: [], proxy: '' },
17
+ provisioning: { copyFromHost: true, fallbackToInstall: true, refresh: false, git: { enabled: true, access: 'full' }, terminal: { enabled: true }, conpty: { enabled: true } },
16
18
  downloads: { requireChecksum: false },
17
19
  toolchains: {
18
20
  node: { version: 'host', extraVersions: [], corepack: true, volta: 'auto', globalPackages: [] },
@@ -33,7 +35,7 @@ export const DEFAULTS = {
33
35
  uv: { enabled: true },
34
36
  vcRuntime: { enabled: false },
35
37
  mesa: { enabled: false, vulkan: false },
36
- vscode: { enabled: true },
38
+ vscode: { enabled: true, setup: true, recommended: true, extensions: [] },
37
39
  environment: {},
38
40
  },
39
41
  databases: {
@@ -48,7 +50,8 @@ export const DEFAULTS = {
48
50
  agentCommands: { enabled: false, agents: 'auto' },
49
51
  secrets: { strip: true, allow: [], patterns: [], warnFiles: true },
50
52
  personalization: { enabled: true },
51
- claudeProfile: { enabled: true, hostDir: 'auto', include: ['settings.json', 'CLAUDE.md', 'commands', 'agents', 'skills', 'plugins'], globalStateFile: 'auto', copyCredentials: false, overwrite: true, syncHistory: true, syncClaudeMem: true },
53
+ apps: { install: [], custom: {} },
54
+ claudeProfile: { enabled: true, hostDir: 'auto', include: ['settings.json', 'CLAUDE.md', 'commands', 'agents', 'skills', 'plugins'], globalStateFile: 'auto', copyCredentials: false, overwrite: true, syncHistory: true, historyScope: 'project', syncClaudeMem: true, syncClaudeMemBack: false },
52
55
  harnesses: {
53
56
  claude: { hostExecutable: 'claude.exe', hostDir: 'auto', npmPackage: '@anthropic-ai/claude-code@latest', npmBin: 'claude', arguments: [], environment: { DISABLE_AUTOUPDATER: '1' }, resumeArguments: ['--continue'] },
54
57
  codex: { npmPackage: '@openai/codex@latest', npmBin: 'codex', arguments: [], environment: {}, resumeArguments: ['resume', '--last'] },
@@ -69,7 +72,7 @@ export const DEFAULTS = {
69
72
  aider: { uvPackage: 'aider-chat', uvBin: 'aider', arguments: [], environment: {}, resumeArguments: ['--restore-chat-history'] },
70
73
  },
71
74
  hooks: { failFast: false },
72
- projects: { all: false, exclude: [], roots: [], askToAddRoot: true, declinedRoots: [], wsbShare: true },
75
+ projects: { all: false, exclude: [], roots: [], askToAddRoot: true, declinedRoots: [], allowOutsideMappings: 'ask', wsbShare: true },
73
76
  };
74
77
  /** Per-user folder for config and hooks. Survives reinstalling the npm package. */
75
78
  export const userDir = () => process.env.AGENT_SANDBOX_HOME ?? path.join(homeDir(), '.agent-sandbox');
@@ -2,6 +2,15 @@ import type { Config, LocalConfigFile, Overrides } from './types.js';
2
2
  export declare const LOCAL_CONFIG_NAME = ".sandbox-config.json";
3
3
  /** Validates a folder's settings and resolves its relative paths against that folder. */
4
4
  export declare const normalizeOverrides: (data: unknown, dir: string, source: string) => Overrides;
5
+ /**
6
+ * The project file's mappings of folders outside the project (%VAR% expanded, links and short names followed). A repo
7
+ * ships this file, so these reach the rest of this PC: projects.allowOutsideMappings in YOUR settings decides.
8
+ */
9
+ export declare const outsideMappings: <M extends {
10
+ host: string;
11
+ }>(dir: string, mappings?: M[]) => M[];
12
+ /** `folder` (also where it really is, through links) is inside the project folder `dir`. */
13
+ export declare const insideProject: (dir: string, folder: string) => boolean;
5
14
  /**
6
15
  * Protections a project file may only make stricter, never looser. A repo can ship the file, so it must not be able to
7
16
  * switch off what you turned on (or turn on what you turned off). The strictest of the two values always wins.
@@ -10,11 +19,32 @@ export declare const ON_IS_SAFER: string[];
10
19
  export declare const OFF_IS_SAFER: string[];
11
20
  export declare const GIT_STRICTNESS: Record<string, number>;
12
21
  export declare const NEVER_LOOSER: string[];
22
+ /** What a project file asks for that would loosen one of your protections (so it is not used). */
23
+ export declare const loosenedRequests: (config: Config, ov: Overrides) => {
24
+ key: string;
25
+ wanted: unknown;
26
+ yours: unknown;
27
+ }[];
13
28
  /** Applies validated overrides. Mappings with the same guest folder replace the configured ones. */
14
29
  export declare const applyOverrides: (config: Config, ov: Overrides) => Config;
15
30
  /** Human-readable list of what a local config changes, with risky items flagged. */
16
- export declare const describeOverrides: (ov: Overrides) => string[];
31
+ export declare const describeOverrides: (ov: Overrides, dir?: string) => string[];
17
32
  export declare const readLocalConfig: (dir: string) => LocalConfigFile | null;
18
33
  export declare const trustStorePath: (storageRoot: string) => string;
19
34
  export declare const isTrusted: (store: string, file: string, hash: string) => boolean;
20
35
  export declare const saveTrust: (store: string, file: string, hash: string) => void;
36
+ export declare const allowStorePath: (storageRoot: string) => string;
37
+ /** true = you allowed it, false = you said no (not asked again), null = not asked yet. */
38
+ export declare const allowedAnswer: (store: string, file: string, setting: string, value: unknown) => boolean | null;
39
+ export declare const saveAllowed: (store: string, file: string, setting: string, value: unknown, allow: boolean) => void;
40
+ /** Answers for folders outside the project are kept with the others, one per folder (and read-only or not). */
41
+ export declare const OUTSIDE_FOLDER = "folder outside the project";
42
+ export declare const outsideKey: (m: {
43
+ host: string;
44
+ readOnly: boolean;
45
+ }) => string;
46
+ /** Your config with these requests let through, so applyOverrides' "strictest wins" keeps the project's value. */
47
+ export declare const allowRequests: (config: Config, allowed: {
48
+ key: string;
49
+ wanted: unknown;
50
+ }[]) => Config;
@@ -4,9 +4,10 @@ import fs from 'node:fs';
4
4
  import path from 'node:path';
5
5
  import crypto from 'node:crypto';
6
6
  import _ from 'lodash';
7
+ import { z } from 'zod';
7
8
  import { mergeConfig } from './config.js';
8
9
  import { describeIssues, LocalOverrides } from './schema.js';
9
- import { expandPath, fail, fileExists, parseJsonc, tryReadJson, writeJson } from './util.js';
10
+ import { expandPath, fail, fileExists, isInsideOrEqual, parseJsonc, tryReadJson, writeJson } from './util.js';
10
11
  export const LOCAL_CONFIG_NAME = '.sandbox-config.json';
11
12
  /** Validates a folder's settings and resolves its relative paths against that folder. */
12
13
  export const normalizeOverrides = (data, dir, source) => {
@@ -18,14 +19,34 @@ export const normalizeOverrides = (data, dir, source) => {
18
19
  const missing = scripts?.filter((s) => !fileExists(s)) ?? [];
19
20
  if (missing.length)
20
21
  fail(source + ' is not valid:\n - scripts: file not found: ' + missing.join(', '));
21
- return { ...overrides, scripts, mappings: overrides.mappings?.map((m) => ({ ...m, host: expandPath(m.host, dir) })) };
22
+ const mappings = overrides.mappings?.map((m) => ({ ...m, host: expandPath(m.host, dir) }));
23
+ return { ...overrides, scripts, mappings };
24
+ };
25
+ /**
26
+ * The project file's mappings of folders outside the project (%VAR% expanded, links and short names followed). A repo
27
+ * ships this file, so these reach the rest of this PC: projects.allowOutsideMappings in YOUR settings decides.
28
+ */
29
+ export const outsideMappings = (dir, mappings = []) => mappings.filter((m) => !insideProject(dir, m.host));
30
+ /** `folder` (also where it really is, through links) is inside the project folder `dir`. */
31
+ export const insideProject = (dir, folder) => {
32
+ // Where a path really is: the nearest existing folder resolved (links, 8.3 short names), plus the part not created yet.
33
+ const real = (p) => {
34
+ const full = path.resolve(p);
35
+ try {
36
+ return fs.realpathSync.native(full);
37
+ }
38
+ catch {
39
+ return path.dirname(full) === full ? full : path.join(real(path.dirname(full)), path.basename(full));
40
+ }
41
+ };
42
+ return isInsideOrEqual(dir, folder) && isInsideOrEqual(real(dir), real(folder));
22
43
  };
23
44
  /**
24
45
  * Protections a project file may only make stricter, never looser. A repo can ship the file, so it must not be able to
25
46
  * switch off what you turned on (or turn on what you turned off). The strictest of the two values always wins.
26
47
  */
27
- export const ON_IS_SAFER = ['readOnly', 'network.blockPrivateRanges', 'sandbox.protectedClient', 'downloads.requireChecksum'];
28
- export const OFF_IS_SAFER = ['sandbox.networking', 'sandbox.clipboard', 'sandbox.audioInput', 'sandbox.videoInput', 'sandbox.printerRedirection', 'sandbox.vGpu', 'claudeProfile.copyCredentials'];
48
+ export const ON_IS_SAFER = ['readOnly', 'workspaceFilter.enabled', 'network.blockPrivateRanges', 'sandbox.protectedClient', 'downloads.requireChecksum'];
49
+ export const OFF_IS_SAFER = ['sandbox.networking', 'sandbox.clipboard', 'sandbox.audioInput', 'sandbox.videoInput', 'sandbox.printerRedirection', 'sandbox.vGpu', 'claudeProfile.copyCredentials', 'claudeProfile.enabled', 'claudeProfile.syncHistory', 'claudeProfile.syncClaudeMem'];
29
50
  export const GIT_STRICTNESS = { full: 0, readonly: 1, none: 2 };
30
51
  export const NEVER_LOOSER = [...ON_IS_SAFER, ...OFF_IS_SAFER, 'provisioning.git.access'];
31
52
  const keepStrictest = (yours, merged) => {
@@ -34,6 +55,12 @@ const keepStrictest = (yours, merged) => {
34
55
  if (GIT_STRICTNESS[yours.provisioning.git.access] > GIT_STRICTNESS[merged.provisioning.git.access])
35
56
  merged.provisioning.git.access = yours.provisioning.git.access;
36
57
  };
58
+ /** What a project file asks for that would loosen one of your protections (so it is not used). */
59
+ export const loosenedRequests = (config, ov) => [
60
+ ...ON_IS_SAFER.filter((p) => _.has(ov, p) && !_.get(ov, p) && _.get(config, p)),
61
+ ...OFF_IS_SAFER.filter((p) => _.has(ov, p) && _.get(ov, p) && !_.get(config, p)),
62
+ ...(_.has(ov, 'provisioning.git.access') && GIT_STRICTNESS[_.get(ov, 'provisioning.git.access')] < GIT_STRICTNESS[config.provisioning.git.access] ? ['provisioning.git.access'] : []),
63
+ ].map((key) => ({ key, wanted: _.get(ov, key), yours: _.get(config, key) }));
37
64
  /** Applies validated overrides. Mappings with the same guest folder replace the configured ones. */
38
65
  export const applyOverrides = (config, ov) => {
39
66
  const { harness, mappings, scripts: _scripts, $schema: _schema, ...rest } = ov;
@@ -41,17 +68,27 @@ export const applyOverrides = (config, ov) => {
41
68
  keepStrictest(config, next);
42
69
  if (harness)
43
70
  next.defaultHarness = harness;
71
+ // A project adds catalogue apps to yours; it never removes the ones you chose.
72
+ if (rest.apps?.install)
73
+ next.apps.install = _.union(config.apps.install, rest.apps.install);
74
+ // Its patterns keep more out on top of yours; yours stay.
75
+ if (rest.network?.blockHosts)
76
+ next.network.blockHosts = _.union(config.network.blockHosts, rest.network.blockHosts);
77
+ if (rest.workspaceFilter?.exclude)
78
+ next.workspaceFilter.exclude = _.union(config.workspaceFilter.exclude, rest.workspaceFilter.exclude);
79
+ const sameGuest = (a, b) => Boolean(a && b && a.toLowerCase() === b.toLowerCase());
44
80
  if (mappings)
45
- next.mappings = [..._.reject(config.mappings, (m) => mappings.some((o) => o.guest.toLowerCase() === m.guest.toLowerCase())), ...mappings];
81
+ next.mappings = [..._.reject(config.mappings, (m) => mappings.some((o) => sameGuest(o.guest, m.guest))), ...mappings];
46
82
  return next;
47
83
  };
48
84
  const flatten = (value, prefix = '') => _.isPlainObject(value) ? Object.entries(value).flatMap(([k, v]) => flatten(v, prefix ? prefix + '.' + k : k)) : [prefix + ' = ' + JSON.stringify(value)];
49
85
  /** Human-readable list of what a local config changes, with risky items flagged. */
50
- export const describeOverrides = (ov) => {
86
+ export const describeOverrides = (ov, dir) => {
51
87
  const { harness, mappings = [], scripts = [], $schema: _schema, ...sections } = ov;
88
+ const outside = dir ? outsideMappings(dir, mappings) : [];
52
89
  const lines = [
53
90
  ...(harness ? ['agent = ' + harness] : []),
54
- ...mappings.map((m) => (m.readOnly ? 'read-only' : 'READ/WRITE') + ' folder ' + m.host + ' -> ' + m.guest),
91
+ ...mappings.map((m) => (m.readOnly ? 'read-only' : 'READ/WRITE') + ' folder ' + m.host + ' -> ' + (m.guest ?? 'the same path') + (outside.includes(m) ? ' (outside the project)' : '')),
55
92
  ...scripts.map((s) => 'runs inside the sandbox: ' + s),
56
93
  ...flatten(sections),
57
94
  ];
@@ -75,3 +112,31 @@ const loadTrust = (store) => tryReadJson(store) ?? {};
75
112
  const key = (file) => path.resolve(file).toLowerCase();
76
113
  export const isTrusted = (store, file, hash) => loadTrust(store)[key(file)] === hash;
77
114
  export const saveTrust = (store, file, hash) => writeJson(store, { ...loadTrust(store), [key(file)]: hash });
115
+ // ---- looser settings you allowed for one project (host-only: <storage>\allowed-requests.json) ----------------------
116
+ // A project file can never loosen a protection by itself. When it asks, you are asked once (interactively); a "yes"
117
+ // is kept here, per project file and per "setting = value", so only that value is ever let through for that project.
118
+ export const allowStorePath = (storageRoot) => path.join(storageRoot, 'allowed-requests.json');
119
+ const loadAllowed = (store) => {
120
+ const parsed = z.record(z.string(), z.array(z.string().max(2000)).max(500)).safeParse(tryReadJson(store) ?? {});
121
+ return parsed.success ? parsed.data : {};
122
+ };
123
+ const allowKey = (setting, value) => setting + ' = ' + JSON.stringify(value);
124
+ /** true = you allowed it, false = you said no (not asked again), null = not asked yet. */
125
+ export const allowedAnswer = (store, file, setting, value) => {
126
+ const answers = loadAllowed(store)[key(file)] ?? [];
127
+ return answers.includes(allowKey(setting, value)) ? true : answers.includes('no: ' + allowKey(setting, value)) ? false : null;
128
+ };
129
+ export const saveAllowed = (store, file, setting, value, allow) => {
130
+ const all = loadAllowed(store);
131
+ const entry = allowKey(setting, value);
132
+ writeJson(store, { ...all, [key(file)]: _.union(_.without(all[key(file)] ?? [], entry, 'no: ' + entry), [(allow ? '' : 'no: ') + entry]) });
133
+ };
134
+ /** Answers for folders outside the project are kept with the others, one per folder (and read-only or not). */
135
+ export const OUTSIDE_FOLDER = 'folder outside the project';
136
+ export const outsideKey = (m) => (m.readOnly ? 'read-only ' : 'read/write ') + path.resolve(m.host).toLowerCase();
137
+ /** Your config with these requests let through, so applyOverrides' "strictest wins" keeps the project's value. */
138
+ export const allowRequests = (config, allowed) => {
139
+ const next = _.cloneDeep(config);
140
+ allowed.forEach((a) => _.set(next, a.key, a.wanted));
141
+ return next;
142
+ };
@@ -1,4 +1,4 @@
1
- import type { Config } from './types.js';
1
+ import type { Config, ResolvedMapping } from './types.js';
2
2
  /** The protections of a running sandbox, by setting path (`sandbox.networking`, `provisioning.git.access`, ...). */
3
3
  export type Protections = Record<string, boolean | string>;
4
4
  export declare const protectionsOf: (config: Config) => Protections;
@@ -7,9 +7,56 @@ export interface JoinCheck {
7
7
  stricter: string[];
8
8
  /** Settings it would have looser: it gets the sandbox's (stricter) value; shown as a note. */
9
9
  looser: string[];
10
+ /** Settings the sandbox turned on at its start for another project you allowed them for: shared, shown as a note. */
11
+ shared: string[];
10
12
  }
11
13
  /**
12
14
  * Compares a project's effective protections with the running sandbox's. Hub projects are always read/write
13
- * (Windows Sandbox maps the hub once, read/write), so a read-only project counts as stricter.
15
+ * (Windows Sandbox maps the hub once, read/write), so a read-only project counts as stricter. `widened`: the boot
16
+ * settings the sandbox turned on for another of its projects (with your consent); a project that has them off
17
+ * shares them instead of being refused.
14
18
  */
15
- export declare const checkJoin: (running: Protections, wanted: Protections) => JoinCheck;
19
+ export declare const checkJoin: (running: Protections, wanted: Protections, widened?: string[]) => JoinCheck;
20
+ /** .wsb devices a project may want on (only when your settings or your answer for that project allow it). */
21
+ export declare const BOOT_SETTINGS: string[];
22
+ /** What the running sandbox booted with (host-only registry). */
23
+ export interface BootRecord {
24
+ memoryMb: number | null;
25
+ mappings: ResolvedMapping[];
26
+ }
27
+ /** What a project wants that the running sandbox started without. */
28
+ export interface Missing {
29
+ settings: string[];
30
+ memoryMb: number | null;
31
+ mappings: ResolvedMapping[];
32
+ }
33
+ /**
34
+ * The boot-only things `config` (a project's effective config: your settings, its approved file, your answers) asks
35
+ * for that the running sandbox lacks. `exists`: optional mappings whose folder is missing are no wish.
36
+ */
37
+ export declare const missingAtBoot: (running: Protections, boot: BootRecord, config: Config, exists?: (folder: string) => boolean) => Missing;
38
+ export declare const nothingMissing: (m: Missing) => boolean;
39
+ /** "mw2-agent wants vGPU and 2 folders (C:\Steam\..., F:\SteamLibrary\...), but this sandbox started without them." */
40
+ export declare const describeMissing: (project: string, m: Missing) => string;
41
+ /** "Set sandbox.vGpu (or the mappings) in your own settings to have them in every sandbox." */
42
+ export declare const missingTip: (m: Missing) => string;
43
+ /** A project mapped at boot besides the launch folder, with its effective config. */
44
+ export interface BootWant {
45
+ name: string;
46
+ config: Config;
47
+ }
48
+ export interface Widened {
49
+ config: Config;
50
+ /** Setting -> the project it was turned on for. */
51
+ widened: Record<string, string>;
52
+ notes: string[];
53
+ }
54
+ /**
55
+ * The launch folder's config plus what the other projects mapped at boot want: their allowed .wsb devices, a fixed
56
+ * memory size larger than the launch's, and their mappings (placed with every other mapping at launch).
57
+ * `keepOff`: devices the launch folder's own .sandbox-config.json switched off; they stay off.
58
+ */
59
+ export declare const widenForProjects: (launch: Config, others: BootWant[], opts?: {
60
+ keepOff?: string[];
61
+ exists?: (folder: string) => boolean;
62
+ }) => Widened;
@@ -3,6 +3,9 @@
3
3
  // A second project may never loosen them, and a project that needs a STRICTER setting than the running sandbox has
4
4
  // cannot get it there without changing it for the sessions already running, so that launch is refused with a clear
5
5
  // message. A project asking for less than the sandbox has simply runs with the sandbox's (stricter) setting.
6
+ // The other direction: what a project WANTS at boot (a .wsb device you allowed for it, more memory, its mappings) and
7
+ // the running sandbox started without. Those can only come with a restart, so they are named (missingAtBoot), and a
8
+ // sandbox started with several projects boots with all of their allowed wants (widenForProjects).
6
9
  // Pure: the host checks it before registering a project, the tests drive it directly.
7
10
  import _ from 'lodash';
8
11
  import { GIT_STRICTNESS, NEVER_LOOSER, OFF_IS_SAFER, ON_IS_SAFER } from './local-config.js';
@@ -19,17 +22,100 @@ const compare = (setting, running, wanted) => {
19
22
  };
20
23
  /**
21
24
  * Compares a project's effective protections with the running sandbox's. Hub projects are always read/write
22
- * (Windows Sandbox maps the hub once, read/write), so a read-only project counts as stricter.
25
+ * (Windows Sandbox maps the hub once, read/write), so a read-only project counts as stricter. `widened`: the boot
26
+ * settings the sandbox turned on for another of its projects (with your consent); a project that has them off
27
+ * shares them instead of being refused.
23
28
  */
24
- export const checkJoin = (running, wanted) => {
29
+ export const checkJoin = (running, wanted, widened = []) => {
25
30
  const stricter = [];
26
31
  const looser = [];
32
+ const shared = [];
27
33
  for (const setting of NEVER_LOOSER) {
28
34
  const result = compare(setting, setting === 'readOnly' ? false : running[setting], wanted[setting]);
29
- if (result > 0)
35
+ if (result > 0 && widened.includes(setting))
36
+ shared.push(setting);
37
+ else if (result > 0)
30
38
  stricter.push(setting + ' = ' + JSON.stringify(wanted[setting]) + ' (the running sandbox has ' + JSON.stringify(setting === 'readOnly' ? false : running[setting]) + ')');
31
39
  if (result < 0)
32
40
  looser.push(setting + ' stays ' + JSON.stringify(running[setting]));
33
41
  }
34
- return { stricter, looser };
42
+ return { stricter, looser, shared };
43
+ };
44
+ // ---- what a project wants at boot ---------------------------------------------------------------------------------
45
+ /** .wsb devices a project may want on (only when your settings or your answer for that project allow it). */
46
+ export const BOOT_SETTINGS = ['sandbox.vGpu', 'sandbox.networking', 'sandbox.clipboard', 'sandbox.audioInput', 'sandbox.videoInput', 'sandbox.printerRedirection'];
47
+ const LABELS = {
48
+ 'sandbox.vGpu': 'vGPU', 'sandbox.networking': 'networking', 'sandbox.clipboard': 'the shared clipboard',
49
+ 'sandbox.audioInput': 'the microphone', 'sandbox.videoInput': 'the camera', 'sandbox.printerRedirection': 'printers',
50
+ };
51
+ const sameHost = (a, b) => a.toLowerCase().replace(/\\+$/, '') === b.toLowerCase().replace(/\\+$/, '');
52
+ /** `m`'s folder is mapped, wherever the launch placed it (read-only is enough for a read-only wish). */
53
+ const isMapped = (mapped, m) => mapped.some((x) => sameHost(x.host, m.host) && (m.readOnly || !x.readOnly));
54
+ /**
55
+ * The boot-only things `config` (a project's effective config: your settings, its approved file, your answers) asks
56
+ * for that the running sandbox lacks. `exists`: optional mappings whose folder is missing are no wish.
57
+ */
58
+ export const missingAtBoot = (running, boot, config, exists = () => true) => ({
59
+ settings: BOOT_SETTINGS.filter((s) => _.get(config, s) === true && running[s] === false),
60
+ memoryMb: typeof config.sandbox.memoryMb === 'number' && boot.memoryMb !== null && config.sandbox.memoryMb > boot.memoryMb ? config.sandbox.memoryMb : null,
61
+ mappings: config.mappings.filter((m) => (!m.optional || exists(m.host)) && !isMapped(boot.mappings, m)).map((m) => ({ host: m.host, guest: m.guest ?? m.host, readOnly: m.readOnly })),
62
+ });
63
+ export const nothingMissing = (m) => m.settings.length === 0 && m.memoryMb === null && m.mappings.length === 0;
64
+ const listing = (items) => items.length <= 1 ? items.join('') : items.slice(0, -1).join(', ') + ' and ' + items[items.length - 1];
65
+ /** "mw2-agent wants vGPU and 2 folders (C:\Steam\..., F:\SteamLibrary\...), but this sandbox started without them." */
66
+ export const describeMissing = (project, m) => {
67
+ const items = [
68
+ ...m.settings.map((s) => LABELS[s] ?? s),
69
+ ...(m.memoryMb !== null ? [m.memoryMb + ' MB of memory'] : []),
70
+ ...(m.mappings.length ? [m.mappings.length + ' folder' + (m.mappings.length === 1 ? '' : 's') + ' (' + m.mappings.map((x) => x.host).join(', ') + ')'] : []),
71
+ ];
72
+ return project + ' wants ' + listing(items) + ', but this sandbox started without ' + (items.length === 1 && !m.mappings.length ? 'it' : 'them') + '.';
73
+ };
74
+ /** "Set sandbox.vGpu (or the mappings) in your own settings to have them in every sandbox." */
75
+ export const missingTip = (m) => {
76
+ const keys = [...m.settings, ...(m.memoryMb !== null ? ['sandbox.memoryMb'] : [])];
77
+ const what = keys.length ? listing(keys) + (m.mappings.length ? ' (or the mappings)' : '') : 'the mappings';
78
+ return 'Set ' + what + ' in your own settings to have them in every sandbox.';
79
+ };
80
+ /**
81
+ * The launch folder's config plus what the other projects mapped at boot want: their allowed .wsb devices, a fixed
82
+ * memory size larger than the launch's, and their mappings (placed with every other mapping at launch).
83
+ * `keepOff`: devices the launch folder's own .sandbox-config.json switched off; they stay off.
84
+ */
85
+ export const widenForProjects = (launch, others, opts = {}) => {
86
+ const keepOff = opts.keepOff ?? [];
87
+ const exists = opts.exists ?? (() => true);
88
+ const config = _.cloneDeep(launch);
89
+ const widened = {};
90
+ const notes = [];
91
+ for (const setting of BOOT_SETTINGS) {
92
+ const by = others.find((o) => _.get(o.config, setting) === true);
93
+ if (!by || _.get(config, setting) === true)
94
+ continue;
95
+ if (keepOff.includes(setting)) {
96
+ notes.push(by.name + ' wants ' + (LABELS[setting] ?? setting) + ', but the launch folder\'s .sandbox-config.json switches it off, so it stays off.');
97
+ continue;
98
+ }
99
+ _.set(config, setting, true);
100
+ widened[setting] = by.name;
101
+ notes.push((LABELS[setting] ?? setting) + ' is on in this sandbox (for every project in it): ' + by.name + ' asks for it and you allowed it.');
102
+ }
103
+ for (const o of others) {
104
+ const mem = o.config.sandbox.memoryMb;
105
+ if (typeof mem === 'number' && typeof config.sandbox.memoryMb === 'number' && mem > config.sandbox.memoryMb)
106
+ config.sandbox.memoryMb = mem;
107
+ for (const m of o.config.mappings) {
108
+ if (config.mappings.some((x) => sameHost(x.host, m.host)))
109
+ continue;
110
+ if (m.optional && !exists(m.host))
111
+ continue;
112
+ if (!exists(m.host)) {
113
+ notes.push(o.name + ': mapped folder not found, skipped: ' + m.host);
114
+ continue;
115
+ }
116
+ // Where it appears (and moving it when that place is taken) is decided with every other mapping at launch.
117
+ config.mappings.push({ ...m, optional: true });
118
+ }
119
+ }
120
+ return { config, widened, notes };
35
121
  };
@@ -0,0 +1,40 @@
1
+ import { z } from 'zod';
2
+ export declare const AppId: z.ZodEnum<{
3
+ [x: string]: string;
4
+ }>;
5
+ export declare const CustomAppId: z.ZodString;
6
+ export declare const CustomApp: z.ZodObject<{
7
+ name: z.ZodString;
8
+ github: z.ZodOptional<z.ZodString>;
9
+ asset: z.ZodOptional<z.ZodString>;
10
+ url: z.ZodOptional<z.ZodString>;
11
+ sha256: z.ZodOptional<z.ZodString>;
12
+ unpack: z.ZodOptional<z.ZodEnum<{
13
+ file: "file";
14
+ archive: "archive";
15
+ nsis: "nsis";
16
+ msi: "msi";
17
+ }>>;
18
+ exe: z.ZodString;
19
+ cli: z.ZodOptional<z.ZodBoolean>;
20
+ }, z.core.$strict>;
21
+ export declare const Apps: z.ZodObject<{
22
+ install: z.ZodArray<z.ZodEnum<{
23
+ [x: string]: string;
24
+ }>>;
25
+ custom: z.ZodRecord<z.ZodString, z.ZodObject<{
26
+ name: z.ZodString;
27
+ github: z.ZodOptional<z.ZodString>;
28
+ asset: z.ZodOptional<z.ZodString>;
29
+ url: z.ZodOptional<z.ZodString>;
30
+ sha256: z.ZodOptional<z.ZodString>;
31
+ unpack: z.ZodOptional<z.ZodEnum<{
32
+ file: "file";
33
+ archive: "archive";
34
+ nsis: "nsis";
35
+ msi: "msi";
36
+ }>>;
37
+ exe: z.ZodString;
38
+ cli: z.ZodOptional<z.ZodBoolean>;
39
+ }, z.core.$strict>>;
40
+ }, z.core.$strict>;
@@ -0,0 +1,27 @@
1
+ // Apps installed in the sandbox: entries from the catalogue (shared/apps.ts) and programs of your own.
2
+ import { z } from 'zod';
3
+ import { APPS } from '../../shared/apps.js';
4
+ const CATALOGUE_IDS = APPS.filter((a) => !a.setting).map((a) => a.id);
5
+ export const AppId = z.enum(CATALOGUE_IDS);
6
+ export const CustomAppId = z.string().regex(/^[a-z0-9][a-z0-9-]{0,40}$/, 'lower-case letters, digits and - only');
7
+ const compiles = (pattern) => { try {
8
+ new RegExp(pattern);
9
+ return true;
10
+ }
11
+ catch {
12
+ return false;
13
+ } };
14
+ export const CustomApp = z.strictObject({
15
+ name: z.string().min(1).max(80).describe('The name on the desktop shortcut, e.g. "My Tool".'),
16
+ github: z.string().regex(/^[\w.-]+\/[\w.-]+$/, 'owner/repo, e.g. sharkdp/bat').optional().describe('GitHub repository (owner/repo): the file comes from its latest release.'),
17
+ asset: z.string().max(200).refine(compiles, 'not a valid pattern').optional().describe('With github: a pattern (regular expression) matching the release file, e.g. windows.*x64\\.zip$'),
18
+ url: z.string().url().startsWith('https://', 'https:// only').optional().describe('Or a direct https:// download link (zip, 7z, exe or msi).'),
19
+ sha256: z.string().regex(/^[0-9a-fA-F]{64}$/, '64 hex characters').optional().describe('SHA-256 of the download (GitHub releases usually publish one; then this is not needed).'),
20
+ unpack: z.enum(['archive', 'file', 'nsis', 'msi']).optional().describe('archive (zip, 7z, tar: the default), file (a single .exe), nsis (an installer 7-Zip can open) or msi.'),
21
+ exe: z.string().min(1).max(200).describe('The program to start (found anywhere in the download), e.g. mytool.exe.'),
22
+ cli: z.boolean().optional().describe('A command-line tool: its folder goes on PATH instead of getting a desktop shortcut.'),
23
+ }).refine((a) => (a.github && a.asset ? !a.url : Boolean(a.url) && !a.github && !a.asset), { message: 'give github + asset, or url (not both)' });
24
+ export const Apps = z.strictObject({
25
+ install: z.array(AppId).describe('Apps from the catalogue to install in every sandbox (the Apps screen in the menu lists them by category). Each is downloaded once and kept.'),
26
+ custom: z.record(CustomAppId, CustomApp).describe('Programs of your own: a GitHub release or a download link, and the program to start. Installed like the catalogue apps.'),
27
+ });
@@ -4,9 +4,21 @@ export declare const Config: z.ZodObject<{
4
4
  defaultHarness: z.ZodString;
5
5
  guestWorkspace: z.ZodUnion<readonly [z.ZodLiteral<"auto">, z.ZodLiteral<"mirror">, z.ZodString]>;
6
6
  readOnly: z.ZodBoolean;
7
+ workspaceFilter: z.ZodObject<{
8
+ enabled: z.ZodBoolean;
9
+ exclude: z.ZodArray<z.ZodString>;
10
+ include: z.ZodArray<z.ZodString>;
11
+ }, z.core.$strict>;
12
+ audit: z.ZodObject<{
13
+ enabled: z.ZodBoolean;
14
+ days: z.ZodNumber;
15
+ maxMb: z.ZodNumber;
16
+ processes: z.ZodBoolean;
17
+ files: z.ZodBoolean;
18
+ }, z.core.$strict>;
7
19
  mappings: z.ZodArray<z.ZodObject<{
8
20
  host: z.ZodString;
9
- guest: z.ZodString;
21
+ guest: z.ZodOptional<z.ZodString>;
10
22
  readOnly: z.ZodBoolean;
11
23
  optional: z.ZodOptional<z.ZodBoolean>;
12
24
  }, z.core.$strict>>;
@@ -15,6 +27,7 @@ export declare const Config: z.ZodObject<{
15
27
  maxMemoryMb: z.ZodNumber;
16
28
  maxMemoryPercent: z.ZodNumber;
17
29
  cpuLimit: z.ZodUnion<readonly [z.ZodLiteral<"none">, z.ZodNumber]>;
30
+ pageFileMb: z.ZodNumber;
18
31
  reserveHostMb: z.ZodNumber;
19
32
  vGpu: z.ZodBoolean;
20
33
  networking: z.ZodBoolean;
@@ -24,14 +37,25 @@ export declare const Config: z.ZodObject<{
24
37
  printerRedirection: z.ZodBoolean;
25
38
  protectedClient: z.ZodBoolean;
26
39
  headless: z.ZodBoolean;
40
+ keepAwake: z.ZodBoolean;
27
41
  }, z.core.$strict>;
28
42
  storage: z.ZodObject<{
29
43
  root: z.ZodString;
30
44
  persistAgentState: z.ZodBoolean;
31
45
  dataName: z.ZodString;
46
+ markSandboxFiles: z.ZodBoolean;
47
+ warnDataGb: z.ZodNumber;
32
48
  }, z.core.$strict>;
33
49
  network: z.ZodObject<{
34
50
  blockPrivateRanges: z.ZodBoolean;
51
+ allowOnly: z.ZodArray<z.ZodString>;
52
+ blockHosts: z.ZodArray<z.ZodString>;
53
+ forwardPorts: z.ZodArray<z.ZodObject<{
54
+ pc: z.ZodNumber;
55
+ sandbox: z.ZodNumber;
56
+ }, z.core.$strict>>;
57
+ dns: z.ZodArray<z.ZodString>;
58
+ proxy: z.ZodString;
35
59
  }, z.core.$strict>;
36
60
  provisioning: z.ZodObject<{
37
61
  copyFromHost: z.ZodBoolean;
@@ -48,6 +72,9 @@ export declare const Config: z.ZodObject<{
48
72
  terminal: z.ZodObject<{
49
73
  enabled: z.ZodBoolean;
50
74
  }, z.core.$strict>;
75
+ conpty: z.ZodObject<{
76
+ enabled: z.ZodBoolean;
77
+ }, z.core.$strict>;
51
78
  }, z.core.$strict>;
52
79
  downloads: z.ZodObject<{
53
80
  requireChecksum: z.ZodBoolean;
@@ -141,6 +168,9 @@ export declare const Config: z.ZodObject<{
141
168
  }, z.core.$strict>;
142
169
  vscode: z.ZodObject<{
143
170
  enabled: z.ZodBoolean;
171
+ setup: z.ZodBoolean;
172
+ recommended: z.ZodBoolean;
173
+ extensions: z.ZodArray<z.ZodString>;
144
174
  }, z.core.$strict>;
145
175
  environment: z.ZodRecord<z.ZodString, z.ZodString>;
146
176
  }, z.core.$strict>;
@@ -193,6 +223,26 @@ export declare const Config: z.ZodObject<{
193
223
  personalization: z.ZodObject<{
194
224
  enabled: z.ZodBoolean;
195
225
  }, z.core.$strict>;
226
+ apps: z.ZodObject<{
227
+ install: z.ZodArray<z.ZodEnum<{
228
+ [x: string]: string;
229
+ }>>;
230
+ custom: z.ZodRecord<z.ZodString, z.ZodObject<{
231
+ name: z.ZodString;
232
+ github: z.ZodOptional<z.ZodString>;
233
+ asset: z.ZodOptional<z.ZodString>;
234
+ url: z.ZodOptional<z.ZodString>;
235
+ sha256: z.ZodOptional<z.ZodString>;
236
+ unpack: z.ZodOptional<z.ZodEnum<{
237
+ file: "file";
238
+ archive: "archive";
239
+ nsis: "nsis";
240
+ msi: "msi";
241
+ }>>;
242
+ exe: z.ZodString;
243
+ cli: z.ZodOptional<z.ZodBoolean>;
244
+ }, z.core.$strict>>;
245
+ }, z.core.$strict>;
196
246
  claudeProfile: z.ZodObject<{
197
247
  enabled: z.ZodBoolean;
198
248
  hostDir: z.ZodString;
@@ -201,7 +251,12 @@ export declare const Config: z.ZodObject<{
201
251
  copyCredentials: z.ZodBoolean;
202
252
  overwrite: z.ZodBoolean;
203
253
  syncHistory: z.ZodBoolean;
254
+ historyScope: z.ZodEnum<{
255
+ project: "project";
256
+ all: "all";
257
+ }>;
204
258
  syncClaudeMem: z.ZodBoolean;
259
+ syncClaudeMemBack: z.ZodBoolean;
205
260
  }, z.core.$strict>;
206
261
  harnesses: z.ZodRecord<z.ZodString, z.ZodObject<{
207
262
  hostExecutable: z.ZodOptional<z.ZodString>;
@@ -223,6 +278,7 @@ export declare const Config: z.ZodObject<{
223
278
  roots: z.ZodArray<z.ZodString>;
224
279
  askToAddRoot: z.ZodBoolean;
225
280
  declinedRoots: z.ZodArray<z.ZodString>;
281
+ allowOutsideMappings: z.ZodUnion<readonly [z.ZodLiteral<"ask">, z.ZodBoolean]>;
226
282
  wsbShare: z.ZodBoolean;
227
283
  }, z.core.$strict>;
228
284
  }, z.core.$strict>;
@@ -235,9 +291,13 @@ export declare const LocalOverrides: z.ZodObject<{
235
291
  harness: z.ZodOptional<z.ZodString>;
236
292
  guestWorkspace: z.ZodOptional<z.ZodUnion<readonly [z.ZodLiteral<"auto">, z.ZodLiteral<"mirror">, z.ZodString]>>;
237
293
  readOnly: z.ZodOptional<z.ZodBoolean>;
294
+ workspaceFilter: z.ZodOptional<z.ZodObject<{
295
+ enabled: z.ZodOptional<z.ZodBoolean>;
296
+ exclude: z.ZodOptional<z.ZodArray<z.ZodString>>;
297
+ }, z.core.$strict>>;
238
298
  mappings: z.ZodOptional<z.ZodArray<z.ZodObject<{
239
299
  host: z.ZodString;
240
- guest: z.ZodString;
300
+ guest: z.ZodOptional<z.ZodString>;
241
301
  readOnly: z.ZodBoolean;
242
302
  optional: z.ZodOptional<z.ZodBoolean>;
243
303
  }, z.core.$strict>>>;
@@ -255,9 +315,11 @@ export declare const LocalOverrides: z.ZodObject<{
255
315
  printerRedirection: z.ZodOptional<z.ZodBoolean>;
256
316
  protectedClient: z.ZodOptional<z.ZodBoolean>;
257
317
  headless: z.ZodOptional<z.ZodBoolean>;
318
+ keepAwake: z.ZodOptional<z.ZodBoolean>;
258
319
  }, z.core.$strict>>;
259
320
  network: z.ZodOptional<z.ZodObject<{
260
321
  blockPrivateRanges: z.ZodOptional<z.ZodBoolean>;
322
+ blockHosts: z.ZodOptional<z.ZodArray<z.ZodString>>;
261
323
  }, z.core.$strict>>;
262
324
  provisioning: z.ZodOptional<z.ZodObject<{
263
325
  copyFromHost: z.ZodOptional<z.ZodBoolean>;
@@ -580,9 +642,13 @@ export declare const LocalOverrides: z.ZodObject<{
580
642
  personalization: z.ZodOptional<z.ZodObject<{
581
643
  enabled: z.ZodOptional<z.ZodBoolean>;
582
644
  }, z.core.$strict>>;
645
+ apps: z.ZodOptional<z.ZodObject<{
646
+ install: z.ZodOptional<z.ZodArray<z.ZodEnum<{
647
+ [x: string]: string;
648
+ }>>>;
649
+ }, z.core.$strict>>;
583
650
  claudeProfile: z.ZodOptional<z.ZodObject<{
584
651
  enabled: z.ZodOptional<z.ZodBoolean>;
585
- include: z.ZodOptional<z.ZodArray<z.ZodString>>;
586
652
  copyCredentials: z.ZodOptional<z.ZodBoolean>;
587
653
  overwrite: z.ZodOptional<z.ZodBoolean>;
588
654
  syncHistory: z.ZodOptional<z.ZodBoolean>;