windows-development-sandbox 1.18.3 → 1.20.0

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 (216) hide show
  1. package/README.md +1 -1
  2. package/config.example.json +9 -0
  3. package/dist/app/features/accounts/accounts.def.d.ts +4 -0
  4. package/dist/app/features/accounts/accounts.run.d.ts +1 -0
  5. package/dist/app/features/accounts/services.def.d.ts +3 -0
  6. package/dist/app/features/accounts/services.def.js +4 -2
  7. package/dist/app/features/accounts/services.run.d.ts +1 -0
  8. package/dist/app/features/accounts/services.run.js +3 -1
  9. package/dist/app/features/agent-commands/agent-commands.def.d.ts +37 -0
  10. package/dist/app/features/agent-commands/agent-commands.def.js +91 -0
  11. package/dist/app/features/agent-commands/agent-commands.run.d.ts +28 -0
  12. package/dist/app/features/agent-commands/agent-commands.run.js +35 -0
  13. package/dist/app/features/agent-commands/index.d.ts +2 -0
  14. package/dist/app/features/agent-commands/index.js +3 -0
  15. package/dist/app/features/agents/agents.def.d.ts +14 -1
  16. package/dist/app/features/agents/agents.def.js +61 -6
  17. package/dist/app/features/agents/agents.run.d.ts +1 -0
  18. package/dist/app/features/agents/agents.run.js +8 -1
  19. package/dist/app/features/agents/claude-env.def.d.ts +36 -0
  20. package/dist/app/features/agents/claude-env.def.js +305 -0
  21. package/dist/app/features/agents/env.def.d.ts +163 -0
  22. package/dist/app/features/agents/env.def.js +184 -0
  23. package/dist/app/features/agents/env.run.d.ts +43 -0
  24. package/dist/app/features/agents/env.run.js +86 -0
  25. package/dist/app/features/agents/index.d.ts +2 -0
  26. package/dist/app/features/agents/index.js +2 -0
  27. package/dist/app/features/defs.d.ts +150 -1
  28. package/dist/app/features/defs.js +11 -1
  29. package/dist/app/features/sandbox/sandbox.def.d.ts +22 -0
  30. package/dist/app/features/sandbox/sandbox.def.js +41 -14
  31. package/dist/app/features/sandbox/sandbox.run.d.ts +20 -0
  32. package/dist/app/features/sandbox/sandbox.run.js +41 -1
  33. package/dist/app/features/settings/categories.def.js +5 -2
  34. package/dist/app/features/settings/settings-rows.js +5 -1
  35. package/dist/app/features/settings/settings.run.d.ts +8 -0
  36. package/dist/app/features/settings/settings.run.js +9 -0
  37. package/dist/app/features/updates/updates.run.js +12 -2
  38. package/dist/app/features/vpn/vpn.def.d.ts +33 -0
  39. package/dist/app/features/vpn/vpn.def.js +30 -6
  40. package/dist/app/features/vpn/vpn.run.d.ts +11 -0
  41. package/dist/app/features/vpn/vpn.run.js +4 -1
  42. package/dist/app/kernel/rpc.d.ts +1 -1
  43. package/dist/app/registry.js +9 -2
  44. package/dist/bin/agent-sandbox.js +2 -2
  45. package/dist/bin/{chunk-M6XUQK53.js → chunk-2IMT7ZCT.js} +1 -1
  46. package/dist/bin/{chunk-3G53NNZP.js → chunk-DK4NTBUF.js} +69 -26
  47. package/dist/bin/{chunk-FQWYCF6L.js → chunk-Z2P5OGKL.js} +6427 -5259
  48. package/dist/bin/claude-host.js +2 -2
  49. package/dist/bin/claude-sbx.js +2 -2
  50. package/dist/bin/codex-sbx.js +2 -2
  51. package/dist/bin/gemini-sbx.js +2 -2
  52. package/dist/bin/sandbox.js +2 -2
  53. package/dist/bin/sbx-hide.js +2 -2
  54. package/dist/bin/sbx-show.js +2 -2
  55. package/dist/cli/commands/agent-commands.d.ts +0 -1
  56. package/dist/cli/commands/agent-commands.js +2 -32
  57. package/dist/cli/commands/index.d.ts +1 -1
  58. package/dist/cli/commands/index.js +1 -1
  59. package/dist/cli/commands/launch/launch.js +6 -3
  60. package/dist/cli/commands/launch/leftover-offer.d.ts +3 -0
  61. package/dist/cli/commands/launch/leftover-offer.js +26 -0
  62. package/dist/cli/commands/registry.js +3 -2
  63. package/dist/cli/commands/repair/checks/sandbox.js +29 -2
  64. package/dist/cli/manifest.d.ts +1 -1
  65. package/dist/cli/manifest.js +1 -1
  66. package/dist/cli/menu/menu.d.ts +1 -1
  67. package/dist/cli/menu/menu.js +3 -3
  68. package/dist/cli/menu/pages/agents-page.d.ts +7 -2
  69. package/dist/cli/menu/pages/agents-page.js +67 -11
  70. package/dist/cli/menu/pages/maintenance-page.js +1 -2
  71. package/dist/cli/menu/pages/settings-page.js +1 -0
  72. package/dist/cli/menu/views.js +2 -1
  73. package/dist/cli/services/agent-commands-service.d.ts +2 -0
  74. package/dist/cli/services/agent-commands-service.js +13 -7
  75. package/dist/cli/services/vpn-service.d.ts +5 -1
  76. package/dist/cli/services/vpn-service.js +6 -1
  77. package/dist/cli/tui/dialogs/text-screen.js +2 -1
  78. package/dist/cli/tui/terminal.js +3 -1
  79. package/dist/core/config/defaults.js +9 -0
  80. package/dist/core/detect/toolchains.js +9 -0
  81. package/dist/core/disk.d.ts +1 -5
  82. package/dist/core/disk.js +1 -16
  83. package/dist/core/schema/codex.d.ts +29 -0
  84. package/dist/core/schema/codex.js +24 -0
  85. package/dist/core/schema/config.d.ts +46 -14
  86. package/dist/core/schema/config.js +2 -0
  87. package/dist/core/schema/index.d.ts +1 -0
  88. package/dist/core/schema/index.js +1 -0
  89. package/dist/core/schema/toolchains.d.ts +4 -0
  90. package/dist/core/schema/toolchains.js +3 -0
  91. package/dist/core/types.d.ts +2 -0
  92. package/dist/guest/accounts/apply.d.ts +6 -6
  93. package/dist/guest/agents/agent-files.d.ts +26 -6
  94. package/dist/guest/agents/agent-files.js +84 -14
  95. package/dist/guest/agents/local-state.d.ts +5 -2
  96. package/dist/guest/agents/local-state.js +24 -10
  97. package/dist/guest/boot/boot.js +10 -5
  98. package/dist/guest/daemon/heartbeat.d.ts +7 -2
  99. package/dist/guest/daemon/heartbeat.js +34 -15
  100. package/dist/guest/daemon/idle-shutdown.js +2 -0
  101. package/dist/guest/daemon/sessions.js +2 -1
  102. package/dist/guest/entries/daemon.js +1 -1
  103. package/dist/guest/toolchains/installers.d.ts +6 -10
  104. package/dist/guest/toolchains/installers.js +4 -12
  105. package/dist/guest/toolchains/native/autosdk.d.ts +3 -0
  106. package/dist/guest/toolchains/native/autosdk.js +50 -0
  107. package/dist/guest/toolchains/native/unreal.js +3 -0
  108. package/dist/guest/toolchains/native/vcruntime.d.ts +0 -3
  109. package/dist/guest/toolchains/native/vcruntime.js +2 -5
  110. package/dist/guest/toolchains/native/visual-studio.d.ts +2 -2
  111. package/dist/guest/toolchains/native/visual-studio.js +100 -16
  112. package/dist/guest/toolchains/native/vs-request.d.ts +21 -0
  113. package/dist/guest/toolchains/native/vs-request.js +60 -1
  114. package/dist/guest/toolchains/not-yet.d.ts +1 -1
  115. package/dist/guest/toolchains/not-yet.js +3 -3
  116. package/dist/guest/toolchains/persisted.js +2 -7
  117. package/dist/guest/toolchains/plan.js +9 -4
  118. package/dist/guest/toolchains/signature.d.ts +5 -0
  119. package/dist/guest/toolchains/signature.js +22 -0
  120. package/dist/guest/vpn/tunnel.d.ts +3 -0
  121. package/dist/guest/vpn/tunnel.js +10 -12
  122. package/dist/guest/vpn/vpn.js +6 -1
  123. package/dist/host/accounts/leases.d.ts +3 -3
  124. package/dist/host/accounts/source.js +1 -1
  125. package/dist/host/agent-commands/commands.js +1 -1
  126. package/dist/host/agent-commands/forwarders.d.ts +14 -2
  127. package/dist/host/agent-commands/forwarders.js +31 -13
  128. package/dist/host/agent-commands/reconcile.d.ts +2 -1
  129. package/dist/host/agent-commands/reconcile.js +7 -8
  130. package/dist/host/agent-commands/user-path.d.ts +5 -0
  131. package/dist/host/agent-commands/user-path.js +13 -0
  132. package/dist/host/health/index.d.ts +1 -0
  133. package/dist/host/health/index.js +1 -0
  134. package/dist/host/health/leftovers.d.ts +3 -0
  135. package/dist/host/health/leftovers.js +40 -0
  136. package/dist/host/health/stale.d.ts +48 -0
  137. package/dist/host/health/stale.js +40 -0
  138. package/dist/host/launch/session-file.js +1 -0
  139. package/dist/host/life/crash-report.d.ts +1 -1
  140. package/dist/host/life/crash-report.js +5 -3
  141. package/dist/host/life/last-end.d.ts +7 -0
  142. package/dist/host/life/last-end.js +21 -0
  143. package/dist/host/life/life-record.d.ts +3 -0
  144. package/dist/host/life/life-record.js +2 -2
  145. package/dist/host/life/stop-events.d.ts +3 -1
  146. package/dist/host/life/stop-events.js +59 -0
  147. package/dist/host/life/stop-warning.js +3 -0
  148. package/dist/host/profile/accounts.js +2 -4
  149. package/dist/host/profile/claude-login.d.ts +3 -2
  150. package/dist/host/profile/claude-login.js +3 -13
  151. package/dist/host/sandbox/cleanup.d.ts +37 -0
  152. package/dist/host/sandbox/cleanup.js +82 -0
  153. package/dist/host/sandbox/index.d.ts +1 -0
  154. package/dist/host/sandbox/index.js +1 -0
  155. package/dist/host/sandbox/platform.js +1 -1
  156. package/dist/host/vpn/index.d.ts +2 -1
  157. package/dist/host/vpn/index.js +2 -1
  158. package/dist/host/vpn/last.d.ts +28 -0
  159. package/dist/host/vpn/last.js +43 -0
  160. package/dist/host/vpn/login.d.ts +3 -1
  161. package/dist/host/vpn/login.js +17 -3
  162. package/dist/runtime/agent.js +31 -1
  163. package/dist/runtime/daemon.js +1419 -1197
  164. package/dist/runtime/db.js +33 -1
  165. package/dist/runtime/main.js +1311 -1037
  166. package/dist/runtime/netproxy.js +25 -1
  167. package/dist/runtime/tool.js +241 -209
  168. package/dist/runtime/view.js +25 -1
  169. package/dist/selftest/agent-commands.test.js +45 -7
  170. package/dist/selftest/agent-paths.test.js +1 -12
  171. package/dist/selftest/agent-settings.test.d.ts +2 -0
  172. package/dist/selftest/agent-settings.test.js +94 -0
  173. package/dist/selftest/app.test.js +2 -1
  174. package/dist/selftest/cli.test.js +4 -0
  175. package/dist/selftest/heavy-load.test.js +119 -2
  176. package/dist/selftest/index.js +2 -0
  177. package/dist/selftest/menu.test.js +5 -3
  178. package/dist/selftest/pc-installs.test.js +5 -0
  179. package/dist/selftest/persisted-installs.test.js +69 -0
  180. package/dist/selftest/service-accounts.test.js +1 -1
  181. package/dist/selftest/support/engine-golden.js +1 -1
  182. package/dist/selftest/support/fixtures.d.ts +9 -0
  183. package/dist/selftest/vpn.test.js +51 -3
  184. package/dist/shared/accounts/anthropic.d.ts +5 -0
  185. package/dist/shared/accounts/anthropic.js +15 -0
  186. package/dist/shared/accounts/index.d.ts +1 -0
  187. package/dist/shared/accounts/index.js +1 -0
  188. package/dist/shared/channel/schema.d.ts +20 -0
  189. package/dist/shared/channel/schema.js +4 -1
  190. package/dist/shared/io/disk.d.ts +9 -0
  191. package/dist/shared/io/disk.js +19 -0
  192. package/dist/shared/io/index.d.ts +1 -0
  193. package/dist/shared/io/index.js +1 -0
  194. package/dist/shared/readiness.d.ts +9 -0
  195. package/dist/shared/readiness.js +11 -0
  196. package/dist/shared/signer.d.ts +24 -0
  197. package/dist/shared/signer.js +15 -0
  198. package/dist/shared/system.d.ts +1 -0
  199. package/dist/shared/system.js +1 -0
  200. package/dist/shared/vpn/failure.d.ts +10 -0
  201. package/dist/shared/vpn/failure.js +28 -0
  202. package/dist/sync/watcher.js +11 -4
  203. package/dist/web/index.html +1 -1
  204. package/dist/web/{main-6XYO5FZC.js → main-OUUQYNK4.js} +75 -74
  205. package/native/csharp/HcsHost/AssemblyInfo.cs +3 -3
  206. package/native/csharp/HostTools/AssemblyInfo.cs +3 -3
  207. package/native/csharp/HostTools/Program.cs +19 -2
  208. package/native/csharp/HostTools/UserPath.cs +17 -0
  209. package/native/csharp/StorageHelper/AssemblyInfo.cs +3 -3
  210. package/native/scripts/edit-user-path.ps1 +6 -2
  211. package/native/scripts/stop-events.ps1 +7 -3
  212. package/package.json +1 -1
  213. package/sandbox/scripts/get-signature.ps1 +13 -0
  214. package/schemas/config.schema.json +64 -0
  215. package/schemas/sandbox-config.schema.json +8 -0
  216. package/sandbox/scripts/get-signer.ps1 +0 -8
