windows-development-sandbox 1.7.0 → 1.10.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 (432) hide show
  1. package/config.example.json +12 -1
  2. package/dist/app/context.d.ts +8 -0
  3. package/dist/app/context.js +12 -0
  4. package/dist/app/features/defs.d.ts +322 -0
  5. package/dist/app/features/defs.js +28 -0
  6. package/dist/app/features/sandbox/index.d.ts +2 -0
  7. package/dist/app/features/sandbox/index.js +3 -0
  8. package/dist/app/features/sandbox/sandbox.def.d.ts +193 -0
  9. package/dist/app/features/sandbox/sandbox.def.js +245 -0
  10. package/dist/app/features/sandbox/sandbox.run.d.ts +72 -0
  11. package/dist/app/features/sandbox/sandbox.run.js +98 -0
  12. package/dist/app/features/sessions/index.d.ts +2 -0
  13. package/dist/app/features/sessions/index.js +3 -0
  14. package/dist/app/features/sessions/sessions.def.d.ts +141 -0
  15. package/dist/app/features/sessions/sessions.def.js +176 -0
  16. package/dist/app/features/sessions/sessions.run.d.ts +39 -0
  17. package/dist/app/features/sessions/sessions.run.js +58 -0
  18. package/dist/{cli/menu/settings/categories.d.ts → app/features/settings/categories.def.d.ts} +2 -0
  19. package/dist/{cli/menu/settings/categories.js → app/features/settings/categories.def.js} +24 -5
  20. package/dist/app/features/settings/index.d.ts +5 -0
  21. package/dist/app/features/settings/index.js +6 -0
  22. package/dist/{cli/menu/settings/config-file.d.ts → app/features/settings/settings-file.d.ts} +1 -1
  23. package/dist/{cli/menu/settings/config-file.js → app/features/settings/settings-file.js} +1 -1
  24. package/dist/app/features/settings/settings-rows.d.ts +35 -0
  25. package/dist/app/features/settings/settings-rows.js +94 -0
  26. package/dist/app/features/settings/settings.def.d.ts +157 -0
  27. package/dist/app/features/settings/settings.def.js +179 -0
  28. package/dist/app/features/settings/settings.run.d.ts +67 -0
  29. package/dist/app/features/settings/settings.run.js +84 -0
  30. package/dist/app/kernel/define.d.ts +143 -0
  31. package/dist/app/kernel/define.js +42 -0
  32. package/dist/app/kernel/errors.d.ts +34 -0
  33. package/dist/app/kernel/errors.js +45 -0
  34. package/dist/app/kernel/fields.d.ts +70 -0
  35. package/dist/app/kernel/fields.js +243 -0
  36. package/dist/app/kernel/index.d.ts +8 -0
  37. package/dist/app/kernel/index.js +10 -0
  38. package/dist/app/kernel/io.d.ts +193 -0
  39. package/dist/app/kernel/io.js +126 -0
  40. package/dist/app/kernel/rpc.d.ts +156 -0
  41. package/dist/app/kernel/rpc.js +47 -0
  42. package/dist/app/kernel/text.d.ts +6 -0
  43. package/dist/app/kernel/text.js +57 -0
  44. package/dist/app/kernel/view.d.ts +146 -0
  45. package/dist/app/kernel/view.js +28 -0
  46. package/dist/app/registry.d.ts +10 -0
  47. package/dist/app/registry.js +50 -0
  48. package/dist/cli/args.d.ts +5 -0
  49. package/dist/cli/args.js +6 -0
  50. package/dist/cli/commands/engine.d.ts +2 -0
  51. package/dist/cli/commands/engine.js +60 -0
  52. package/dist/cli/commands/index.d.ts +1 -1
  53. package/dist/cli/commands/index.js +1 -1
  54. package/dist/cli/commands/launch/launch.js +49 -7
  55. package/dist/cli/commands/launch/prepare.js +3 -3
  56. package/dist/cli/commands/launch/running.js +2 -1
  57. package/dist/cli/commands/launch/summary.d.ts +2 -0
  58. package/dist/cli/commands/maintenance/config-share.d.ts +12 -0
  59. package/dist/cli/commands/maintenance/config-share.js +80 -0
  60. package/dist/cli/commands/maintenance/config.js +8 -82
  61. package/dist/cli/commands/maintenance/logs.js +14 -0
  62. package/dist/cli/commands/registry.js +3 -7
  63. package/dist/cli/commands/repair/checks/contained.d.ts +2 -0
  64. package/dist/cli/commands/repair/checks/contained.js +139 -0
  65. package/dist/cli/commands/repair/checks/hcs.d.ts +7 -0
  66. package/dist/cli/commands/repair/checks/hcs.js +28 -0
  67. package/dist/cli/commands/repair/checks/index.js +4 -0
  68. package/dist/cli/commands/repair/checks/pc.js +32 -1
  69. package/dist/cli/commands/repair/checks/sandbox.js +16 -1
  70. package/dist/cli/commands/repair/checks/vpn.js +34 -9
  71. package/dist/cli/commands/repair/env.js +2 -1
  72. package/dist/cli/commands/sessions/index.d.ts +0 -4
  73. package/dist/cli/commands/sessions/index.js +3 -7
  74. package/dist/cli/commands/shared-use.d.ts +5 -2
  75. package/dist/cli/commands/shared-use.js +11 -8
  76. package/dist/cli/commands/storage/contained.d.ts +3 -0
  77. package/dist/cli/commands/storage/contained.js +179 -0
  78. package/dist/cli/commands/storage/offer.js +7 -1
  79. package/dist/cli/commands/storage/storage.js +10 -2
  80. package/dist/cli/commands/user-config.js +1 -1
  81. package/dist/cli/commands/vpn.js +20 -3
  82. package/dist/cli/commands/web.d.ts +5 -0
  83. package/dist/cli/commands/web.js +40 -0
  84. package/dist/cli/index.js +2 -1
  85. package/dist/cli/manifest.d.ts +72 -104
  86. package/dist/cli/manifest.js +64 -39
  87. package/dist/cli/menu/add-agent.d.ts +1 -1
  88. package/dist/cli/menu/add-agent.js +1 -1
  89. package/dist/cli/menu/apps.d.ts +7 -1
  90. package/dist/cli/menu/apps.js +36 -0
  91. package/dist/cli/menu/command-fields.d.ts +1 -1
  92. package/dist/cli/menu/command-form.js +2 -2
  93. package/dist/cli/menu/io-menu.d.ts +29 -0
  94. package/dist/cli/menu/io-menu.js +126 -0
  95. package/dist/cli/menu/live.d.ts +27 -0
  96. package/dist/cli/menu/live.js +69 -0
  97. package/dist/cli/menu/memory.d.ts +19 -0
  98. package/dist/cli/menu/memory.js +53 -0
  99. package/dist/cli/menu/menu.d.ts +10 -1
  100. package/dist/cli/menu/menu.js +27 -9
  101. package/dist/cli/menu/pages/agents-page.d.ts +3 -1
  102. package/dist/cli/menu/pages/agents-page.js +12 -5
  103. package/dist/cli/menu/pages/apps-page.d.ts +3 -1
  104. package/dist/cli/menu/pages/apps-page.js +19 -2
  105. package/dist/cli/menu/pages/list-page.d.ts +27 -0
  106. package/dist/cli/menu/pages/list-page.js +109 -7
  107. package/dist/cli/menu/pages/maintenance-page.d.ts +0 -5
  108. package/dist/cli/menu/pages/maintenance-page.js +17 -19
  109. package/dist/cli/menu/pages/sandbox-page.d.ts +4 -6
  110. package/dist/cli/menu/pages/sandbox-page.js +26 -118
  111. package/dist/cli/menu/pages/scopes.d.ts +1 -1
  112. package/dist/cli/menu/pages/sessions-page.d.ts +5 -11
  113. package/dist/cli/menu/pages/sessions-page.js +19 -88
  114. package/dist/cli/menu/pages/settings-page.d.ts +2 -10
  115. package/dist/cli/menu/pages/settings-page.js +64 -94
  116. package/dist/cli/menu/render-view.d.ts +28 -0
  117. package/dist/cli/menu/render-view.js +112 -0
  118. package/dist/cli/menu/settings/{path-kind.d.ts → ask-path.d.ts} +1 -7
  119. package/dist/cli/menu/settings/ask-path.js +14 -0
  120. package/dist/cli/menu/settings/collections/collection-editor.d.ts +1 -1
  121. package/dist/cli/menu/settings/collections/string-list-editor.d.ts +1 -2
  122. package/dist/cli/menu/settings/collections/string-list-editor.js +1 -1
  123. package/dist/cli/menu/settings/draft.d.ts +1 -1
  124. package/dist/cli/menu/settings/format.d.ts +1 -3
  125. package/dist/cli/menu/settings/format.js +6 -36
  126. package/dist/cli/menu/settings/index.d.ts +1 -3
  127. package/dist/cli/menu/settings/index.js +3 -3
  128. package/dist/cli/menu/settings/object-editor.d.ts +1 -1
  129. package/dist/cli/menu/settings/share.d.ts +36 -0
  130. package/dist/cli/menu/settings/share.js +131 -0
  131. package/dist/cli/menu/settings/value-editor.d.ts +5 -1
  132. package/dist/cli/menu/settings/value-editor.js +57 -54
  133. package/dist/cli/menu/several.js +1 -1
  134. package/dist/cli/menu/share-settings.d.ts +21 -0
  135. package/dist/cli/menu/share-settings.js +112 -0
  136. package/dist/cli/menu/shell.d.ts +21 -5
  137. package/dist/cli/menu/shell.js +31 -6
  138. package/dist/cli/menu/views.d.ts +7 -0
  139. package/dist/cli/menu/views.js +11 -0
  140. package/dist/cli/menu/vpn-login.js +8 -0
  141. package/dist/cli/operations/handler.d.ts +6 -0
  142. package/dist/cli/operations/handler.js +61 -0
  143. package/dist/cli/operations/index.d.ts +4 -0
  144. package/dist/cli/operations/index.js +5 -0
  145. package/dist/cli/operations/input.d.ts +6 -0
  146. package/dist/cli/operations/input.js +37 -0
  147. package/dist/cli/operations/io-cli.d.ts +7 -0
  148. package/dist/cli/operations/io-cli.js +62 -0
  149. package/dist/cli/operations/spec.d.ts +18 -0
  150. package/dist/cli/operations/spec.js +108 -0
  151. package/dist/cli/output/colors.d.ts +2 -2
  152. package/dist/cli/output/colors.js +2 -11
  153. package/dist/cli/output/index.d.ts +1 -0
  154. package/dist/cli/output/index.js +1 -0
  155. package/dist/cli/output/print.d.ts +3 -1
  156. package/dist/cli/output/print.js +7 -3
  157. package/dist/cli/output/progress.d.ts +15 -0
  158. package/dist/cli/output/progress.js +44 -0
  159. package/dist/cli/output/render-view.d.ts +4 -0
  160. package/dist/cli/output/render-view.js +63 -0
  161. package/dist/cli/services/agent-commands-service.d.ts +2 -0
  162. package/dist/cli/services/agent-commands-service.js +11 -2
  163. package/dist/cli/services/logs-service.js +4 -3
  164. package/dist/cli/tui/app.d.ts +6 -1
  165. package/dist/cli/tui/app.js +26 -4
  166. package/dist/cli/tui/list/list-screen.d.ts +14 -0
  167. package/dist/cli/tui/list/list-screen.js +64 -36
  168. package/dist/cli/tui/list/list-types.d.ts +2 -0
  169. package/dist/cli/tui/terminal.d.ts +7 -0
  170. package/dist/cli/tui/terminal.js +30 -10
  171. package/dist/core/config/defaults.js +9 -1
  172. package/dist/core/schema/config.d.ts +14 -0
  173. package/dist/core/schema/config.js +16 -1
  174. package/dist/core/schema/harness.d.ts +1 -0
  175. package/dist/core/schema/harness.js +5 -0
  176. package/dist/core/schema/sandbox.d.ts +6 -0
  177. package/dist/core/schema/sandbox.js +20 -3
  178. package/dist/core/schema/vpn.d.ts +2 -1
  179. package/dist/core/schema/vpn.js +4 -4
  180. package/dist/core/schema/web.d.ts +1 -0
  181. package/dist/core/schema/web.js +4 -1
  182. package/dist/core/types.d.ts +23 -5
  183. package/dist/core/util/fs.d.ts +2 -0
  184. package/dist/core/util/fs.js +9 -0
  185. package/dist/core/util/index.d.ts +1 -1
  186. package/dist/core/util/index.js +1 -1
  187. package/dist/guest/agents/agent-files.d.ts +5 -0
  188. package/dist/guest/agents/agent-files.js +4 -0
  189. package/dist/guest/agents/local-state.d.ts +2 -0
  190. package/dist/guest/agents/local-state.js +21 -1
  191. package/dist/guest/boot/boot.js +5 -2
  192. package/dist/guest/daemon/daemon.js +4 -0
  193. package/dist/guest/daemon/sessions.d.ts +5 -0
  194. package/dist/guest/daemon/sessions.js +17 -3
  195. package/dist/guest/entries/daemon.js +3 -0
  196. package/dist/guest/governor/decide.d.ts +71 -0
  197. package/dist/guest/governor/decide.js +211 -0
  198. package/dist/guest/governor/governor.d.ts +45 -0
  199. package/dist/guest/governor/governor.js +230 -0
  200. package/dist/guest/governor/index.d.ts +2 -0
  201. package/dist/guest/governor/index.js +2 -0
  202. package/dist/guest/network/firewall.d.ts +1 -0
  203. package/dist/guest/network/firewall.js +65 -23
  204. package/dist/guest/runtime/csc.d.ts +1 -0
  205. package/dist/guest/runtime/csc.js +1 -0
  206. package/dist/guest/runtime/process.js +53 -5
  207. package/dist/guest/sessions/viewer.js +3 -1
  208. package/dist/guest/setup/claude-mem.js +12 -4
  209. package/dist/guest/toolchains/languages/node.js +29 -3
  210. package/dist/guest/toolchains/native/rust-extras.js +4 -2
  211. package/dist/guest/vpn/vpn.js +69 -21
  212. package/dist/guest/vpn/wireguard.d.ts +32 -0
  213. package/dist/guest/vpn/wireguard.js +132 -0
  214. package/dist/host/agent-commands/commands.d.ts +4 -0
  215. package/dist/host/agent-commands/commands.js +31 -7
  216. package/dist/host/audit/log.d.ts +1 -1
  217. package/dist/host/channel/attach.d.ts +3 -1
  218. package/dist/host/channel/attach.js +5 -2
  219. package/dist/host/channel/channel.d.ts +2 -0
  220. package/dist/host/channel/channel.js +4 -0
  221. package/dist/host/engines/engine.d.ts +12 -0
  222. package/dist/host/engines/engine.js +1 -0
  223. package/dist/host/engines/hcs/check.d.ts +25 -0
  224. package/dist/host/engines/hcs/check.js +95 -0
  225. package/dist/host/engines/hcs/documents.d.ts +44 -0
  226. package/dist/host/engines/hcs/documents.js +155 -0
  227. package/dist/host/engines/hcs/engine.d.ts +39 -0
  228. package/dist/host/engines/hcs/engine.js +171 -0
  229. package/dist/host/engines/hcs/helper.d.ts +43 -0
  230. package/dist/host/engines/hcs/helper.js +173 -0
  231. package/dist/host/engines/hcs/images.d.ts +37 -0
  232. package/dist/host/engines/hcs/images.js +152 -0
  233. package/dist/host/engines/index.d.ts +7 -0
  234. package/dist/host/engines/index.js +5 -0
  235. package/dist/host/engines/registry.d.ts +16 -0
  236. package/dist/host/engines/registry.js +30 -0
  237. package/dist/host/engines/types.d.ts +58 -0
  238. package/dist/host/engines/types.js +1 -0
  239. package/dist/host/engines/windows-sandbox/engine.d.ts +16 -0
  240. package/dist/host/engines/windows-sandbox/engine.js +63 -0
  241. package/dist/host/engines/windows-sandbox/wsb-text.d.ts +2 -0
  242. package/dist/host/engines/windows-sandbox/wsb-text.js +29 -0
  243. package/dist/host/launch/generate.d.ts +5 -1
  244. package/dist/host/launch/generate.js +50 -8
  245. package/dist/host/launch/index.d.ts +3 -1
  246. package/dist/host/launch/index.js +3 -1
  247. package/dist/host/launch/launch-plan.d.ts +13 -0
  248. package/dist/host/launch/launch-plan.js +26 -0
  249. package/dist/host/launch/mappings.js +2 -1
  250. package/dist/host/launch/risk-notes.d.ts +10 -0
  251. package/dist/host/launch/risk-notes.js +18 -0
  252. package/dist/host/launch/session-file.d.ts +2 -0
  253. package/dist/host/launch/session-file.js +7 -1
  254. package/dist/host/launch/wsb-file.d.ts +28 -4
  255. package/dist/host/launch/wsb-file.js +61 -42
  256. package/dist/host/life/crash-report.d.ts +27 -1
  257. package/dist/host/life/crash-report.js +173 -7
  258. package/dist/host/life/host-memory.d.ts +41 -0
  259. package/dist/host/life/host-memory.js +140 -0
  260. package/dist/host/life/index.d.ts +2 -0
  261. package/dist/host/life/index.js +2 -0
  262. package/dist/host/life/stop-events.d.ts +2 -0
  263. package/dist/host/life/stop-events.js +20 -3
  264. package/dist/host/life/stop-warning.js +1 -1
  265. package/dist/host/life/watchdog.d.ts +56 -0
  266. package/dist/host/life/watchdog.js +124 -0
  267. package/dist/host/native.d.ts +3 -2
  268. package/dist/host/native.js +22 -3
  269. package/dist/host/profile/claude-login.js +3 -2
  270. package/dist/host/sandbox/contained-deps.d.ts +2 -0
  271. package/dist/host/sandbox/contained-deps.js +17 -0
  272. package/dist/host/sandbox/index.d.ts +2 -0
  273. package/dist/host/sandbox/index.js +2 -0
  274. package/dist/host/sandbox/platform.d.ts +2 -0
  275. package/dist/host/sandbox/platform.js +7 -7
  276. package/dist/host/sandbox/tasklist.d.ts +13 -0
  277. package/dist/host/sandbox/tasklist.js +47 -0
  278. package/dist/host/secrets/launch-secrets.js +2 -1
  279. package/dist/host/sharing/current.js +2 -6
  280. package/dist/host/storage/contained/check.d.ts +9 -0
  281. package/dist/host/storage/contained/check.js +37 -0
  282. package/dist/host/storage/contained/contained-storage.d.ts +66 -0
  283. package/dist/host/storage/contained/contained-storage.js +168 -0
  284. package/dist/host/storage/contained/disk-port.d.ts +43 -0
  285. package/dist/host/storage/contained/disk-port.js +122 -0
  286. package/dist/host/storage/contained/index.d.ts +9 -0
  287. package/dist/host/storage/contained/index.js +8 -0
  288. package/dist/host/storage/contained/layout.d.ts +28 -0
  289. package/dist/host/storage/contained/layout.js +45 -0
  290. package/dist/host/storage/contained/resolve.d.ts +4 -0
  291. package/dist/host/storage/contained/resolve.js +30 -0
  292. package/dist/host/storage/contained/setting.d.ts +1 -0
  293. package/dist/host/storage/contained/setting.js +5 -0
  294. package/dist/host/storage/contained/state.d.ts +89 -0
  295. package/dist/host/storage/contained/state.js +166 -0
  296. package/dist/host/storage/contained/transfer.d.ts +24 -0
  297. package/dist/host/storage/contained/transfer.js +121 -0
  298. package/dist/host/storage/index.d.ts +2 -0
  299. package/dist/host/storage/index.js +2 -0
  300. package/dist/host/storage/providers/contained.d.ts +15 -0
  301. package/dist/host/storage/providers/contained.js +39 -0
  302. package/dist/host/storage/providers/folder.d.ts +12 -0
  303. package/dist/host/storage/providers/folder.js +20 -0
  304. package/dist/host/storage/providers/index.d.ts +4 -0
  305. package/dist/host/storage/providers/index.js +3 -0
  306. package/dist/host/storage/providers/registry.d.ts +9 -0
  307. package/dist/host/storage/providers/registry.js +14 -0
  308. package/dist/host/storage/providers/types.d.ts +33 -0
  309. package/dist/host/storage/providers/types.js +1 -0
  310. package/dist/host/tools/conpty.js +3 -2
  311. package/dist/host/tools/windows-terminal.js +8 -7
  312. package/dist/host/vpn/index.d.ts +1 -1
  313. package/dist/host/vpn/index.js +1 -1
  314. package/dist/host/vpn/prepare.d.ts +2 -0
  315. package/dist/host/vpn/prepare.js +63 -2
  316. package/dist/host/web/browser.d.ts +2 -0
  317. package/dist/host/web/browser.js +14 -1
  318. package/dist/host/web/index.d.ts +3 -2
  319. package/dist/host/web/index.js +3 -2
  320. package/dist/host/web/rpc.d.ts +24 -0
  321. package/dist/host/web/rpc.js +149 -0
  322. package/dist/host/web/runner.d.ts +2 -0
  323. package/dist/host/web/runner.js +1 -2
  324. package/dist/host/web/server.d.ts +7 -0
  325. package/dist/host/web/server.js +23 -3
  326. package/dist/runtime/agent.js +83 -18
  327. package/dist/runtime/daemon.js +1805 -1221
  328. package/dist/runtime/db.js +83 -18
  329. package/dist/runtime/main.js +779 -219
  330. package/dist/runtime/netproxy.js +73 -11
  331. package/dist/runtime/view.js +91 -16
  332. package/dist/selftest/agent-paths.test.js +11 -1
  333. package/dist/selftest/app.test.d.ts +2 -0
  334. package/dist/selftest/app.test.js +324 -0
  335. package/dist/selftest/channel.test.js +13 -0
  336. package/dist/selftest/cli.test.js +2 -1
  337. package/dist/selftest/config.test.js +59 -0
  338. package/dist/selftest/contained.test.d.ts +2 -0
  339. package/dist/selftest/contained.test.js +544 -0
  340. package/dist/selftest/credentials.test.js +1 -1
  341. package/dist/selftest/engine.test.d.ts +2 -0
  342. package/dist/selftest/engine.test.js +127 -0
  343. package/dist/selftest/guest-boot.test.js +47 -1
  344. package/dist/selftest/hcs.test.d.ts +2 -0
  345. package/dist/selftest/hcs.test.js +482 -0
  346. package/dist/selftest/heavy-load.test.d.ts +2 -0
  347. package/dist/selftest/heavy-load.test.js +355 -0
  348. package/dist/selftest/index.js +12 -0
  349. package/dist/selftest/layout.test.js +12 -0
  350. package/dist/selftest/menu-perf.test.d.ts +2 -0
  351. package/dist/selftest/menu-perf.test.js +248 -0
  352. package/dist/selftest/menu-screens.js +4 -2
  353. package/dist/selftest/menu.test.js +36 -8
  354. package/dist/selftest/network.test.js +20 -1
  355. package/dist/selftest/support/engine-cases.d.ts +8 -0
  356. package/dist/selftest/support/engine-cases.js +65 -0
  357. package/dist/selftest/support/engine-golden.d.ts +3 -0
  358. package/dist/selftest/support/engine-golden.js +66 -0
  359. package/dist/selftest/support/fixtures.d.ts +8 -1
  360. package/dist/selftest/support/hcs-probes.d.ts +17 -0
  361. package/dist/selftest/support/hcs-probes.js +41 -0
  362. package/dist/selftest/vpn.test.js +136 -3
  363. package/dist/selftest/web.test.js +101 -3
  364. package/dist/shared/channel/run-dir.d.ts +4 -1
  365. package/dist/shared/channel/run-dir.js +7 -1
  366. package/dist/shared/channel/schema.d.ts +35 -1
  367. package/dist/shared/channel/schema.js +27 -0
  368. package/dist/shared/channel/secrets.d.ts +1 -0
  369. package/dist/shared/channel/secrets.js +6 -0
  370. package/dist/shared/io/copy-tree.d.ts +7 -0
  371. package/dist/shared/io/copy-tree.js +45 -0
  372. package/dist/shared/io/index.d.ts +2 -0
  373. package/dist/shared/io/index.js +2 -0
  374. package/dist/shared/io/robocopy-result.d.ts +6 -0
  375. package/dist/shared/io/robocopy-result.js +23 -0
  376. package/dist/shared/memory/index.d.ts +1 -0
  377. package/dist/shared/memory/index.js +1 -0
  378. package/dist/shared/memory/policy.d.ts +38 -0
  379. package/dist/shared/memory/policy.js +63 -0
  380. package/dist/shared/network/firewall-rules.d.ts +8 -0
  381. package/dist/shared/network/firewall-rules.js +28 -0
  382. package/dist/shared/pty/output-filter.d.ts +14 -1
  383. package/dist/shared/pty/output-filter.js +22 -4
  384. package/dist/shared/pty/terminal.d.ts +4 -1
  385. package/dist/shared/pty/terminal.js +4 -2
  386. package/dist/shared/system.d.ts +1 -0
  387. package/dist/shared/system.js +1 -0
  388. package/dist/shared/vpn/download.d.ts +16 -0
  389. package/dist/shared/vpn/download.js +16 -0
  390. package/dist/shared/vpn/wireguard.d.ts +51 -0
  391. package/dist/shared/vpn/wireguard.js +227 -0
  392. package/dist/sync/sync-cycle.js +5 -5
  393. package/dist/sync/watcher.d.ts +13 -0
  394. package/dist/sync/watcher.js +92 -3
  395. package/dist/web/index.html +4 -3
  396. package/dist/web/main-NRRS5O6Z.js +157 -0
  397. package/dist/web/main-T5DMCHYV.css +1 -0
  398. package/native/csharp/HcsHost/AssemblyInfo.cs +14 -0
  399. package/native/csharp/HcsHost/Guard.cs +100 -0
  400. package/native/csharp/HcsHost/Hcs.cs +256 -0
  401. package/native/csharp/HcsHost/Json.cs +78 -0
  402. package/native/csharp/HcsHost/Program.cs +185 -0
  403. package/native/csharp/HcsHost/VirtualDisk.cs +95 -0
  404. package/native/csharp/HostTools/AssemblyInfo.cs +14 -0
  405. package/native/csharp/HostTools/MemoryStatus.cs +128 -0
  406. package/native/csharp/HostTools/Program.cs +58 -17
  407. package/native/scripts/check-contained.ps1 +28 -0
  408. package/native/scripts/contained-common.ps1 +241 -0
  409. package/native/scripts/dismount-contained.ps1 +66 -0
  410. package/native/scripts/memory-status.ps1 +42 -0
  411. package/native/scripts/mount-contained.ps1 +139 -0
  412. package/native/scripts/run-elevated.ps1 +17 -4
  413. package/native/scripts/stop-events.ps1 +23 -8
  414. package/package.json +9 -2
  415. package/sandbox/csharp/MemGov.cs +466 -0
  416. package/sandbox/csharp/PtyHost.cs +111 -5
  417. package/sandbox/scripts/new-shortcuts.ps1 +3 -1
  418. package/schemas/config.schema.json +66 -8
  419. package/schemas/sandbox-config.schema.json +3 -3
  420. package/dist/cli/commands/sessions/control.d.ts +0 -4
  421. package/dist/cli/commands/sessions/control.js +0 -66
  422. package/dist/cli/commands/sessions/list.d.ts +0 -2
  423. package/dist/cli/commands/sessions/list.js +0 -48
  424. package/dist/cli/commands/sessions/status.d.ts +0 -4
  425. package/dist/cli/commands/sessions/status.js +0 -114
  426. package/dist/cli/commands/sessions/window.d.ts +0 -3
  427. package/dist/cli/commands/sessions/window.js +0 -18
  428. package/dist/cli/menu/settings/path-kind.js +0 -44
  429. package/dist/web/main-CXESL347.js +0 -45
  430. package/dist/web/main-VLR7DVQ4.css +0 -1
  431. /package/dist/{cli/menu/settings → app/kernel}/schema-node.d.ts +0 -0
  432. /package/dist/{cli/menu/settings → app/kernel}/schema-node.js +0 -0
