windows-development-sandbox 1.18.4 → 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 (181) 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 +132 -1
  28. package/dist/app/features/defs.js +9 -0
  29. package/dist/app/features/settings/categories.def.js +5 -2
  30. package/dist/app/features/settings/settings-rows.js +5 -1
  31. package/dist/app/features/settings/settings.run.d.ts +8 -0
  32. package/dist/app/features/settings/settings.run.js +9 -0
  33. package/dist/app/features/updates/updates.run.js +12 -2
  34. package/dist/app/features/vpn/vpn.def.d.ts +33 -0
  35. package/dist/app/features/vpn/vpn.def.js +30 -6
  36. package/dist/app/features/vpn/vpn.run.d.ts +11 -0
  37. package/dist/app/features/vpn/vpn.run.js +4 -1
  38. package/dist/app/kernel/rpc.d.ts +1 -1
  39. package/dist/app/registry.js +7 -1
  40. package/dist/bin/agent-sandbox.js +2 -2
  41. package/dist/bin/{chunk-MY7NLQQY.js → chunk-2IMT7ZCT.js} +1 -1
  42. package/dist/bin/{chunk-N4GXYTLV.js → chunk-DK4NTBUF.js} +69 -26
  43. package/dist/bin/{chunk-FRPLQLSY.js → chunk-Z2P5OGKL.js} +4323 -3463
  44. package/dist/bin/claude-host.js +2 -2
  45. package/dist/bin/claude-sbx.js +2 -2
  46. package/dist/bin/codex-sbx.js +2 -2
  47. package/dist/bin/gemini-sbx.js +2 -2
  48. package/dist/bin/sandbox.js +2 -2
  49. package/dist/bin/sbx-hide.js +2 -2
  50. package/dist/bin/sbx-show.js +2 -2
  51. package/dist/cli/commands/agent-commands.d.ts +0 -1
  52. package/dist/cli/commands/agent-commands.js +2 -32
  53. package/dist/cli/commands/index.d.ts +1 -1
  54. package/dist/cli/commands/index.js +1 -1
  55. package/dist/cli/commands/launch/launch.js +2 -2
  56. package/dist/cli/commands/registry.js +3 -2
  57. package/dist/cli/manifest.d.ts +1 -1
  58. package/dist/cli/manifest.js +1 -1
  59. package/dist/cli/menu/menu.d.ts +1 -1
  60. package/dist/cli/menu/menu.js +3 -3
  61. package/dist/cli/menu/pages/agents-page.d.ts +7 -2
  62. package/dist/cli/menu/pages/agents-page.js +67 -11
  63. package/dist/cli/menu/pages/maintenance-page.js +1 -2
  64. package/dist/cli/menu/pages/settings-page.js +1 -0
  65. package/dist/cli/menu/views.js +2 -1
  66. package/dist/cli/services/agent-commands-service.d.ts +2 -0
  67. package/dist/cli/services/agent-commands-service.js +13 -7
  68. package/dist/cli/services/vpn-service.d.ts +5 -1
  69. package/dist/cli/services/vpn-service.js +6 -1
  70. package/dist/cli/tui/dialogs/text-screen.js +2 -1
  71. package/dist/cli/tui/terminal.js +3 -1
  72. package/dist/core/config/defaults.js +9 -0
  73. package/dist/core/detect/toolchains.js +9 -0
  74. package/dist/core/disk.d.ts +1 -5
  75. package/dist/core/disk.js +1 -16
  76. package/dist/core/schema/codex.d.ts +29 -0
  77. package/dist/core/schema/codex.js +24 -0
  78. package/dist/core/schema/config.d.ts +46 -14
  79. package/dist/core/schema/config.js +2 -0
  80. package/dist/core/schema/index.d.ts +1 -0
  81. package/dist/core/schema/index.js +1 -0
  82. package/dist/core/schema/toolchains.d.ts +4 -0
  83. package/dist/core/schema/toolchains.js +3 -0
  84. package/dist/core/types.d.ts +2 -0
  85. package/dist/guest/accounts/apply.d.ts +6 -6
  86. package/dist/guest/agents/agent-files.d.ts +26 -6
  87. package/dist/guest/agents/agent-files.js +84 -14
  88. package/dist/guest/agents/local-state.d.ts +5 -2
  89. package/dist/guest/agents/local-state.js +24 -10
  90. package/dist/guest/boot/boot.js +10 -5
  91. package/dist/guest/daemon/sessions.js +2 -1
  92. package/dist/guest/toolchains/installers.d.ts +6 -10
  93. package/dist/guest/toolchains/installers.js +4 -12
  94. package/dist/guest/toolchains/native/autosdk.d.ts +3 -0
  95. package/dist/guest/toolchains/native/autosdk.js +50 -0
  96. package/dist/guest/toolchains/native/unreal.js +3 -0
  97. package/dist/guest/toolchains/native/vcruntime.d.ts +0 -3
  98. package/dist/guest/toolchains/native/vcruntime.js +2 -5
  99. package/dist/guest/toolchains/native/visual-studio.d.ts +2 -2
  100. package/dist/guest/toolchains/native/visual-studio.js +100 -16
  101. package/dist/guest/toolchains/native/vs-request.d.ts +21 -0
  102. package/dist/guest/toolchains/native/vs-request.js +60 -1
  103. package/dist/guest/toolchains/not-yet.d.ts +1 -1
  104. package/dist/guest/toolchains/not-yet.js +3 -3
  105. package/dist/guest/toolchains/persisted.js +2 -7
  106. package/dist/guest/toolchains/plan.js +9 -4
  107. package/dist/guest/toolchains/signature.d.ts +5 -0
  108. package/dist/guest/toolchains/signature.js +22 -0
  109. package/dist/guest/vpn/tunnel.d.ts +3 -0
  110. package/dist/guest/vpn/tunnel.js +10 -12
  111. package/dist/guest/vpn/vpn.js +6 -1
  112. package/dist/host/accounts/leases.d.ts +3 -3
  113. package/dist/host/accounts/source.js +1 -1
  114. package/dist/host/agent-commands/commands.js +1 -1
  115. package/dist/host/agent-commands/forwarders.d.ts +14 -2
  116. package/dist/host/agent-commands/forwarders.js +31 -13
  117. package/dist/host/agent-commands/reconcile.d.ts +2 -1
  118. package/dist/host/agent-commands/reconcile.js +7 -8
  119. package/dist/host/agent-commands/user-path.d.ts +5 -0
  120. package/dist/host/agent-commands/user-path.js +13 -0
  121. package/dist/host/launch/session-file.js +1 -0
  122. package/dist/host/profile/accounts.js +2 -4
  123. package/dist/host/profile/claude-login.d.ts +3 -2
  124. package/dist/host/profile/claude-login.js +3 -13
  125. package/dist/host/vpn/index.d.ts +2 -1
  126. package/dist/host/vpn/index.js +2 -1
  127. package/dist/host/vpn/last.d.ts +28 -0
  128. package/dist/host/vpn/last.js +43 -0
  129. package/dist/host/vpn/login.d.ts +3 -1
  130. package/dist/host/vpn/login.js +17 -3
  131. package/dist/runtime/agent.js +28 -0
  132. package/dist/runtime/daemon.js +1117 -912
  133. package/dist/runtime/db.js +30 -0
  134. package/dist/runtime/main.js +1308 -1036
  135. package/dist/runtime/netproxy.js +22 -0
  136. package/dist/runtime/tool.js +238 -208
  137. package/dist/runtime/view.js +22 -0
  138. package/dist/selftest/agent-commands.test.js +45 -7
  139. package/dist/selftest/agent-paths.test.js +1 -12
  140. package/dist/selftest/agent-settings.test.d.ts +2 -0
  141. package/dist/selftest/agent-settings.test.js +94 -0
  142. package/dist/selftest/app.test.js +2 -1
  143. package/dist/selftest/cli.test.js +4 -0
  144. package/dist/selftest/index.js +2 -0
  145. package/dist/selftest/menu.test.js +5 -3
  146. package/dist/selftest/pc-installs.test.js +5 -0
  147. package/dist/selftest/persisted-installs.test.js +69 -0
  148. package/dist/selftest/service-accounts.test.js +1 -1
  149. package/dist/selftest/support/engine-golden.js +1 -1
  150. package/dist/selftest/support/fixtures.d.ts +9 -0
  151. package/dist/selftest/vpn.test.js +51 -3
  152. package/dist/shared/accounts/anthropic.d.ts +5 -0
  153. package/dist/shared/accounts/anthropic.js +15 -0
  154. package/dist/shared/accounts/index.d.ts +1 -0
  155. package/dist/shared/accounts/index.js +1 -0
  156. package/dist/shared/channel/schema.d.ts +19 -0
  157. package/dist/shared/channel/schema.js +2 -0
  158. package/dist/shared/io/disk.d.ts +9 -0
  159. package/dist/shared/io/disk.js +19 -0
  160. package/dist/shared/io/index.d.ts +1 -0
  161. package/dist/shared/io/index.js +1 -0
  162. package/dist/shared/readiness.d.ts +9 -0
  163. package/dist/shared/readiness.js +11 -0
  164. package/dist/shared/signer.d.ts +24 -0
  165. package/dist/shared/signer.js +15 -0
  166. package/dist/shared/vpn/failure.d.ts +10 -0
  167. package/dist/shared/vpn/failure.js +28 -0
  168. package/dist/sync/watcher.js +2 -0
  169. package/dist/web/index.html +1 -1
  170. package/dist/web/{main-GVZWWP4I.js → main-OUUQYNK4.js} +75 -74
  171. package/native/csharp/HcsHost/AssemblyInfo.cs +3 -3
  172. package/native/csharp/HostTools/AssemblyInfo.cs +3 -3
  173. package/native/csharp/HostTools/Program.cs +14 -1
  174. package/native/csharp/HostTools/UserPath.cs +17 -0
  175. package/native/csharp/StorageHelper/AssemblyInfo.cs +3 -3
  176. package/native/scripts/edit-user-path.ps1 +6 -2
  177. package/package.json +1 -1
  178. package/sandbox/scripts/get-signature.ps1 +13 -0
  179. package/schemas/config.schema.json +64 -0
  180. package/schemas/sandbox-config.schema.json +8 -0
  181. 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-N4GXYTLV.js";