@@ -2,8 +2,8 @@
2
2
  import { createRequire as __createRequire } from 'node:module'; const require = __createRequire(import.meta.url);
3
3
  import {
4
4
  run
5
- } from "./chunk-3G53NNZP.js";
6
- import "./chunk-FQWYCF6L.js";
5
+ } from "./chunk-DK4NTBUF.js";
6
+ import "./chunk-Z2P5OGKL.js";
7
7
 
8
8
  // src/bin/claude-host.ts
9
9
  var args = process.argv.slice(2);
@@ -2,8 +2,8 @@
2
2
  import { createRequire as __createRequire } from 'node:module'; const require = __createRequire(import.meta.url);
3
3
  import {
4
4
  run
5
- } from "./chunk-3G53NNZP.js";
6
- import "./chunk-FQWYCF6L.js";
5
+ } from "./chunk-DK4NTBUF.js";
6
+ import "./chunk-Z2P5OGKL.js";
7
7
 
8
8
  // src/bin/claude-sbx.ts
9
9
  run(process.argv.slice(2), { harness: "claude" });
@@ -2,8 +2,8 @@
2
2
  import { createRequire as __createRequire } from 'node:module'; const require = __createRequire(import.meta.url);
3
3
  import {
4
4
  run
5
- } from "./chunk-3G53NNZP.js";
6
- import "./chunk-FQWYCF6L.js";
5
+ } from "./chunk-DK4NTBUF.js";
6
+ import "./chunk-Z2P5OGKL.js";
7
7
 