@@ -4,6 +4,7 @@ import path from 'node:path';
4
4
  import { spawnSync } from 'node:child_process';
5
5
  import { moveIntoPlace, spawnAsync, stagingFolder } from '../../shared/download/index.js';
6
6
  import { safeRemove } from '../../shared/io/safe-remove.js';
7
+ import { robocopyErrors, robocopyFailed, robocopyMeaning } from '../../shared/io/robocopy-result.js';
7
8
  import { SYSTEM } from '../../shared/system.js';
8
9
  import { scriptArgs } from '../../shared/scripts.js';
9
10
  import { isWin } from './paths.js';
@@ -27,7 +28,18 @@ export function execOutput(file, args, options = {}) {
27
28
  }
28
29
  /** Runs sandbox\scripts\<name> (a .ps1 in the launch folder) with `args`; throws when it fails. */
29
30
  export function runScript(name, args = []) {
30
- exec(SYSTEM.powershell(), scriptArgs(name, args));
31
+ const result = spawnSync(SYSTEM.powershell(), scriptArgs(name, args), {
32
+ stdio: ['inherit', 'inherit', 'pipe'],
33
+ encoding: 'utf8',
34
+ windowsHide: true,
35
+ });
36
+ if (result.error)
37
+ throw result.error;
38
+ const errors = String(result.stderr ?? '').trim();
39
+ if (errors)
40
+ process.stderr.write(errors + '\n');
41
+ if (result.status !== 0)
42
+ throw new Error(name + ' failed with exit code ' + result.status + (errors ? ': ' + errors.slice(0, 400) : ''));
31
43
  }