6
- import "./chunk-FRPLQLSY.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-N4GXYTLV.js";
6
- import "./chunk-FRPLQLSY.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-N4GXYTLV.js";
6
- import "./chunk-FRPLQLSY.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-N4GXYTLV.js";
6
- import "./chunk-FRPLQLSY.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-N4GXYTLV.js";
6
- import "./chunk-FRPLQLSY.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-N4GXYTLV.js";
6
- import "./chunk-FRPLQLSY.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-N4GXYTLV.js";
6
- import "./chunk-FRPLQLSY.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';
@@ -74,13 +74,13 @@ class Launcher {
74
74
  return this.launchSessions();
75
75
  return this.launchHere();
76
76
  }
77
- /** 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. */
78
78
  syncAgentCommands() {
79
79
  try {
80
80
  syncAgentCommands(this.ctx, this.ctx.loaded.config);
81
81
  }
82
82
  catch (e) {
83
- console.warn('Agent commands (Beta): ' + errMsg(e));
83
+ console.warn('Agent commands: ' + errMsg(e));
84
84
  }
85
85
  }
86
86
  /**
@@ -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,
@@ -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>;
@@ -1,12 +1,12 @@
1
- // "Agent commands" (Beta): typing claude, codex ... on this PC starts them in a hidden sandbox. Never prints.
1
+ // "Agent commands": typing claude, codex ... on this PC starts them in a hidden sandbox. Never prints.
2
2
  import fs from 'node:fs';
3
- import path from 'node:path';
4
3
  import { loadConfig, userConfigPath } from '../../core/config/index.js';
5
4
  import { fail } from '../../core/util/index.js';
6
- import { agentCommands, agentCommandsAsync, commandsDir, reconcileAgentCommands, } from '../../host/agent-commands/index.js';
5
+ import { agentCommands, agentCommandsAsync, commandsDir, forwardTarget, reconcileAgentCommands, } from '../../host/agent-commands/index.js';
6
+ import { InstallLayout } from '../../host/install/layout.js';
7
7
  import { insideSandbox } from '../../host/sandbox/index.js';
8
8
  import { writeFileAtomic } from '../../shared/io/index.js';
9
- import { editConfigText } from '../../app/features/settings/index.js';
9
+ import { editConfigText } from '../../app/features/settings/settings-file.js';
10
10
  export class AgentCommandsService {
11
11
  ctx;
12
12
  constructor(ctx) {
@@ -17,8 +17,12 @@ export class AgentCommandsService {
17
17
  if (insideSandbox())
18
18
  fail("Agent commands change your PC's PATH, so they are set up from your PC, not inside the sandbox.", 'Run sandbox agent-commands on in a terminal on your PC.');
19
19
  }
20
+ /** Your settings as they are on disk now (a switch made a moment ago, here or in Settings, counts). */
21
+ config() {
22
+ return loadConfig(this.ctx.loaded.configPath ?? undefined).config;
23
+ }
20
24
  status() {
21
- const { config } = this.ctx.loaded;
25
+ const config = this.config();
22
26
  return {
23
27
  enabled: config.agentCommands.enabled,
24
28
  dir: commandsDir(this.ctx.storageRoot),
@@ -27,7 +31,7 @@ export class AgentCommandsService {
27
31
  }
28
32
  /** status() that does not block the program (the menu asks while it stays responsive). */
29
33
  async statusAsync() {
30
- const { config } = this.ctx.loaded;
34
+ const config = this.config();
31
35
  return {
32
36
  enabled: config.agentCommands.enabled,
33
37
  dir: commandsDir(this.ctx.storageRoot),
@@ -48,6 +52,8 @@ export class AgentCommandsService {
48
52
  reconcile(config) {
49
53
  if (insideSandbox())
50
54
  return null;
51
- return reconcileAgentCommands(config, this.ctx.storageRoot, process.execPath, path.join(this.ctx.packageRoot, 'dist', 'bin', 'sandbox.js'));
55
+ // Under a managed install the forwarding files call its launcher, which keeps working after an update.
56
+ const installRoot = InstallLayout.detect(this.ctx.packageRoot)?.root ?? null;
57
+ return reconcileAgentCommands(config, this.ctx.storageRoot, forwardTarget(this.ctx.packageRoot, installRoot, process.execPath));
52
58
  }
53
59
  }
@@ -1,7 +1,7 @@
1
1
  import type { Config } from '../../core/types.js';
2
2
  import type { VpnProvider } from '../../core/schema/index.js';
3
3
  import type { SecretStore } from '../../host/secrets/index.js';
4
- import { type LoginState, type VpnCredentials, type VpnLine } from '../../host/vpn/index.js';
4
+ import { type VpnFailureInfo, type LoginState, type VpnCredentials, type VpnLine } from '../../host/vpn/index.js';
5
5
  import type { Context } from '../commands/context.js';
6
6
  export interface VpnReport {
7
7
  readonly enabled: boolean;
@@ -13,6 +13,8 @@ export interface VpnReport {
13
13
  readonly loginSavedAt: string | null;
14
14
  /** The running sandbox's tunnel, when one runs with the VPN on. */
15
15
  readonly running: VpnLine | null;
16
+ /** The last sandbox's VPN attempt, when it failed (kept after it closed). */
17
+ readonly lastFailure: VpnFailureInfo | null;
16
18
  }
17
19
  export declare class VpnService {
18
20
  private readonly ctx;
@@ -26,5 +28,7 @@ export declare class VpnService {
26
28
  }): Promise<VpnReport>;
27
29
  loginState(): LoginState;
28
30
  save(credentials: VpnCredentials): void;
31
+ /** A hint when the login does not look like it belongs to the provider (null when it looks right). */
32
+ warning(credentials: VpnCredentials): string | null;
29
33
  remove(): boolean;
30
34
  }
@@ -1,5 +1,5 @@
1
1
  import { isSandboxRunning } from '../../host/sandbox/index.js';
2
- import { hostPublicIp, readVpnLine, VpnLogin, } from '../../host/vpn/index.js';
2
+ import { hostPublicIp, loginWarning, readLastFailure, readVpnLine, VpnLogin, } from '../../host/vpn/index.js';
3
3
  export class VpnService {
4
4
  ctx;
5
5
  login;
@@ -25,6 +25,7 @@ export class VpnService {
25
25
  killSwitch: c.killSwitch,
26
26
  login: this.login.state(c.provider),
27
27
  loginSavedAt: this.login.savedAt(c.provider),
28
+ lastFailure: readLastFailure(this.ctx.storageRoot),
28
29
  running: running ? readVpnLine(this.ctx.dataPath, await hostPublicIp()) : null,
29
30
  };
30
31
  }
@@ -34,6 +35,10 @@ export class VpnService {
34
35
  save(credentials) {
35
36
  this.login.save(this.provider, credentials);
36
37
  }
38
+ /** A hint when the login does not look like it belongs to the provider (null when it looks right). */
39
+ warning(credentials) {
40
+ return loginWarning(credentials, this.provider);
41
+ }
37
42
  remove() {
38
43
  return this.login.remove(this.provider);
39
44
  }
@@ -100,7 +100,8 @@ export class TextScreen {
100
100
  }
101
101
  if (key.text) {
102
102
  const accept = this.o.accept;
103
- const add = [...key.text.replace(/[\r\n]/g, '')].filter((c) => !accept || accept.test(c));
103
+ // Line breaks and the bracketed-paste markers a terminal wraps around pasted text are not part of the text.
104
+ const add = [...key.text.replace(/\x1b?\[20[01]~/g, '').replace(/[\r\n]/g, '')].filter((c) => !accept || accept.test(c));
104
105
  if (!add.length)
105
106
  return;
106
107
  set([...chars.slice(0, this.cursor), ...add, ...chars.slice(this.cursor)], this.cursor + add.length);
@@ -119,9 +119,11 @@ export class Terminal {
119
119
  });
120
120
  }
121
121
  push(str, key) {
122
+ // The markers around a paste (readline names them paste-start / paste-end) carry no text.
123
+ const marker = key?.name === 'paste-start' || key?.name === 'paste-end';
122
124
  const event = {
123
125
  name: key?.name ?? '',
124
- text: str && !key?.ctrl && !key?.meta && str.length >= 1 && str >= ' ' && str !== '\x7f' ? str : '',
126
+ text: str && !marker && !key?.ctrl && !key?.meta && str.length >= 1 && str >= ' ' && str !== '\x7f' ? str : '',
125
127
  ctrl: Boolean(key?.ctrl),
126
128
  meta: Boolean(key?.meta),
127
129
  shift: Boolean(key?.shift),