8
8
  // src/bin/codex-sbx.ts
9
9
  run(process.argv.slice(2), { harness: "codex" });
@@ -2,8 +2,8 @@
2
2
  import { createRequire as __createRequire } from 'node:module'; const require = __createRequire(import.meta.url);
3
3
  import {
4
4
  run
5
- } from "./chunk-3G53NNZP.js";
6
- import "./chunk-FQWYCF6L.js";
5
+ } from "./chunk-DK4NTBUF.js";
6
+ import "./chunk-Z2P5OGKL.js";
7
7
 
8
8
  // src/bin/gemini-sbx.ts
9
9
  run(process.argv.slice(2), { harness: "gemini" });
@@ -2,8 +2,8 @@
2
2
  import { createRequire as __createRequire } from 'node:module'; const require = __createRequire(import.meta.url);
3
3
  import {
4
4
  run
5
- } from "./chunk-3G53NNZP.js";
6
- import "./chunk-FQWYCF6L.js";
5
+ } from "./chunk-DK4NTBUF.js";
6
+ import "./chunk-Z2P5OGKL.js";
7
7
 
8
8
  // src/bin/sandbox.ts
9
9
  run(process.argv.slice(2));
@@ -2,8 +2,8 @@
2
2
  import { createRequire as __createRequire } from 'node:module'; const require = __createRequire(import.meta.url);
3
3
  import {
4
4
  run
5
- } from "./chunk-3G53NNZP.js";
6
- import "./chunk-FQWYCF6L.js";
5
+ } from "./chunk-DK4NTBUF.js";
6
+ import "./chunk-Z2P5OGKL.js";
7
7
 
8
8
  // src/bin/sbx-hide.ts
9
9
  run(process.argv.slice(2), { command: "hide" });
@@ -2,8 +2,8 @@
2
2
  import { createRequire as __createRequire } from 'node:module'; const require = __createRequire(import.meta.url);