32
44
  /** runScript for what it prints ('' when it fails). */
33
45
  export function scriptOutput(name, args = [], options = {}) {
@@ -59,10 +71,46 @@ export function robocopy(from, to, files = []) {
59
71
  fs.copyFileSync(path.join(from, f), path.join(to, f));
60
72
  return;
61
73
  }
62
- const what = files.length ? files : ['/E', '/MT:16'];
63
- const result = spawnSync(SYSTEM.robocopy(), [from, to, ...what, '/NFL', '/NDL', '/NJH', '/NJS', '/NP', '/R:1', '/W:1', '/XJ'], { stdio: 'ignore', windowsHide: true });
64
- if ((result.status ?? 16) >= 8)
65
- throw new Error('robocopy ' + from + ' -> ' + to + ' failed (exit ' + result.status + ')');
74
+ const what = files.length ? files : ['/E'];
75
+ // A second pass for what the first could not copy (a file another program holds open for a moment, a busy mapped
76
+ // folder): one thread and more patience; robocopy skips what is already in place.
77
+ const passes = [
78
+ ['/MT:16', '/R:1', '/W:1'],
79
+ ['/MT:1', '/R:4', '/W:3'],
80
+ ];
81
+ let code = null;
82
+ let output = '';
83
+ for (const pass of passes) {
84
+ const result = spawnSync(SYSTEM.robocopy(), [
85
+ from,
86
+ to,
87
+ ...what,
88
+ ...(files.length ? [] : [pass[0]]),
89
+ pass[1],
90
+ pass[2],
91
+ '/NFL',
92
+ '/NDL',
93
+ '/NJH',
94
+ '/NJS',
95
+ '/NP',
96
+ '/XJ',
97
+ ], { encoding: 'utf8', windowsHide: true });
98
+ code = result.status;
99
+ output = String(result.stdout ?? '') + String(result.stderr ?? '');
100
+ if (!robocopyFailed(code))
101
+ return;
102
+ }
103
+ const why = robocopyErrors(output);
104
+ throw new Error('robocopy ' +
105
+ from +
106
+ ' -> ' +
107
+ to +
108
+ ' failed (exit ' +
109
+ code +
110
+ ': ' +
111
+ robocopyMeaning(code) +
112
+ ')' +
113
+ (why.length ? ': ' + why.join(' | ') : ''));
66
114
  }