3
3
  import {
4
4
  run
5
- } from "./chunk-3G53NNZP.js";
6
- import "./chunk-FQWYCF6L.js";
5
+ } from "./chunk-DK4NTBUF.js";
6
+ import "./chunk-Z2P5OGKL.js";
7
7
 
8
8
  // src/bin/sbx-show.ts
9
9
  run(process.argv.slice(2), { command: "show" });
@@ -3,4 +3,3 @@ import type { Reconciled } from '../../host/agent-commands/index.js';
3
3
  import { type Context } from './context.js';
4
4
  /** Brings this PC in line with agentCommands and says what changed (used after on/off, settings and launch). */
5
5
  export declare function syncAgentCommands(ctx: Context, config: Config, quiet?: boolean): Reconciled | null;
6
- export declare function agentCommandsCommand(ctx: Context): number;
@@ -1,5 +1,4 @@
1
- import { fail } from '../../core/util/index.js';
2
- import { c, table } from '../output/index.js';
1
+ import { c } from '../output/index.js';
3
2
  import { AgentCommandsService } from '../services/index.js';
4
3
  /** Brings this PC in line with agentCommands and says what changed (used after on/off, settings and launch). */
5
4
  export function syncAgentCommands(ctx, config, quiet = false) {
@@ -7,39 +6,10 @@ export function syncAgentCommands(ctx, config, quiet = false) {
7
6
  if (!result || !result.changed || quiet)
8
7
  return result;
9
8
  if (result.on) {
10
- console.log('Agent commands (Beta) are on for: ' +
11
- (result.commands.map((a) => a.command).join(', ') || 'no installed agents found'));
9
+ console.log('Agent commands are on for: ' + (result.commands.map((a) => a.command).join(', ') || 'no installed agents found'));
12
10
  console.log(c.dim('Open a new terminal, then type e.g. claude in any project folder. Run the real one: set SANDBOX_AGENT_DIRECT=1'));
13
11
  }
14
12
  else
15
13
  console.log('Agent commands are off: claude, codex ... run on this PC again (open a new terminal).');
16
14
  return result;
17
15
  }
18
- export function agentCommandsCommand(ctx) {
19
- const service = new AgentCommandsService(ctx);
20
- service.assertOnPc();
21
- const action = ctx.options.positionals[0] ?? 'status';
22
- if (action === 'status') {
23
- const { enabled, dir, found } = service.status();
24
- console.log('Agent commands (Beta): ' + (enabled ? c.green('on') : 'off') + c.dim(' (' + dir + ')'));
25
- if (found.length)
26
- table(found.map((a) => [a.command, 'sandboxed ' + a.harness + (enabled ? '' : ' when on'), c.dim('real: ' + a.real)]));
27
- else
28
- console.log(c.dim('No agents found on this PC (claude, codex, gemini ... on your PATH).'));
29
- if (!enabled)
30
- console.log(c.dim('Turn on with: sandbox agent-commands on'));
31
- return 0;
32
- }
33
- if (action !== 'on' && action !== 'off')
34
- fail('Unknown action "' + action + '".', 'Use: sandbox agent-commands [status|on|off]');
35
- const result = service.setEnabled(action === 'on');
36
- if (result?.on) {
37
- console.log('Agent commands (Beta) are on: ' +
38
- (result.commands.map((a) => a.command).join(', ') || 'no installed agents found yet'));
39
- console.log(c.dim('Open a new terminal, then type e.g. claude in a project folder: it starts in a hidden sandbox and attaches.'));
40
- console.log(c.dim('Run the real one on this PC: set SANDBOX_AGENT_DIRECT=1 (PowerShell: $env:SANDBOX_AGENT_DIRECT=1). Turn off: sandbox agent-commands off'));
41
- }
42
- else
43
- console.log('Agent commands are off: claude, codex ... run on this PC again. Open a new terminal.');
44
- return 0;
45
- }
@@ -7,5 +7,5 @@ export { contextOrDefaults } from './context.js';
7
7
  export { attach, db, exec } from './sessions/index.js';
8
8
  export { resume } from './resume.js';
9
9
  export { claudeHost, config, doctor, hostCommand, logs, migrateBeforeCommand, sync, updatesBeforeCommand, watcher, } from './maintenance/index.js';
10
- export { agentCommandsCommand, syncAgentCommands } from './agent-commands.js';
10
+ export { syncAgentCommands } from './agent-commands.js';
11
11
  export { HANDLERS, type Handler } from './registry.js';
@@ -7,5 +7,5 @@ export { contextOrDefaults } from './context.js';
7
7
  export { attach, db, exec } from './sessions/index.js';
8
8
  export { resume } from './resume.js';
9
9
  export { claudeHost, config, doctor, hostCommand, logs, migrateBeforeCommand, sync, updatesBeforeCommand, watcher, } from './maintenance/index.js';
10
- export { agentCommandsCommand, syncAgentCommands } from './agent-commands.js';
10
+ export { syncAgentCommands } from './agent-commands.js';
11
11
  export { HANDLERS } from './registry.js';
@@ -21,6 +21,7 @@ import { watcherRunning } from '../../../sync/index.js';
21
21
  import { protectionsOf } from '../../../core/local-config/index.js';
22
22
  import { errMsg } from '../../../core/util/index.js';
23
23
  import { askYesNo } from '../../../core/prompt.js';
24
+ import { offerLeftoverCleanup } from './leftover-offer.js';
24
25
  import { resolveRun } from '../../../core/run/index.js';
25
26
  import { c, table } from '../../output/index.js';
26
27
  import { syncAgentCommands } from '../agent-commands.js';
@@ -73,13 +74,13 @@ class Launcher {
73
74
  return this.launchSessions();
74
75
  return this.launchHere();
75
76
  }
76
- /** agentCommands (Beta) follows the config: a setting changed in the file takes effect here too. Never blocks. */
77
+ /** agentCommands follows the config: a setting changed in the file takes effect here too. Never blocks. */
77
78
  syncAgentCommands() {
78
79
  try {
79
80
  syncAgentCommands(this.ctx, this.ctx.loaded.config);
80
81
  }
81
82
  catch (e) {
82
- console.warn('Agent commands (Beta): ' + errMsg(e));
83
+ console.warn('Agent commands: ' + errMsg(e));
83
84
  }
84
85
  }
85
86
  /**
@@ -133,8 +134,10 @@ class Launcher {
133
134
  const provider = providerFor(config, config.storage.contained === 'off'
134
135
  ? {}
135
136
  : { contained: realContainedDeps(ctx.storageRoot, config.storage.containedMaxGb, Boolean(ctx.options.config)) });
136
- if (!generateOnly)
137
+ if (!generateOnly) {
138
+ await offerLeftoverCleanup(ctx);
137
139
  assertCanLaunch(isSandboxRunning() ? runningOwner() : undefined);
140
+ }
138
141
  // Contained storage: the image is mounted now (before anything reads or writes the data set), and let go of again
139
142
  // when the launch fails before the sandbox starts. The background watcher holds it from then on.
140
143
  const dataMappings = await this.prepareStorage(provider, run);
@@ -0,0 +1,3 @@
1
+ import type { Context } from '../context.js';
2
+ /** Does nothing unless leftovers are found; then asks (needs a terminal) and fails with the way out when declined. */
3
+ export declare function offerLeftoverCleanup(ctx: Context): Promise<void>;
@@ -0,0 +1,26 @@
1
+ // A launch that finds Windows Sandbox programs left behind by a sandbox that is gone (a crash, a kill from outside):
2
+ // Windows then says "already running" or that the sandbox was signed out. Asks to close them and goes on.
3
+ import { askYesNo } from '../../../core/prompt.js';
4
+ import { fail } from '../../../core/util/index.js';
5
+ import { readSandboxFacts } from '../../../host/health/index.js';
6
+ import { cleanUpLeftovers, endSandboxPrograms, isSandboxRunning, restartSandboxService, } from '../../../host/sandbox/index.js';
7
+ import { c } from '../../output/index.js';
8
+ /** Does nothing unless leftovers are found; then asks (needs a terminal) and fails with the way out when declined. */
9
+ export async function offerLeftoverCleanup(ctx) {
10
+ if (!isSandboxRunning())
11
+ return;
12
+ const { storageRoot, dataPath } = ctx;
13
+ const outcome = await cleanUpLeftovers({
14
+ facts: () => readSandboxFacts(storageRoot, dataPath, true),
15
+ end: endSandboxPrograms,
16
+ restartService: restartSandboxService,
17
+ confirm: (q) => {
18
+ for (const line of q.lines)
19
+ console.log(c.yellow(line));
20
+ return askYesNo(q.title, true);
21
+ },
22
+ say: (line) => console.log(line),
23
+ });
24
+ if (outcome === 'declined' || outcome === 'failed')
25
+ fail('Windows Sandbox programs from a sandbox that is gone are still there, so a new one cannot start.', 'Run sandbox cleanup (it asks first), or end the WindowsSandbox programs in Task Manager.');
26
+ }
@@ -3,6 +3,7 @@ import { launch } from './launch/index.js';
3
3
  import { setup } from './setup/index.js';
4
4
  import { cleanData, showData } from '../../app/features/data/index.js';
5
5
  import { attach, db, exec } from './sessions/index.js';
6
+ import { agentCommandsStatus } from '../../app/features/agent-commands/index.js';
6
7
  import { listAccounts } from '../../app/features/accounts/index.js';
7
8
  import { containedStatus, showStorage } from '../../app/features/storage/index.js';
8
9
  import { listProjects } from '../../app/features/projects/index.js';
@@ -11,7 +12,6 @@ import { fail } from '../../core/util/index.js';
11
12
  import { claudeHost, config, doctor, hostCommand, logs, sync, watcher } from './maintenance/index.js';
12
13
  import { engine } from './engine.js';
13
14
  import { resume } from './resume.js';
14
- import { agentCommandsCommand } from './agent-commands.js';
15
15
  import { vpn } from './vpn.js';
16
16
  import { github } from './github.js';
17
17
  import { web, webServer } from './web.js';
@@ -21,7 +21,6 @@ export const HANDLERS = {
21
21
  generate: (ctx) => launch(ctx, true),
22
22
  attach: attach,
23
23
  exec: exec,
24
- 'agent-commands': agentCommandsCommand,
25
24
  db: db,
26
25
  resume: resume,
27
26
  // The bare command lists; its actions (save, use ...) are the operations' hints.
@@ -52,6 +51,8 @@ export const HANDLERS = {
52
51
  'host-claude': claudeHost,
53
52
  host: hostCommand,
54
53
  _watch: watcher,
54
+ // The bare command shows the status; on and off are the operations' hints (src/app/features/agent-commands).
55
+ 'agent-commands': definitionHandler(agentCommandsStatus),
55
56
  vpn: vpn,
56
57
  github: github,
57
58
  web: web,
@@ -6,10 +6,10 @@ import path from 'node:path';
6
6
  import { diskUsage, humanBytes } from '../../../../core/disk.js';
7
7
  import { dirExists, fileExists, isLink } from '../../../../core/util/index.js';
8
8
  import { SandboxChannel } from '../../../../host/channel/index.js';
9
- import { orphanWatchers, runStateProblems, staleLaunchFolders, watcherRecords } from '../../../../host/health/index.js';
9
+ import { judgeSandbox, orphanWatchers, readSandboxFacts, runStateProblems, staleLaunchFolders, watcherRecords, } from '../../../../host/health/index.js';
10
10
  import { vgpuSource, vgpuWarning } from '../../../../host/launch/index.js';
11
11
  import { hostToolsExe } from '../../../../host/native.js';
12
- import { sandboxFeatureInstalled } from '../../../../host/sandbox/index.js';
12
+ import { endSandboxPrograms, sandboxFeatureInstalled } from '../../../../host/sandbox/index.js';
13
13
  import { assertTestWritable, refuseInSelfTest } from '../../../../host/test-guard.js';
14
14
  import { conptyDir, terminalDir, terminalExe } from '../../../../host/tools/index.js';
15
15
  import { RunDir } from '../../../../shared/channel/index.js';
@@ -90,6 +90,33 @@ export const SANDBOX_CHECKS = [
90
90
  };
91
91
  },
92
92
  },
93
+ {
94
+ id: 'sandbox-leftover',
95
+ group: 'Sandbox',
96
+ name: 'Leftover Windows Sandbox programs',
97
+ run: async (env) => {
98
+ if (!env.running)
99
+ return ok('none');
100
+ const found = judgeSandbox(await readSandboxFacts(env.storageRoot, env.dataPath, false));
101
+ if (found.state !== 'leftover')
102
+ return ok(found.state === 'none' ? 'none' : 'the sandbox is working');
103
+ return {
104
+ status: 'needs-you',
105
+ detail: found.why,
106
+ means: 'A sandbox that died leaves its Windows programs behind, and Windows then refuses to start a new one ("the sandbox was signed out" or "already running").',
107
+ plan: 'close ' + found.images.join(', ') + ' (only the ones in your Windows session)',
108
+ data: found.pids,
109
+ };
110
+ },
111
+ fix: (_env, finding) => {
112
+ const pids = Array.isArray(finding.data) ? finding.data : [];
113
+ return endSandboxPrograms(pids)
114
+ ? { done: 'closed the leftover Windows Sandbox programs' }
115
+ : {
116
+ done: 'Windows would not close some of them; sandbox cleanup can also restart the Windows Sandbox service',
117
+ };
118
+ },
119
+ },
93
120
  {
94
121
  id: 'sandbox-run-state',
95
122
  group: 'Sandbox',
@@ -702,7 +702,7 @@ declare const COMMAND_LIST: readonly [{
702
702
  readonly examples: readonly ["sandbox config set sandbox.memoryMb 8192", "sandbox config get sandbox.memoryMb", "sandbox config reset sandbox.memoryMb", "sandbox config list", "sandbox config export team-settings.json", "sandbox config import team-settings.json"];
703
703
  }, {
704
704
  readonly name: "agent-commands";
705
- readonly summary: "(Beta) make claude, codex ... on this PC start in a hidden sandbox and attach";
705
+ readonly summary: "make claude, codex ... on this PC start in a hidden sandbox and attach";
706
706
  readonly group: "Advanced";
707
707
  readonly args: readonly [{
708
708
  readonly name: "action";
@@ -491,7 +491,7 @@ const COMMAND_LIST = [
491
491
  },
492
492
  {
493
493
  name: 'agent-commands',
494
- summary: '(Beta) make claude, codex ... on this PC start in a hidden sandbox and attach',
494
+ summary: 'make claude, codex ... on this PC start in a hidden sandbox and attach',
495
495
  group: 'Advanced',
496
496
  args: [{ name: 'action', description: 'on, off or status', required: false, choices: ['status', 'on', 'off'] }],
497
497
  options: [],
@@ -23,6 +23,6 @@ export declare class Menu {
23
23
  private execute;
24
24
  /** Shows the end of a log on the normal screen. */
25
25
  private view;
26
- /** After a save: agent commands (Beta) may have been switched, which changes this PC. */
26
+ /** After a save: agent commands may have been switched, which changes this PC. */
27
27
  private applyAgentCommands;
28
28
  }
@@ -168,7 +168,7 @@ export class Menu {
168
168
  await pressAnyKey(Theme.muted('Press any key for the menu…'));
169
169
  });
170
170
  }
171
- /** After a save: agent commands (Beta) may have been switched, which changes this PC. */
171
+ /** After a save: agent commands may have been switched, which changes this PC. */
172
172
  applyAgentCommands(saved) {
173
173
  if (!saved.files.some((f) => !f.isProject))
174
174
  return undefined;
@@ -177,11 +177,11 @@ export class Menu {
177
177
  if (!result?.changed)
178
178
  return undefined;
179
179
  return result.on
180
- ? 'Agent commands (Beta) are on: open a new terminal.'
180
+ ? 'Agent commands are on: open a new terminal.'
181
181
  : 'Agent commands are off: claude, codex ... run on this PC again (open a new terminal).';
182
182
  }
183
183
  catch (e) {
184
- return 'Agent commands (Beta): ' + errMsg(e);
184
+ return 'Agent commands: ' + errMsg(e);
185
185
  }
186
186
  }
187
187
  }
@@ -1,4 +1,5 @@
1
- import { type AgentsList } from '../../../app/features/agents/index.js';
1
+ import type { AgentCommandsStatus as AgentCommandsFacts } from '../../../app/features/agent-commands/index.js';
2
+ import { type AgentEnv, type AgentsList } from '../../../app/features/agents/index.js';
2
3
  import type { ConfigFile } from '../../../app/features/settings/index.js';
3
4
  import type { Context } from '../../commands/context.js';
4
5
  import { type App, type Category, type ListOptions, type ListResult } from '../../tui/index.js';
@@ -17,8 +18,12 @@ export interface AgentsModel {
17
18
  readonly window: WindowChoice;
18
19
  /** Agents that can be run on this PC without the sandbox (see hostAgentsToShow). */
19
20
  readonly hostAgents: readonly string[];
21
+ /** Whether agent commands are on and which agents were found (the "On this PC" category). */
22
+ readonly agentCommands?: AgentCommandsFacts;
20
23
  /** The agents you added yourself: only these can be removed (the built-in ones stay). */
21
24
  readonly removable?: readonly string[];
25
+ /** Claude's environment variables (the Claude environment tab); missing when Claude is not an agent here. */
26
+ readonly claudeEnv?: AgentEnv;
22
27
  }
23
28
  /** The agents page's data as the agents view takes it. Pure. */
24
29
  export declare function agentsList(m: AgentsModel): AgentsList;
@@ -34,7 +39,7 @@ export declare function startArgv(m: AgentsModel, agent: string, cwd: string): s
34
39
  /** Settings has unsaved changes to the agents (their list or the default): the agent operations wait for them. */
35
40
  export declare function agentsHaveStagedChanges(file: ConfigFile, draft: Draft): boolean;
36
41
  /** The categories of the agents section; "On this PC" only when there is something in it. Pure. */
37
- export declare function agentCategories(m: Pick<AgentsModel, 'folder' | 'hostAgents'>): Category[];
42
+ export declare function agentCategories(m: Pick<AgentsModel, 'folder' | 'hostAgents' | 'claudeEnv'>): Category[];
38
43
  /** The list for the agents section. Pure. */
39
44
  export declare function agentsOptions(m: AgentsModel, cwd?: string): ListOptions<string>;
40
45
  export declare class AgentsPage extends ListPage<string> {
@@ -4,7 +4,7 @@
4
4
  // are on.
5
5
  import fs from 'node:fs';
6
6
  import path from 'node:path';
7
- import { agentsPage, listAgents } from '../../../app/features/agents/index.js';
7
+ import { agentEnv, agentEnvPage, agentsPage, listAgents, } from '../../../app/features/agents/index.js';
8
8
  import { readQuery } from '../../../app/registry.js';
9
9
  import { commandLine, readHistory } from '../../history.js';
10
10
  import { AgentCommandsService } from '../../services/index.js';
@@ -16,6 +16,10 @@ import { menuView } from '../render-view.js';
16
16
  import { askPath } from '../settings/ask-path.js';
17
17
  import { SeveralAgents } from '../several.js';
18
18
  import { ListPage } from './list-page.js';
19
+ /** What the agent commands section shows (before the status is read: off, nothing found). Pure. */
20
+ function commandsOf(m) {
21
+ return m.agentCommands ?? { enabled: m.hostAgents.length > 0, dir: '', found: [] };
22
+ }
19
23
  /** The agents page's data as the agents view takes it. Pure. */
20
24
  export function agentsList(m) {
21
25
  const removable = m.removable ?? [];
@@ -32,7 +36,13 @@ export function agentsList(m) {
32
36
  }
33
37
  /** The Start category: the agents view's rows and keys, as the menu draws them. Pure. */
34
38
  function startView(m) {
35
- return menuView(agentsPage(agentsList(m)), { category: 'start', refresh: false });
39
+ const page = agentsPage(agentsList(m), commandsOf(m));
40
+ return menuView({ ...page, sections: page.sections.slice(0, 1) }, { category: 'start', refresh: false });
41
+ }
42
+ /** The On this PC category: the agent commands switch and the agents it covers (the view's second section). Pure. */
43
+ function commandsView(m) {
44
+ const page = agentsPage(agentsList(m), commandsOf(m));
45
+ return menuView({ ...page, sections: page.sections.slice(1) }, { category: 'host', refresh: false });
36
46
  }
37
47
  /**
38
48
  * The "not sandboxed" entries: only while agent commands are on (typing the agent then starts the sandbox, so a way to
@@ -65,16 +75,21 @@ export function agentCategories(m) {
65
75
  label: 'Start',
66
76
  description: 'Agents you can start in ' + m.folder + '. Add an agent at the end of the list.',
67
77
  },
68
- { id: 'options', label: 'Options', description: 'How a start goes, and the other ways to start.' },
69
- ...(m.hostAgents.length
78
+ ...(m.claudeEnv
70
79
  ? [
71
80
  {
72
- id: 'host',
73
- label: 'On this PC',
74
- description: 'The real agents on this PC, with no sandbox around them.',
81
+ id: 'env',
82
+ label: 'Claude environment',
83
+ description: 'Models, limits, how many subagents run at once, and other variables of Claude.',
75
84
  },
76
85
  ]
77
86
  : []),
87
+ { id: 'options', label: 'Options', description: 'How a start goes, and the other ways to start.' },
88
+ {
89
+ id: 'host',
90
+ label: 'On this PC',
91
+ description: 'Agent commands: typing claude, codex ... here starts them in a sandbox. The real agents are listed too.',
92
+ },
78
93
  ];
79
94
  }
80
95
  /** A Start row of the view with the menu's own lines: the default's accent and the command a start runs. Pure. */
@@ -88,9 +103,15 @@ function startRow(item, m, cwd) {
88
103
  details: [Theme.muted('Command ') + commandLine(startArgv(m, name, cwd))],
89
104
  };
90
105
  }
106
+ /** The Claude environment tab: the environment view's rows and keys, as the menu draws them. Pure. */
107
+ function envView(m) {
108
+ return m.claudeEnv ? menuView(agentEnvPage(m.claudeEnv), { category: 'env', refresh: false }) : undefined;
109
+ }
91
110
  /** The list for the agents section. Pure. */
92
111
  export function agentsOptions(m, cwd = process.cwd()) {
93
112
  const view = startView(m);
113
+ const env = envView(m);
114
+ const commands = commandsView(m);
94
115
  return {
95
116
  path: [],
96
117
  categories: agentCategories(m),
@@ -108,6 +129,8 @@ export function agentsOptions(m, cwd = process.cwd()) {
108
129
  ]
109
130
  : []),
110
131
  ...view.items.map((item) => startRow(item, m, cwd)),
132
+ ...(env?.items ?? []),
133
+ ...commands.items,
111
134
  {
112
135
  value: 'opt:attach',
113
136
  label: 'Attach this terminal',
@@ -161,8 +184,21 @@ export function agentsOptions(m, cwd = process.cwd()) {
161
184
  description: 'Runs the real ' + agent + ' on this PC, with no sandbox around it.',
162
185
  })),
163
186
  ],
164
- enterLabel: (_category, id) => ENTER[id ?? ''] ?? (id?.startsWith('agent:') ? 'start' : id?.startsWith('host:') ? 'run' : 'open'),
165
- actions: (category) => (category === 'start' ? view.actions : []),
187
+ enterLabel: (category, id) => ENTER[id ?? ''] ??
188
+ (id?.startsWith('agent:')
189
+ ? 'start'
190
+ : id?.startsWith('host:')
191
+ ? 'run'
192
+ : category === 'host'
193
+ ? commands.enterLabel(category, id)
194
+ : 'open'),
195
+ actions: (category) => category === 'start'
196
+ ? view.actions
197
+ : category === 'env'
198
+ ? (env?.actions ?? [])
199
+ : category === 'host'
200
+ ? commands.actions
201
+ : [],
166
202
  };
167
203
  }
168
204
  /** What Enter does on the rows that are not an agent. */
@@ -199,7 +235,7 @@ export class AgentsPage extends ListPage {
199
235
  current() {
200
236
  if (!this.facts)
201
237
  throw new Error('The agents tab was not loaded');
202
- const { agents, commands, last } = this.facts;
238
+ const { agents, commands, last, env } = this.facts;
203
239
  return {
204
240
  folder: this.folder,
205
241
  defaultAgent: agents.defaultAgent,
@@ -208,7 +244,13 @@ export class AgentsPage extends ListPage {
208
244
  attach: this.attach,
209
245
  window: this.window,
210
246
  hostAgents: hostAgentsToShow(commands.enabled, commands.found),
247
+ agentCommands: {
248
+ enabled: commands.enabled,
249
+ dir: commands.dir,
250
+ found: commands.found.map((a) => ({ command: a.command, harness: a.harness, real: a.real })),
251
+ },
211
252
  removable: agents.agents.filter((a) => a.removable).map((a) => a.name),
253
+ ...(env ? { claudeEnv: env } : {}),
212
254
  };
213
255
  }
214
256
  /** The cursor starts on the default agent. */
@@ -220,6 +262,9 @@ export class AgentsPage extends ListPage {
220
262
  agents: await readQuery(listAgents, this.ctx),
221
263
  commands: await new AgentCommandsService(this.ctx).statusAsync(),
222
264
  last: readHistory()[0]?.argv,
265
+ env: 'claude' in this.ctx.loaded.config.harnesses
266
+ ? await readQuery(agentEnv, this.ctx, { agent: 'claude' })
267
+ : undefined,
223
268
  };
224
269
  this.model = this.current();
225
270
  }
@@ -229,9 +274,20 @@ export class AgentsPage extends ListPage {
229
274
  }
230
275
  async act(result) {
231
276
  const model = this.current();
232
- const chosen = startView(model).resolve(result);
277
+ const env = this.category === 'env' ? envView(model) : undefined;
278
+ if (env) {
279
+ const picked = env.resolve(result);
280
+ if (picked?.kind === 'action')
281
+ return this.operate(picked.action);
282
+ return {};
283
+ }
284
+ const chosen = startView(model).resolve(result) ?? commandsView(model).resolve(result);
233
285
  if (chosen?.kind === 'action') {
234
286
  const { action } = chosen;
287
+ if (action.kind === 'link' && action.target.kind === 'view') {
288
+ await this.selectCategory('env');
289
+ return {};
290
+ }
235
291
  // A start keeps the options of this page (attach, window, folder), which the plain link does not know.
236
292
  if (action.kind === 'link' && action.target.kind === 'start-agent')
237
293
  return { exit: { kind: 'run', argv: startArgv(model, action.target.agent, process.cwd()) } };
@@ -51,7 +51,7 @@ const CATEGORIES = [
51
51
  },
52
52
  { id: 'more', label: 'More commands', description: 'Less common commands.' },
53
53
  ];
54
- const MORE_COMMANDS = ['sync', 'config', 'generate', 'exec', 'agent-commands'];
54
+ const MORE_COMMANDS = ['sync', 'config', 'generate', 'exec'];
55
55
  const NO_LOGS = { folder: '', files: [], crash: false };
56
56
  /** Until the status is read: the server counts as not running. */
57
57
  const NO_WEB = { running: false, port: null, address: null };
@@ -61,7 +61,6 @@ const MORE_LABELS = {
61
61
  config: 'Your settings file',
62
62
  generate: 'Preview a start',
63
63
  exec: 'Run a command in the sandbox',
64
- 'agent-commands': 'Agent commands (Beta)',
65
64
  };
66
65
  /** The Data & storage category's rows and keys once measured: the data view (src/app/features/data) as menu rows. */
67
66
  function dataMenuView(data) {
@@ -158,6 +158,7 @@ export class SettingsPage extends ListPage {
158
158
  login: 'none',
159
159
  loginSavedAt: null,
160
160
  running: null,
161
+ lastFailure: null,
161
162
  };
162
163
  /** Whether a GitHub token is saved (never the token): the token row needs only its state. */
163
164
  github = { shareToken: false, gitAccess: 'full', token: 'none', kind: null, savedAt: null };
@@ -1,7 +1,7 @@
1
1
  // The shared core's views the menu shows, and in which section (the parity self-test reads this list: an operation or
2
2
  // query is reachable in the menu when a view here offers it). Settings is drawn by its own page over the same rows,
3
3
  // because the menu stages changes until Ctrl+S.
4
- import { agentsView } from '../../app/features/agents/index.js';
4
+ import { agentEnvView, agentsView } from '../../app/features/agents/index.js';
5
5
  import { dataView } from '../../app/features/data/index.js';
6
6
  import { accountsView } from '../../app/features/accounts/index.js';
7
7
  import { appsView } from '../../app/features/apps/index.js';
@@ -21,6 +21,7 @@ import { githubView } from '../../app/features/github/index.js';
21
21
  import { accessView } from '../../app/features/access/index.js';
22
22
  export const MENU_VIEWS = [
23
23
  { view: agentsView, section: 'Agents' },
24
+ { view: agentEnvView, section: 'Agents' },
24
25
  { view: sessionsView, section: 'Sessions' },
25
26
  { view: sandboxView, section: 'Sandbox' },
26
27
  { view: settingsView, section: 'Settings' },
@@ -14,6 +14,8 @@ export declare class AgentCommandsService {
14
14
  constructor(ctx: Context);
15
15
  /** Agent commands change this PC's PATH, so they are set up from the PC, not inside the sandbox. */
16
16
  assertOnPc(): void;
17
+ /** Your settings as they are on disk now (a switch made a moment ago, here or in Settings, counts). */
18
+ private config;
17
19
  status(): AgentCommandsStatus;
18
20
  /** status() that does not block the program (the menu asks while it stays responsive). */
19
21
  statusAsync(): Promise<AgentCommandsStatus>;