67
115
  /**
68
116
  * A Node install without its global packages (nvm keeps those per version: often tens of thousands of small files,
@@ -30,7 +30,9 @@ export async function viewSession(id) {
30
30
  return 1;
31
31
  }
32
32
  const socket = await connect({ ...session.attach, addresses: ['127.0.0.1'] });
33
- const result = await runTerminalClient(socket, session.attach.token);
33
+ // The sandbox's own terminal: a copy in the agent reaches the sandbox's clipboard (and yours only through
34
+ // sandbox.clipboard, which already shares it).
35
+ const result = await runTerminalClient(socket, session.attach.token, { clipboardWrites: true });
34
36
  if (result.reason === 'exit')
35
37
  console.log(session.harness + ' exited with code ' + result.exitCode + '.');
36
38
  else if (result.reason === 'error')
@@ -6,6 +6,7 @@ import { dryRun } from '../runtime/paths.js';
6
6
  import { exists, mkdirp } from '../runtime/fs.js';
7
7
  import { log, warn } from '../runtime/log.js';
8
8
  import { robocopy } from '../runtime/process.js';
9
+ import { copyTreeTolerant } from '../../shared/io/copy-tree.js';
9
10
  /**
10
11
  * claude-mem keeps SQLite + a vector store. They run from the sandbox's own disk (SQLite and mapped folders do not
11
12
  * mix) and are mirrored to persistent storage, from where the host syncs them back to your PC.
@@ -49,8 +50,13 @@ const databaseFingerprint = (dir) => {
49
50
  if (entry.isDirectory())
50
51
  walk(full);
51
52
  else if (entry.isFile() && /\.(db|sqlite3?)(-wal)?$/i.test(entry.name)) {
52
- const s = fs.statSync(full);
53
- parts.push(full + ':' + s.size + ':' + s.mtimeMs);
53
+ try {
54
+ const s = fs.statSync(full);
55
+ parts.push(full + ':' + s.size + ':' + s.mtimeMs);
56
+ }
57
+ catch {
58
+ // Gone since the listing (a database being replaced): left out, the next save looks again.
59
+ }
54
60
  }
55
61
  }
56
62
  };
@@ -116,8 +122,10 @@ export function saveClaudeMem() {
116
122
  const incoming = store + '.incoming';
117
123
  const previous = store + '.previous';
118
124
  fs.rmSync(incoming, { recursive: true, force: true });
119
- // The database, its -wal and -shm files are copied together, so the copy is consistent with itself.
120
- fs.cpSync(local, incoming, { recursive: true, filter: (src) => !/\.(pid|lock)$/i.test(src) });
125
+ // The database and its -wal are copied together, so the copy is consistent with itself. A -journal is never copied:
126
+ // beside a database taken at another moment it would be rolled back over it. The -shm is rebuilt by SQLite. Files the
127
+ // agent removes while this runs (hook spool files) are skipped.
128
+ copyTreeTolerant(local, incoming, (name) => /\.(pid|lock)$|-journal$|-shm$/i.test(name));
121
129
  // Swap without a window where no copy exists: old aside, new in, then keep the old one as a dated backup.
122
130
  fs.rmSync(previous, { recursive: true, force: true });
123
131
  if (exists(store))
@@ -14,6 +14,7 @@ import { arch, tool } from '../tool-env.js';
14
14
  import { installOnce } from '../package-ledger.js';
15
15
  import { resolveCached } from '../version-cache.js';
16
16
  import { duringSession } from '../../runtime/project-context.js';
17
+ import { session } from '../../runtime/session.js';
17
18
  import { pickNodeVersion, pickSpreadVersions } from '../../../core/detect/versions.js';
18
19
  const NVM_HOME = tool('nvm');
19
20
  const NVM_SYMLINK = path.join(localDir, 'nodejs');
@@ -73,7 +74,14 @@ const ensureNvm = async () => {
73
74
  log('Downloading nvm-windows (' + asset.url.split('/').slice(-2, -1)[0] + ') ...');
74
75
  await downloader.archive(asset.url, NVM_HOME, { sha256: asset.sha256, locate: 'nvm.exe', fileName: asset.name });
75
76
  };
76
- /** A Node version: from the host's nvm when it has it (fast, offline), else `nvm install`. */
77
+ /** This PC's own Node folder (mapped read-only), when it is exactly this version. */
78
+ const hostNodeFolder = (version) => {
79
+ const dir = path.join(hostToolsDir, 'node');
80
+ return session.hostTools.nodeVersion?.replace(/^v/, '') === version && exists(path.join(dir, 'node.exe'))
81
+ ? dir
82
+ : null;
83
+ };
84
+ /** A Node version: from the host's nvm or Node when it has it (fast, offline), else `nvm install`. */
77
85
  const installNodeVersion = async (version) => {
78
86
  const dir = path.join(NVM_HOME, 'v' + version);
79
87
  const fromHost = path.join(HOST_NVM, 'v' + version);
@@ -84,6 +92,12 @@ const installNodeVersion = async (version) => {
84
92
  copyNode(fromHost, dir);
85
93
  return;
86
94
  }
95
+ const hostNode = hostNodeFolder(version);
96
+ if (hostNode) {
97
+ log('Copying Node ' + version + ' from this PC (without global packages) ...');
98
+ copyNode(hostNode, dir);
99
+ return;
100
+ }
87
101
  log('Downloading Node ' + version + ' (~30 MB once) ...');
88
102
  mkdirp(NVM_TEMP);
89
103
  let out;
@@ -93,8 +107,20 @@ const installNodeVersion = async (version) => {
93
107
  finally {
94
108
  safeRemove(NVM_TEMP, NVM_HOME);
95
109
  }
96
- if (!exists(path.join(dir, 'node.exe')))
97
- throw new Error('nvm install ' + version + ' failed: ' + out.trim().split('\n').slice(-2).join(' '));
110
+ if (!exists(path.join(dir, 'node.exe'))) {
111
+ const tail = out.trim().split(/\r?\n/).filter(Boolean).slice(-3).join(' ');
112
+ const why = /complete/i.test(out)
113
+ ? 'nvm said it finished, but its move into the data folder did not happen'
114
+ : 'nvm could not download or unpack it: check the internet connection and the free disk space';
115
+ throw new Error('nvm install ' +
116
+ version +
117
+ ' did not leave ' +
118
+ path.join(dir, 'node.exe') +
119
+ ' (' +
120
+ why +
121
+ ')' +
122
+ (tail ? '. nvm printed: ' + tail : ''));
123
+ }
98
124
  };
99
125
  const activate = async (version) => {
100
126
  await nvm(['use', version]);
@@ -7,6 +7,7 @@ import { execOutput } from '../../runtime/process.js';
7
7
  import { ensureCompiled } from '../../runtime/csc.js';
8
8
  import { arch, tool } from '../tool-env.js';
9
9
  import { wanted } from '../detect.js';
10
+ import { cargoJobs } from '../../../shared/memory/index.js';
10
11
  /**
11
12
  * Where cargo builds: shared with the PC's target\, one on the sandbox's disk, or a path. "separate" sets no variable:
12
13
  * the cargo shim (sandbox/csharp/CargoShim.cs) gives every checkout its own target\sandbox, so git worktrees of several
@@ -66,8 +67,9 @@ export function cargoDefaults(host) {
66
67
  const file = tool('rust', 'cargo', 'config.toml');
67
68
  if (exists(file) || !exists(path.dirname(file)))
68
69
  return;
69
- // Several agents often build at once: up to 4 jobs each (8 each crashed Windows inside the sandbox on 13 Oct 2026).
70
- const jobs = Math.max(2, Math.min(4, Math.floor(os.cpus().length / 4)));
70
+ // Several agents often build at once: up to 4 jobs each (8 each crashed Windows inside the sandbox on 13 Oct 2026),
71
+ // and about 4 GB of memory per job. The daemon lowers it further per session (CARGO_BUILD_JOBS) when several build.
72
+ const jobs = cargoJobs(os.cpus().length, os.totalmem() / 1024 ** 3, 1);
71
73
  const gnu = host.endsWith('-gnu');
72
74
  fs.writeFileSync(file, [
73
75
  '# Written by windows-development-sandbox once; edit freely, it is never overwritten.',
@@ -9,6 +9,7 @@ import path from 'node:path';
9
9
  import { now } from '../../shared/channel/run-dir.js';
10
10
  import { writeJsonAtomic } from '../../shared/io/json.js';
11
11
  import { assertSafeOvpn } from '../../shared/vpn/ovpn.js';
12
+ import { assertSafeWireguard, renderWireguard, routesEverything, wireguardEndpoints, wireguardTunnelAddress, } from '../../shared/vpn/wireguard.js';
12
13
  import { isIPv4 } from '../../shared/network/firewall-rules.js';
13
14
  import { killSwitchCommands, tunnelCommands } from '../../shared/vpn/kill-switch.js';
14
15
  import { errMsg } from '../../core/util/errors.js';
@@ -18,6 +19,7 @@ import { log, warn } from '../runtime/log.js';
18
19
  import { session } from '../runtime/session.js';
19
20
  import { ensureOpenVpn } from './openvpn.js';
20
21
  import { startTunnel } from './tunnel.js';
22
+ import { ensureWireguard, removeWireguardTunnel, startWireguardTunnel, waitForHandshake } from './wireguard.js';
21
23
  /** Writes run\vpn.json (what `sandbox vpn status` and the launch show). Never throws. */
22
24
  export function setVpnStatus(state, message, extra = {}) {
23
25
  try {
@@ -46,6 +48,21 @@ async function serverAddresses(remotes) {
46
48
  }
47
49
  return out;
48
50
  }
51
+ /** The WireGuard peers' endpoints as IPv4 servers (udp), and the config with those addresses in place of host names. */
52
+ async function wireguardServers(config) {
53
+ const endpointIps = {};
54
+ const servers = [];
55
+ const found = wireguardEndpoints(config);
56
+ for (let i = 0; i < found.length; i++) {
57
+ const [ip] = await serverAddresses([{ host: found[i].host, port: found[i].port, proto: 'udp' }]).then((all) => all.map((s) => s.ip));
58
+ if (!ip)
59
+ throw new Error('the VPN server address could not be found');
60
+ // WireGuard looks the name up itself at start, after the kill switch has closed DNS: give it the address.
61
+ endpointIps[i] = ip;
62
+ servers.push({ ip, port: found[i].port, proto: 'udp' });
63
+ }
64
+ return { servers, text: renderWireguard(config, endpointIps) };
65
+ }
49
66
  /** What the sandbox looks like from outside, through the tunnel (null when it cannot be told quickly). */
50
67
  async function publicAddress() {
51
68
  try {
@@ -74,31 +91,62 @@ export async function setupVpn(secrets, ptyHostExe) {
74
91
  try {
75
92
  if (!session.networking)
76
93
  throw new Error('networking is off for this sandbox');
77
- const credentials = secrets.vpn;
78
- if (!credentials)
79
- throw new Error('the VPN login did not arrive (start the sandbox again)');
80
- // The profile came from the host, but it is data like any other: checked again here before OpenVPN reads it.
81
- const profile = path.join(launchDir, 'vpn', 'server.ovpn');
82
- const remotes = assertSafeOvpn(fs.readFileSync(profile, 'utf8'), vpn.server);
83
- const exe = await ensureOpenVpn();
84
- const servers = await serverAddresses(remotes);
85
- if (servers.length === 0)
86
- throw new Error('the VPN server addresses could not be found');
87
- if (killSwitch)
88
- block(servers);
89
- if (dryRun || !isWin) {
90
- log('(dry run) would start the VPN tunnel to ' + vpn.server);
91
- setVpnStatus('off', 'dry run');
92
- return true;
94
+ let result;
95
+ let wireguardExe = null;
96
+ if (vpn.provider === 'wireguard-file') {
97
+ // The config came from the host (through the one-shot file), but it is data like any other: checked again here.
98
+ if (!secrets.wireguardConfig)
99
+ throw new Error('the WireGuard config did not arrive (start the sandbox again)');
100
+ const config = assertSafeWireguard(secrets.wireguardConfig, vpn.server);
101
+ const exe = await ensureWireguard();
102
+ const { servers, text } = await wireguardServers(config);
103
+ if (killSwitch && !routesEverything(config))
104
+ warn('VPN: AllowedIPs does not include 0.0.0.0/0: with the kill switch on, only the tunnel traffic can leave the sandbox.');
105
+ if (killSwitch)
106
+ block(servers);
107
+ if (dryRun || !isWin) {
108
+ log('(dry run) would start the WireGuard tunnel ' + vpn.server);
109
+ setVpnStatus('off', 'dry run');
110
+ return true;
111
+ }
112
+ log('VPN: connecting with ' + vpn.server);
113
+ wireguardExe = exe;
114
+ result = await startWireguardTunnel({ exe, config: text, tunnelIp: wireguardTunnelAddress(config) });
115
+ if (result.ok && result.tunnelIp && killSwitch)
116
+ netsh(tunnelCommands(result.tunnelIp));
117
+ if (result.ok)
118
+ result = await waitForHandshake(exe, publicAddress);
93
119
  }
94
- log('VPN: connecting to ' + vpn.server);
95
- const result = await startTunnel({ exe, profile, username: credentials.username, password: credentials.password });
96
- if (!result.ok)
120
+ else {
121
+ const credentials = secrets.vpn;
122
+ if (!credentials)
123
+ throw new Error('the VPN login did not arrive (start the sandbox again)');
124
+ // The profile came from the host, but it is data like any other: checked again here before OpenVPN reads it.
125
+ const profile = path.join(launchDir, 'vpn', 'server.ovpn');
126
+ const remotes = assertSafeOvpn(fs.readFileSync(profile, 'utf8'), vpn.server);
127
+ const exe = await ensureOpenVpn();
128
+ const servers = await serverAddresses(remotes);
129
+ if (servers.length === 0)
130
+ throw new Error('the VPN server addresses could not be found');
131
+ if (killSwitch)
132
+ block(servers);
133
+ if (dryRun || !isWin) {
134
+ log('(dry run) would start the VPN tunnel to ' + vpn.server);
135
+ setVpnStatus('off', 'dry run');
136
+ return true;
137
+ }
138
+ log('VPN: connecting to ' + vpn.server);
139
+ result = await startTunnel({ exe, profile, username: credentials.username, password: credentials.password });
140
+ if (result.ok && result.tunnelIp && hasAdapter(result.tunnelIp) && killSwitch)
141
+ netsh(tunnelCommands(result.tunnelIp));
142
+ }
143
+ if (!result.ok) {
144
+ if (wireguardExe)
145
+ await removeWireguardTunnel(wireguardExe);
97
146
  throw new Error(result.message);
147
+ }
98
148
  if (!result.tunnelIp || !hasAdapter(result.tunnelIp))
99
149
  throw new Error('the tunnel reported it was up, but no network adapter with its address appeared');
100
- if (killSwitch)
101
- netsh(tunnelCommands(result.tunnelIp));
102
150
  const publicIp = await publicAddress();
103
151
  log('VPN: up' + (publicIp ? ' (the sandbox appears as ' + publicIp + ')' : '') + '.');
104
152
  setVpnStatus('up', 'The tunnel is up', publicIp ? { publicIp } : {});
@@ -0,0 +1,32 @@
1
+ import type { TunnelOutcome } from './tunnel.js';
2
+ /** The tunnel's name: the config file is sandbox.conf and the Windows service WireGuardTunnel$sandbox. */
3
+ export declare const WIREGUARD_TUNNEL = "sandbox";
4
+ export declare function wireguardDir(env?: NodeJS.ProcessEnv): string;
5
+ export declare function wireguardExe(env?: NodeJS.ProcessEnv): string;
6
+ export declare function wireguardDownload(arch?: string): {
7
+ file: string;
8
+ sha256: string;
9
+ url: string;
10
+ };
11
+ /** Installs WireGuard unless it is there; returns the path of wireguard.exe. Throws with the reason when it cannot. */
12
+ export declare function ensureWireguard(): Promise<string>;
13
+ /** Takes the tunnel away again (a failed tunnel must not leave a route into nothing). Never throws. */
14
+ export declare function removeWireguardTunnel(exe: string): Promise<void>;
15
+ export interface WireguardOptions {
16
+ readonly exe: string;
17
+ /** The checked config text (private key inside). */
18
+ readonly config: string;
19
+ /** The tunnel's own IPv4 address from the config's Address. */
20
+ readonly tunnelIp: string;
21
+ readonly timeoutMs?: number;
22
+ }
23
+ /**
24
+ * Installs the tunnel service from a config file that is deleted when the service has it, and waits until the service
25
+ * runs and its adapter has the tunnel address. Removes the service again when that fails.
26
+ */
27
+ export declare function startWireguardTunnel(o: WireguardOptions): Promise<TunnelOutcome>;
28
+ /**
29
+ * Waits for the server to answer: the first handshake. `poke` sends some traffic through the tunnel (a handshake only
30
+ * starts when there is something to send, unless PersistentKeepalive is set). Without wg.exe the check is skipped.
31
+ */
32
+ export declare function waitForHandshake(exe: string, poke: () => Promise<unknown>, timeoutMs?: number): Promise<TunnelOutcome>;
@@ -0,0 +1,132 @@
1
+ // WireGuard for the sandbox: one checksummed MSI from the publisher (installed silently, it brings wireguard.exe and
2
+ // wg.exe), the checked config in a file that exists only while the tunnel service starts, and a wait for the
3
+ // tunnel. The config holds the private key: it is written from the one-shot secrets file and deleted as soon as the
4
+ // service has it (or has failed). The kill switch is applied by the caller BEFORE this runs.
5
+ import { execFile } from 'node:child_process';
6
+ import crypto from 'node:crypto';
7
+ import fs from 'node:fs';
8
+ import os from 'node:os';
9
+ import path from 'node:path';
10
+ import { downloader, spawnAsync } from '../../shared/download/index.js';
11
+ import { SYSTEM } from '../../shared/system.js';
12
+ import { WIREGUARD_MSI } from '../../shared/vpn/download.js';
13
+ import { handshakeDone, serviceRunning } from '../../shared/vpn/wireguard.js';
14
+ import { dryRun, isWin, toolsDir } from '../runtime/paths.js';
15
+ import { exists, mkdirp } from '../runtime/fs.js';
16
+ import { log } from '../runtime/log.js';
17
+ /** The tunnel's name: the config file is sandbox.conf and the Windows service WireGuardTunnel$sandbox. */
18
+ export const WIREGUARD_TUNNEL = 'sandbox';
19
+ export function wireguardDir(env = process.env) {
20
+ return path.join(env.ProgramFiles ?? 'C:\\Program Files', 'WireGuard');
21
+ }
22
+ export function wireguardExe(env = process.env) {
23
+ return path.join(wireguardDir(env), 'wireguard.exe');
24
+ }
25
+ export function wireguardDownload(arch = process.arch) {
26
+ const entry = arch === 'arm64' ? WIREGUARD_MSI.arm64 : WIREGUARD_MSI.x64;
27
+ return { ...entry, url: WIREGUARD_MSI.base + entry.file };
28
+ }
29
+ const hasHash = (file, sha256) => crypto.createHash('sha256').update(fs.readFileSync(file)).digest('hex') === sha256;
30
+ /** Installs WireGuard unless it is there; returns the path of wireguard.exe. Throws with the reason when it cannot. */
31
+ export async function ensureWireguard() {
32
+ const exe = wireguardExe();
33
+ if (!isWin || dryRun)
34
+ return exe;
35
+ if (exists(exe))
36
+ return exe;
37
+ const dl = wireguardDownload();
38
+ const msi = path.join(mkdirp(path.join(toolsDir, 'downloads')), dl.file);
39
+ // The tools folder lasts between sandboxes and the agent can write it: a cached file is trusted only by its hash.
40
+ if (!exists(msi) || !hasHash(msi, dl.sha256)) {
41
+ log('VPN: downloading WireGuard ' + WIREGUARD_MSI.version + ' (about 3 MB)');
42
+ await downloader.file(dl.url, msi, dl.sha256);
43
+ }
44
+ log('VPN: installing WireGuard');
45
+ const logFile = path.join(toolsDir, 'downloads', 'wireguard-install.log');
46
+ // DO_NOT_LAUNCH: the installer would otherwise start the manager window.
47
+ const code = await spawnAsync(SYSTEM.msiexec(), ['/i', msi, '/qn', '/norestart', 'DO_NOT_LAUNCH=1', '/l*v', logFile], {
48
+ stdio: 'ignore',
49
+ });
50
+ if (code !== 0 && code !== 3010)
51
+ throw new Error('The WireGuard installer failed (exit ' + code + '). Its log: ' + logFile);
52
+ if (!exists(exe))
53
+ throw new Error('WireGuard was installed but ' + exe + ' is missing. The installer log: ' + logFile);
54
+ return exe;
55
+ }
56
+ const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
57
+ function output(file, args) {
58
+ return new Promise((resolve) => {
59
+ execFile(file, args, { windowsHide: true, timeout: 15000, maxBuffer: 1024 * 1024 }, (error, stdout, stderr) => {
60
+ const code = error ? (typeof error.code === 'number' ? error.code : -1) : 0;
61
+ resolve({ code, text: String(stdout) + String(stderr) });
62
+ });
63
+ });
64
+ }
65
+ const serviceName = () => 'WireGuardTunnel$' + WIREGUARD_TUNNEL;
66
+ const hasAdapter = (ip) => Object.values(os.networkInterfaces()).some((list) => (list ?? []).some((i) => i.address === ip));
67
+ /** Takes the tunnel away again (a failed tunnel must not leave a route into nothing). Never throws. */
68
+ export async function removeWireguardTunnel(exe) {
69
+ try {
70
+ await output(exe, ['/uninstalltunnelservice', WIREGUARD_TUNNEL]);
71
+ }
72
+ catch {
73
+ // Nothing installed, nothing to remove.
74
+ }
75
+ }
76
+ /**
77
+ * Installs the tunnel service from a config file that is deleted when the service has it, and waits until the service
78
+ * runs and its adapter has the tunnel address. Removes the service again when that fails.
79
+ */
80
+ export async function startWireguardTunnel(o) {
81
+ const root = path.join(process.env.SystemRoot ?? 'C:\\Windows', 'Temp');
82
+ const dir = fs.mkdtempSync(path.join(fs.existsSync(root) ? root : os.tmpdir(), 'sbx-wg-'));
83
+ const file = path.join(dir, WIREGUARD_TUNNEL + '.conf');
84
+ fs.writeFileSync(file, o.config, 'utf8');
85
+ const fail = async (message) => {
86
+ await removeWireguardTunnel(o.exe);
87
+ return { ok: false, message };
88
+ };
89
+ try {
90
+ const install = await output(o.exe, ['/installtunnelservice', file]);
91
+ if (install.code !== 0)
92
+ return await fail('WireGuard could not install the tunnel (exit ' + install.code + ')');
93
+ const deadline = Date.now() + (o.timeoutMs ?? 60000);
94
+ for (;;) {
95
+ const state = await output(SYSTEM.sc(), ['query', serviceName()]);
96
+ if (serviceRunning(state.text) && hasAdapter(o.tunnelIp)) {
97
+ return { ok: true, message: 'the tunnel service is running', tunnelIp: o.tunnelIp };
98
+ }
99
+ if (/STOPPED/i.test(state.text) || /1060/.test(state.text))
100
+ return await fail('the WireGuard tunnel service stopped (check the config: keys, address, endpoint)');
101
+ if (Date.now() > deadline)
102
+ return await fail('the WireGuard tunnel did not start in time (the network driver may not work here)');
103
+ await sleep(500);
104
+ }
105
+ }
106
+ finally {
107
+ // The service has read the file by now (or never will): the private key is not left on disk.
108
+ fs.rmSync(dir, { recursive: true, force: true });
109
+ }
110
+ }
111
+ /**
112
+ * Waits for the server to answer: the first handshake. `poke` sends some traffic through the tunnel (a handshake only
113
+ * starts when there is something to send, unless PersistentKeepalive is set). Without wg.exe the check is skipped.
114
+ */
115
+ export async function waitForHandshake(exe, poke, timeoutMs = 30000) {
116
+ const wg = path.join(path.dirname(exe), 'wg.exe');
117
+ if (!exists(wg))
118
+ return { ok: true, message: 'the tunnel service is running (no wg.exe to check the handshake)' };
119
+ const deadline = Date.now() + timeoutMs;
120
+ for (;;) {
121
+ await poke().catch(() => undefined);
122
+ const shown = await output(wg, ['show', WIREGUARD_TUNNEL, 'latest-handshakes']);
123
+ if (shown.code === 0 && handshakeDone(shown.text))
124
+ return { ok: true, message: 'the server answered' };
125
+ if (Date.now() > deadline)
126
+ return {
127
+ ok: false,
128
+ message: 'the VPN server did not answer (check the endpoint, the keys and that its UDP port is reachable)',
129
+ };
130
+ await sleep(1000);
131
+ }
132
+ }
@@ -9,5 +9,9 @@ export interface AgentCommand {
9
9
  export declare function cmdSafe(p: string): boolean;
10
10
  /** The agent's own executable on this PC, skipping this tool's forwarding files. */
11
11
  export declare function realAgent(command: string, skipDir: string, pathValue?: string): string | null;
12
+ /** realAgent without blocking the program: every candidate is looked up at once, the first in PATH order wins. */
13
+ export declare function realAgentAsync(command: string, skipDir: string, pathValue?: string): Promise<string | null>;
12
14
  /** The agents that get a forwarding command: installed on this PC, and listed (or "auto" = all of them). */
13
15
  export declare function agentCommands(config: Config, storageRoot: string, pathValue?: string): AgentCommand[];
16
+ /** agentCommands without blocking the program. */
17
+ export declare function agentCommandsAsync(config: Config, storageRoot: string, pathValue?: string): Promise<AgentCommand[]>;
@@ -3,7 +3,7 @@
3
3
  // <storage>\agent-commands go first on YOUR PATH (so they win over npm's and the installers' copies); turning it off
4
4
  // removes the files and that one PATH entry. SANDBOX_AGENT_DIRECT=1 runs the real agent on this PC instead.
5
5
  import path from 'node:path';
6
- import { fileExists } from '../../core/util/index.js';
6
+ import { fileExists, fileExistsAsync } from '../../core/util/index.js';
7
7
  export function commandsDir(storageRoot) {
8
8
  return path.join(storageRoot, 'agent-commands');
9
9
  }
@@ -12,11 +12,14 @@ const EXTENSIONS = ['.exe', '.cmd', '.bat'];
12
12
  export function cmdSafe(p) {
13
13
  return !/[%"^\r\n]/.test(p);
14
14
  }
15
+ /** The folders of PATH to look in, without this tool's forwarding folder. */
16
+ function searchDirs(skipDir, pathValue) {
17
+ const skip = path.resolve(skipDir).toLowerCase();
18
+ return pathValue.split(path.delimiter).filter((d) => d && path.resolve(d).toLowerCase() !== skip);
19
+ }
15
20
  /** The agent's own executable on this PC, skipping this tool's forwarding files. */
16
21
  export function realAgent(command, skipDir, pathValue = process.env.PATH ?? process.env.Path ?? '') {
17
- const skip = path.resolve(skipDir).toLowerCase();
18
- const dirs = pathValue.split(path.delimiter).filter((d) => d && path.resolve(d).toLowerCase() !== skip);
19
- for (const dir of dirs)
22
+ for (const dir of searchDirs(skipDir, pathValue))
20
23
  for (const ext of EXTENSIONS) {
21
24
  const candidate = path.join(dir, command + ext);
22
25
  if (fileExists(candidate))
@@ -24,14 +27,35 @@ export function realAgent(command, skipDir, pathValue = process.env.PATH ?? proc
24
27
  }
25
28
  return null;
26
29
  }
27
- /** The agents that get a forwarding command: installed on this PC, and listed (or "auto" = all of them). */
28
- export function agentCommands(config, storageRoot, pathValue) {
30
+ /** realAgent without blocking the program: every candidate is looked up at once, the first in PATH order wins. */
31
+ export async function realAgentAsync(command, skipDir, pathValue = process.env.PATH ?? process.env.Path ?? '') {
32
+ const candidates = searchDirs(skipDir, pathValue).flatMap((dir) => EXTENSIONS.map((ext) => path.join(dir, command + ext)));
33
+ const found = await Promise.all(candidates.map(fileExistsAsync));
34
+ const at = found.indexOf(true);
35
+ return at < 0 ? null : candidates[at];
36
+ }
37
+ /** The agents that may get a forwarding command (listed, or "auto" = all) and the command each is typed as. */
38
+ function wantedCommands(config) {
29
39
  const wanted = config.agentCommands.agents;
30
40
  return Object.entries(config.harnesses).flatMap(([harness, h]) => {
31
41
  if (wanted !== 'auto' && !wanted.includes(harness))
32
42
  return [];
33
43
  const command = h.npmBin ?? (h.hostExecutable ? path.parse(h.hostExecutable).name : harness);
34
- const real = /^[A-Za-z0-9._-]+$/.test(command) ? realAgent(command, commandsDir(storageRoot), pathValue) : null;
44
+ return /^[A-Za-z0-9._-]+$/.test(command) ? [{ harness, command }] : [];
45
+ });
46
+ }
47
+ /** The agents that get a forwarding command: installed on this PC, and listed (or "auto" = all of them). */
48
+ export function agentCommands(config, storageRoot, pathValue) {
49
+ return wantedCommands(config).flatMap(({ harness, command }) => {
50
+ const real = realAgent(command, commandsDir(storageRoot), pathValue);
35
51
  return real && cmdSafe(real) ? [{ harness, command, real }] : [];
36
52
  });
37
53
  }
54
+ /** agentCommands without blocking the program. */
55
+ export async function agentCommandsAsync(config, storageRoot, pathValue) {
56
+ const found = await Promise.all(wantedCommands(config).map(async ({ harness, command }) => {
57
+ const real = await realAgentAsync(command, commandsDir(storageRoot), pathValue);
58
+ return real && cmdSafe(real) ? [{ harness, command, real }] : [];
59
+ }));
60
+ return found.flat();
61
+ }
@@ -5,9 +5,9 @@ export declare const AuditEventSchema: z.ZodObject<{
5
5
  k: z.ZodEnum<{
6
6
  file: "file";
7
7
  session: "session";
8
+ note: "note";
8
9
  proc: "proc";
9
10
  guard: "guard";
10
- note: "note";
11
11
  }>;
12
12
  p: z.ZodOptional<z.ZodString>;
13
13
  c: z.ZodOptional<z.ZodString>;
@@ -16,4 +16,6 @@ export declare function sessionViewers(session: SessionStatus): Promise<Viewers
16
16
  */
17
17
  export declare function findUnwatchedSession(dataPath: string, harness: string, cwd: string): Promise<SessionStatus | null>;
18
18
  /** Bridges this terminal to a session. Returns the agent's exit code, or 0 when the user detaches. */
19
- export declare function attachTerminal(session: SessionStatus): Promise<number>;
19
+ export declare function attachTerminal(session: SessionStatus, o?: {
20
+ clipboardWrites?: boolean;
21
+ }): Promise<number>;
@@ -53,7 +53,7 @@ export async function findUnwatchedSession(dataPath, harness, cwd) {
53
53
  return null;
54
54
  }
55
55
  /** Bridges this terminal to a session. Returns the agent's exit code, or 0 when the user detaches. */
56
- export async function attachTerminal(session) {
56
+ export async function attachTerminal(session, o = {}) {
57
57
  if (!process.stdin.isTTY || !process.stdout.isTTY)
58
58
  fail('--terminal / attach needs an interactive terminal.', 'Run it directly in Windows Terminal or PowerShell, not through a pipe or script.');
59
59
  if (!session.attach)
@@ -71,7 +71,10 @@ export async function attachTerminal(session) {
71
71
  safeText(session.cwd) +
72
72
  '). Detach any time with Ctrl+]');
73
73
  await sleep(400);
74
- const result = await runTerminalClient(socket, session.attach.token, { detachKey: DETACH_KEY });
74
+ const result = await runTerminalClient(socket, session.attach.token, {
75
+ detachKey: DETACH_KEY,
76
+ clipboardWrites: o.clipboardWrites,
77
+ });
75
78
  switch (result.reason) {
76
79
  case 'detach':
77
80
  console.log('Detached. The session keeps running; reattach with: sandbox attach ' + session.id);
@@ -46,6 +46,8 @@ export declare class SandboxChannel {
46
46
  exec(command: string[], cwd: string, timeoutSeconds: number): Promise<GuestResponse>;
47
47
  /** Restarts running sessions in place (they resume via the harness's resumeArguments, e.g. claude --continue). */
48
48
  restart(harness: string | null, cwd?: string): Promise<GuestResponse>;
49
+ /** The PC is low on memory or CPU: the sandbox slows its builds down for `seconds`. Never waits for an answer. */
50
+ pressure(reason: string, seconds: number): void;
49
51
  ping(): Promise<GuestResponse>;
50
52
  runningSessions(): SessionStatus[];
51
53
  /** Running sessions of the sandbox that is running now (its launch id), newest last, capped. */
@@ -179,6 +179,10 @@ export class SandboxChannel {
179
179
  this.send({ kind: 'restart', id: requestId, harness, ...(cwd ? { cwd } : {}), createdAt: now() });
180
180
  return this.waitForResponse(requestId, 30000);
181
181
  }
182
+ /** The PC is low on memory or CPU: the sandbox slows its builds down for `seconds`. Never waits for an answer. */
183
+ pressure(reason, seconds) {
184
+ this.send({ kind: 'pressure', id: newId(), reason, seconds, createdAt: now() });
185
+ }
182
186
  async ping() {
183
187
  const requestId = newId();
184
188
  this.send({ kind: 'ping', id: requestId, createdAt: now() });