windows-development-sandbox 1.9.0 → 1.11.3

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 (398) hide show
  1. package/README.md +64 -17
  2. package/config.example.json +7 -2
  3. package/dist/app/features/accounts/accounts.def.d.ts +122 -0
  4. package/dist/app/features/accounts/accounts.def.js +243 -0
  5. package/dist/app/features/accounts/accounts.run.d.ts +51 -0
  6. package/dist/app/features/accounts/accounts.run.js +125 -0
  7. package/dist/app/features/accounts/index.d.ts +2 -0
  8. package/dist/app/features/accounts/index.js +3 -0
  9. package/dist/app/features/agents/agent-entry.d.ts +17 -0
  10. package/dist/app/features/agents/agent-entry.js +50 -0
  11. package/dist/app/features/agents/agents.def.d.ts +65 -0
  12. package/dist/app/features/agents/agents.def.js +142 -0
  13. package/dist/app/features/agents/agents.run.d.ts +31 -0
  14. package/dist/app/features/agents/agents.run.js +189 -0
  15. package/dist/app/features/agents/index.d.ts +3 -0
  16. package/dist/app/features/agents/index.js +4 -0
  17. package/dist/app/features/apps/apps-rows.d.ts +31 -0
  18. package/dist/app/features/apps/apps-rows.js +73 -0
  19. package/dist/app/features/apps/apps.def.d.ts +181 -0
  20. package/dist/app/features/apps/apps.def.js +264 -0
  21. package/dist/app/features/apps/apps.run.d.ts +58 -0
  22. package/dist/app/features/apps/apps.run.js +76 -0
  23. package/dist/app/features/apps/index.d.ts +3 -0
  24. package/dist/app/features/apps/index.js +4 -0
  25. package/dist/app/features/data/data.def.d.ts +154 -0
  26. package/dist/app/features/data/data.def.js +201 -0
  27. package/dist/app/features/data/data.run.d.ts +46 -0
  28. package/dist/app/features/data/data.run.js +141 -0
  29. package/dist/app/features/data/index.d.ts +2 -0
  30. package/dist/app/features/data/index.js +3 -0
  31. package/dist/app/features/defs.d.ts +952 -4
  32. package/dist/app/features/defs.js +147 -10
  33. package/dist/app/features/engine/engine.def.d.ts +228 -0
  34. package/dist/app/features/engine/engine.def.js +194 -0
  35. package/dist/app/features/engine/engine.run.d.ts +72 -0
  36. package/dist/app/features/engine/engine.run.js +77 -0
  37. package/dist/app/features/engine/index.d.ts +2 -0
  38. package/dist/app/features/engine/index.js +4 -0
  39. package/dist/app/features/files/files.def.d.ts +51 -0
  40. package/dist/app/features/files/files.def.js +38 -0
  41. package/dist/app/features/files/files.run.d.ts +20 -0
  42. package/dist/app/features/files/files.run.js +59 -0
  43. package/dist/app/features/files/index.d.ts +2 -0
  44. package/dist/app/features/files/index.js +3 -0
  45. package/dist/app/features/github/github.def.d.ts +80 -0
  46. package/dist/app/features/github/github.def.js +142 -0
  47. package/dist/app/features/github/github.run.d.ts +36 -0
  48. package/dist/app/features/github/github.run.js +59 -0
  49. package/dist/app/features/github/index.d.ts +2 -0
  50. package/dist/app/features/github/index.js +3 -0
  51. package/dist/app/features/health/health.def.d.ts +275 -0
  52. package/dist/app/features/health/health.def.js +281 -0
  53. package/dist/app/features/health/health.run.d.ts +125 -0
  54. package/dist/app/features/health/health.run.js +204 -0
  55. package/dist/app/features/health/index.d.ts +2 -0
  56. package/dist/app/features/health/index.js +3 -0
  57. package/dist/app/features/logs/index.d.ts +2 -0
  58. package/dist/app/features/logs/index.js +3 -0
  59. package/dist/app/features/logs/logs.def.d.ts +78 -0
  60. package/dist/app/features/logs/logs.def.js +115 -0
  61. package/dist/app/features/logs/logs.run.d.ts +20 -0
  62. package/dist/app/features/logs/logs.run.js +42 -0
  63. package/dist/app/features/projects/index.d.ts +2 -0
  64. package/dist/app/features/projects/index.js +3 -0
  65. package/dist/app/features/projects/projects.def.d.ts +101 -0
  66. package/dist/app/features/projects/projects.def.js +208 -0
  67. package/dist/app/features/projects/projects.run.d.ts +56 -0
  68. package/dist/app/features/projects/projects.run.js +155 -0
  69. package/dist/app/features/sandbox/sandbox.def.js +11 -5
  70. package/dist/app/features/sessions/sessions.def.js +3 -0
  71. package/dist/app/features/settings/categories.def.js +3 -2
  72. package/dist/{cli/menu/settings/share.d.ts → app/features/settings/settings-share.d.ts} +1 -1
  73. package/dist/{cli/menu/settings/share.js → app/features/settings/settings-share.js} +3 -2
  74. package/dist/app/features/settings/settings.def.d.ts +22 -5
  75. package/dist/app/features/settings/settings.def.js +81 -6
  76. package/dist/app/features/settings/settings.run.d.ts +17 -0
  77. package/dist/app/features/settings/settings.run.js +89 -4
  78. package/dist/app/features/storage/index.d.ts +2 -0
  79. package/dist/app/features/storage/index.js +3 -0
  80. package/dist/app/features/storage/storage.def.d.ts +133 -0
  81. package/dist/app/features/storage/storage.def.js +225 -0
  82. package/dist/app/features/storage/storage.run.d.ts +62 -0
  83. package/dist/app/features/storage/storage.run.js +370 -0
  84. package/dist/app/features/updates/index.d.ts +3 -0
  85. package/dist/app/features/updates/index.js +4 -0
  86. package/dist/app/features/updates/update-lines.d.ts +13 -0
  87. package/dist/app/features/updates/update-lines.js +61 -0
  88. package/dist/app/features/updates/updates.def.d.ts +89 -0
  89. package/dist/app/features/updates/updates.def.js +225 -0
  90. package/dist/app/features/updates/updates.run.d.ts +48 -0
  91. package/dist/app/features/updates/updates.run.js +154 -0
  92. package/dist/app/features/vpn/index.d.ts +2 -0
  93. package/dist/app/features/vpn/index.js +3 -0
  94. package/dist/app/features/vpn/vpn.def.d.ts +112 -0
  95. package/dist/app/features/vpn/vpn.def.js +174 -0
  96. package/dist/app/features/vpn/vpn.run.d.ts +37 -0
  97. package/dist/app/features/vpn/vpn.run.js +54 -0
  98. package/dist/app/features/web/index.d.ts +2 -0
  99. package/dist/app/features/web/index.js +3 -0
  100. package/dist/app/features/web/web.def.d.ts +33 -0
  101. package/dist/app/features/web/web.def.js +90 -0
  102. package/dist/app/features/web/web.run.d.ts +24 -0
  103. package/dist/app/features/web/web.run.js +93 -0
  104. package/dist/app/kernel/define.d.ts +10 -1
  105. package/dist/app/kernel/io.d.ts +34 -0
  106. package/dist/app/kernel/io.js +9 -0
  107. package/dist/app/kernel/rpc.d.ts +17 -0
  108. package/dist/app/kernel/text.d.ts +2 -0
  109. package/dist/app/kernel/text.js +7 -0
  110. package/dist/app/kernel/view.d.ts +24 -1
  111. package/dist/app/kernel/view.js +1 -1
  112. package/dist/app/registry.js +74 -1
  113. package/dist/cli/args.js +1 -1
  114. package/dist/cli/commands/context.d.ts +7 -0
  115. package/dist/cli/commands/context.js +29 -2
  116. package/dist/cli/commands/engine.js +5 -58
  117. package/dist/cli/commands/github.d.ts +2 -0
  118. package/dist/cli/commands/github.js +11 -0
  119. package/dist/cli/commands/index.d.ts +3 -5
  120. package/dist/cli/commands/index.js +3 -5
  121. package/dist/cli/commands/launch/launch.js +23 -4
  122. package/dist/cli/commands/launch/running.js +4 -1
  123. package/dist/cli/commands/maintenance/config.js +1 -6
  124. package/dist/cli/commands/maintenance/index.d.ts +0 -1
  125. package/dist/cli/commands/maintenance/index.js +1 -2
  126. package/dist/cli/commands/maintenance/logs.js +27 -32
  127. package/dist/cli/commands/maintenance/migrate.d.ts +0 -8
  128. package/dist/cli/commands/maintenance/migrate.js +2 -44
  129. package/dist/cli/commands/maintenance/update.d.ts +0 -19
  130. package/dist/cli/commands/maintenance/update.js +1 -99
  131. package/dist/cli/commands/projects/index.d.ts +0 -1
  132. package/dist/cli/commands/projects/index.js +0 -1
  133. package/dist/cli/commands/registry.d.ts +1 -1
  134. package/dist/cli/commands/registry.js +29 -11
  135. package/dist/cli/commands/repair/checks/data.js +1 -1
  136. package/dist/cli/commands/repair/checks/hcs.js +28 -1
  137. package/dist/cli/commands/repair/checks/sandbox.js +1 -1
  138. package/dist/cli/commands/repair/health-run.d.ts +30 -0
  139. package/dist/cli/commands/repair/health-run.js +32 -0
  140. package/dist/cli/commands/repair/printer.d.ts +2 -2
  141. package/dist/cli/commands/repair/printer.js +2 -11
  142. package/dist/cli/commands/repair/run.d.ts +1 -21
  143. package/dist/cli/commands/repair/run.js +13 -70
  144. package/dist/cli/commands/storage/index.d.ts +0 -1
  145. package/dist/cli/commands/storage/index.js +0 -1
  146. package/dist/cli/commands/storage/offer.js +6 -2
  147. package/dist/cli/commands/vpn.d.ts +1 -3
  148. package/dist/cli/commands/vpn.js +8 -88
  149. package/dist/cli/commands/web.d.ts +7 -1
  150. package/dist/cli/commands/web.js +45 -71
  151. package/dist/cli/index.js +3 -2
  152. package/dist/cli/manifest.d.ts +16 -153
  153. package/dist/cli/manifest.js +20 -50
  154. package/dist/cli/menu/command-form.js +1 -2
  155. package/dist/cli/menu/io-menu.d.ts +2 -2
  156. package/dist/cli/menu/io-menu.js +6 -4
  157. package/dist/cli/menu/menu.js +1 -1
  158. package/dist/cli/menu/pages/accounts-page.d.ts +5 -17
  159. package/dist/cli/menu/pages/accounts-page.js +28 -170
  160. package/dist/cli/menu/pages/agents-page.d.ts +17 -18
  161. package/dist/cli/menu/pages/agents-page.js +67 -124
  162. package/dist/cli/menu/pages/apps-page.d.ts +15 -8
  163. package/dist/cli/menu/pages/apps-page.js +121 -29
  164. package/dist/cli/menu/pages/list-page.js +10 -1
  165. package/dist/cli/menu/pages/maintenance-page.d.ts +51 -52
  166. package/dist/cli/menu/pages/maintenance-page.js +246 -427
  167. package/dist/cli/menu/pages/sandbox-page.d.ts +3 -5
  168. package/dist/cli/menu/pages/sandbox-page.js +34 -112
  169. package/dist/cli/menu/pages/settings-page.d.ts +21 -3
  170. package/dist/cli/menu/pages/settings-page.js +89 -17
  171. package/dist/cli/menu/render-view.js +2 -1
  172. package/dist/cli/menu/settings/index.d.ts +0 -1
  173. package/dist/cli/menu/settings/index.js +3 -3
  174. package/dist/cli/menu/views.js +28 -0
  175. package/dist/cli/operations/handler.js +7 -3
  176. package/dist/cli/operations/input.d.ts +1 -1
  177. package/dist/cli/operations/input.js +29 -5
  178. package/dist/cli/operations/io-cli.js +9 -1
  179. package/dist/cli/operations/spec.js +8 -2
  180. package/dist/cli/output/render-view.js +2 -0
  181. package/dist/cli/{commands/data/collect.js → services/data-collect.js} +2 -2
  182. package/dist/cli/{commands/data/remove.js → services/data-remove.js} +2 -2
  183. package/dist/cli/services/data-service.d.ts +1 -1
  184. package/dist/cli/services/data-service.js +2 -2
  185. package/dist/cli/services/databases-service.d.ts +5 -0
  186. package/dist/cli/services/databases-service.js +15 -0
  187. package/dist/cli/services/health-service.d.ts +8 -4
  188. package/dist/cli/services/health-service.js +10 -8
  189. package/dist/cli/services/index.d.ts +4 -1
  190. package/dist/cli/services/index.js +4 -1
  191. package/dist/cli/services/logs-service.d.ts +7 -0
  192. package/dist/cli/services/logs-service.js +13 -0
  193. package/dist/cli/services/storage-service.d.ts +19 -0
  194. package/dist/cli/services/storage-service.js +71 -0
  195. package/dist/cli/services/vpn-service.d.ts +6 -4
  196. package/dist/cli/services/vpn-service.js +5 -11
  197. package/dist/cli/services/web-service.d.ts +4 -3
  198. package/dist/cli/services/web-service.js +12 -32
  199. package/dist/core/config/defaults.js +3 -2
  200. package/dist/core/schema/config.d.ts +6 -0
  201. package/dist/core/schema/config.js +2 -0
  202. package/dist/core/schema/github.d.ts +4 -0
  203. package/dist/core/schema/github.js +7 -0
  204. package/dist/core/schema/index.d.ts +1 -0
  205. package/dist/core/schema/index.js +1 -0
  206. package/dist/core/schema/profile.d.ts +1 -0
  207. package/dist/core/schema/profile.js +3 -0
  208. package/dist/core/schema/sandbox.js +3 -3
  209. package/dist/core/schema/toolchains.js +2 -2
  210. package/dist/core/schema/vpn.js +1 -1
  211. package/dist/core/schema/web.d.ts +1 -0
  212. package/dist/core/schema/web.js +4 -1
  213. package/dist/core/types.d.ts +6 -3
  214. package/dist/guest/boot/boot.js +11 -1
  215. package/dist/guest/daemon/daemon.js +1 -1
  216. package/dist/guest/daemon/sessions.d.ts +3 -2
  217. package/dist/guest/daemon/sessions.js +38 -7
  218. package/dist/guest/governor/core.d.ts +44 -0
  219. package/dist/guest/governor/core.js +124 -0
  220. package/dist/guest/governor/decide.d.ts +53 -15
  221. package/dist/guest/governor/decide.js +155 -72
  222. package/dist/guest/governor/governor.d.ts +2 -35
  223. package/dist/guest/governor/governor.js +35 -99
  224. package/dist/guest/governor/index.d.ts +1 -0
  225. package/dist/guest/governor/index.js +1 -0
  226. package/dist/guest/network/firewall.d.ts +3 -0
  227. package/dist/guest/network/firewall.js +65 -23
  228. package/dist/guest/runtime/process.js +53 -5
  229. package/dist/guest/sessions/viewer.js +5 -1
  230. package/dist/guest/setup/claude-mem.js +12 -4
  231. package/dist/guest/setup/github-token.d.ts +7 -0
  232. package/dist/guest/setup/github-token.js +65 -0
  233. package/dist/guest/toolchains/languages/node.js +40 -4
  234. package/dist/guest/toolchains/native/rust-extras.js +2 -2
  235. package/dist/guest/toolchains/tools/cli.d.ts +8 -0
  236. package/dist/guest/toolchains/tools/cli.js +16 -2
  237. package/dist/guest/vpn/openvpn.d.ts +5 -0
  238. package/dist/guest/vpn/openvpn.js +14 -1
  239. package/dist/guest/vpn/vpn.js +55 -16
  240. package/dist/guest/vpn/wireguard.js +2 -0
  241. package/dist/host/channel/attach.d.ts +1 -0
  242. package/dist/host/channel/attach.js +1 -0
  243. package/dist/host/channel/channel.d.ts +1 -1
  244. package/dist/host/channel/channel.js +2 -2
  245. package/dist/host/engines/hcs/check.d.ts +1 -0
  246. package/dist/host/engines/hcs/check.js +1 -1
  247. package/dist/host/engines/hcs/engine.d.ts +1 -1
  248. package/dist/host/engines/hcs/engine.js +6 -4
  249. package/dist/host/engines/hcs/helper.d.ts +11 -0
  250. package/dist/host/engines/hcs/helper.js +31 -2
  251. package/dist/host/engines/hcs/leftovers.d.ts +42 -0
  252. package/dist/host/engines/hcs/leftovers.js +101 -0
  253. package/dist/host/engines/index.d.ts +1 -0
  254. package/dist/host/engines/index.js +1 -0
  255. package/dist/host/github/deliver.d.ts +13 -0
  256. package/dist/host/github/deliver.js +51 -0
  257. package/dist/host/github/index.d.ts +2 -0
  258. package/dist/host/github/index.js +3 -0
  259. package/dist/host/github/token.d.ts +21 -0
  260. package/dist/host/github/token.js +74 -0
  261. package/dist/host/launch/generate.js +11 -5
  262. package/dist/host/launch/index.d.ts +1 -1
  263. package/dist/host/launch/index.js +1 -1
  264. package/dist/host/launch/session-file.js +1 -2
  265. package/dist/host/launch/wsb-file.d.ts +11 -7
  266. package/dist/host/launch/wsb-file.js +30 -12
  267. package/dist/host/life/host-memory.d.ts +1 -0
  268. package/dist/host/life/host-memory.js +4 -1
  269. package/dist/host/life/watchdog.d.ts +11 -0
  270. package/dist/host/life/watchdog.js +36 -4
  271. package/dist/host/sandbox/tasklist.d.ts +6 -0
  272. package/dist/host/sandbox/tasklist.js +37 -0
  273. package/dist/host/secrets/launch-secrets.d.ts +2 -1
  274. package/dist/host/secrets/launch-secrets.js +4 -2
  275. package/dist/host/vpn/status.js +13 -3
  276. package/dist/host/web/backend.d.ts +1 -3
  277. package/dist/host/web/index.d.ts +2 -1
  278. package/dist/host/web/index.js +2 -1
  279. package/dist/host/web/live.d.ts +23 -0
  280. package/dist/host/web/live.js +107 -0
  281. package/dist/host/web/rpc.d.ts +6 -1
  282. package/dist/host/web/rpc.js +23 -31
  283. package/dist/host/web/runner.d.ts +4 -0
  284. package/dist/host/web/runner.js +12 -1
  285. package/dist/host/web/server.d.ts +30 -2
  286. package/dist/host/web/server.js +87 -29
  287. package/dist/host/web/state.d.ts +1 -0
  288. package/dist/host/web/state.js +2 -0
  289. package/dist/runtime/agent.js +44 -16
  290. package/dist/runtime/daemon.js +1154 -857
  291. package/dist/runtime/db.js +44 -16
  292. package/dist/runtime/main.js +532 -115
  293. package/dist/runtime/netproxy.js +34 -9
  294. package/dist/runtime/view.js +499 -149
  295. package/dist/selftest/app.test.js +620 -12
  296. package/dist/selftest/channel.test.js +18 -1
  297. package/dist/selftest/cli.test.js +1 -1
  298. package/dist/selftest/config.test.js +1 -1
  299. package/dist/selftest/contained.test.js +1 -1
  300. package/dist/selftest/data.test.js +1 -2
  301. package/dist/selftest/engine.test.js +3 -1
  302. package/dist/selftest/github.test.d.ts +2 -0
  303. package/dist/selftest/github.test.js +243 -0
  304. package/dist/selftest/guest-boot.test.js +47 -1
  305. package/dist/selftest/hcs.test.js +105 -8
  306. package/dist/selftest/health.test.js +71 -0
  307. package/dist/selftest/heavy-load.test.js +152 -66
  308. package/dist/selftest/index.js +4 -0
  309. package/dist/selftest/layout.test.js +8 -0
  310. package/dist/selftest/live-terminal.test.js +74 -0
  311. package/dist/selftest/menu-screens.js +171 -56
  312. package/dist/selftest/menu.test.js +54 -37
  313. package/dist/selftest/migrate.test.js +14 -12
  314. package/dist/selftest/network.test.js +20 -1
  315. package/dist/selftest/powershell-variables.test.d.ts +2 -0
  316. package/dist/selftest/powershell-variables.test.js +61 -0
  317. package/dist/selftest/support/engine-golden.js +1 -0
  318. package/dist/selftest/support/fixtures.d.ts +5 -0
  319. package/dist/selftest/support/web-preview.d.ts +1 -0
  320. package/dist/selftest/support/web-preview.js +856 -0
  321. package/dist/selftest/update-service.test.js +15 -8
  322. package/dist/selftest/vpn.test.js +86 -2
  323. package/dist/selftest/web.test.js +58 -11
  324. package/dist/shared/channel/run-dir.d.ts +2 -0
  325. package/dist/shared/channel/run-dir.js +3 -0
  326. package/dist/shared/channel/schema.d.ts +7 -0
  327. package/dist/shared/channel/schema.js +9 -1
  328. package/dist/shared/channel/secrets.d.ts +1 -0
  329. package/dist/shared/channel/secrets.js +3 -0
  330. package/dist/shared/github/index.d.ts +1 -0
  331. package/dist/shared/github/index.js +2 -0
  332. package/dist/shared/github/token.d.ts +29 -0
  333. package/dist/shared/github/token.js +89 -0
  334. package/dist/shared/io/copy-tree.d.ts +7 -0
  335. package/dist/shared/io/copy-tree.js +45 -0
  336. package/dist/shared/io/index.d.ts +2 -0
  337. package/dist/shared/io/index.js +2 -0
  338. package/dist/shared/io/robocopy-result.d.ts +6 -0
  339. package/dist/shared/io/robocopy-result.js +23 -0
  340. package/dist/shared/memory/policy.d.ts +38 -12
  341. package/dist/shared/memory/policy.js +58 -19
  342. package/dist/shared/network/firewall-rules.d.ts +8 -0
  343. package/dist/shared/network/firewall-rules.js +28 -0
  344. package/dist/shared/pty/ctrl-c.d.ts +18 -0
  345. package/dist/shared/pty/ctrl-c.js +36 -0
  346. package/dist/shared/pty/index.d.ts +1 -0
  347. package/dist/shared/pty/index.js +1 -0
  348. package/dist/shared/pty/terminal.d.ts +2 -0
  349. package/dist/shared/pty/terminal.js +21 -2
  350. package/dist/shared/vpn/kill-switch.d.ts +13 -2
  351. package/dist/shared/vpn/kill-switch.js +88 -8
  352. package/dist/shared/web/protocol.d.ts +0 -4
  353. package/dist/sync/watcher.js +1 -1
  354. package/dist/web/index.html +2 -2
  355. package/dist/web/{main-T5DMCHYV.css → main-6JF3ASB5.css} +1 -1
  356. package/dist/web/main-Z645W45E.js +219 -0
  357. package/native/csharp/HcsHost/AssemblyInfo.cs +3 -3
  358. package/native/csharp/HostTools/AssemblyInfo.cs +3 -3
  359. package/native/csharp/HostTools/MemoryStatus.cs +128 -107
  360. package/native/scripts/memory-status.ps1 +10 -2
  361. package/package.json +10 -3
  362. package/sandbox/csharp/MemGov.cs +668 -398
  363. package/sandbox/csharp/PtyHost.cs +104 -15
  364. package/sandbox/csharp/SandboxTools/CpuCap.cs +50 -1
  365. package/sandbox/csharp/SandboxTools/Program.cs +1 -1
  366. package/sandbox/scripts/new-shortcuts.ps1 +3 -1
  367. package/sandbox/scripts/repair-wmi-services.ps1 +26 -0
  368. package/schemas/config.schema.json +25 -7
  369. package/schemas/sandbox-config.schema.json +7 -3
  370. package/dist/cli/commands/accounts.d.ts +0 -2
  371. package/dist/cli/commands/accounts.js +0 -96
  372. package/dist/cli/commands/data/clean.d.ts +0 -3
  373. package/dist/cli/commands/data/clean.js +0 -60
  374. package/dist/cli/commands/data/index.d.ts +0 -4
  375. package/dist/cli/commands/data/index.js +0 -62
  376. package/dist/cli/commands/maintenance/clean.d.ts +0 -2
  377. package/dist/cli/commands/maintenance/clean.js +0 -24
  378. package/dist/cli/commands/maintenance/config-share.d.ts +0 -12
  379. package/dist/cli/commands/maintenance/config-share.js +0 -80
  380. package/dist/cli/commands/projects/projects.d.ts +0 -2
  381. package/dist/cli/commands/projects/projects.js +0 -162
  382. package/dist/cli/commands/storage/contained.d.ts +0 -3
  383. package/dist/cli/commands/storage/contained.js +0 -179
  384. package/dist/cli/commands/storage/storage.d.ts +0 -8
  385. package/dist/cli/commands/storage/storage.js +0 -179
  386. package/dist/cli/menu/add-agent.d.ts +0 -47
  387. package/dist/cli/menu/add-agent.js +0 -168
  388. package/dist/cli/menu/apps.d.ts +0 -69
  389. package/dist/cli/menu/apps.js +0 -214
  390. package/dist/cli/menu/data-folder.d.ts +0 -18
  391. package/dist/cli/menu/data-folder.js +0 -99
  392. package/dist/cli/menu/share-settings.d.ts +0 -21
  393. package/dist/cli/menu/share-settings.js +0 -112
  394. package/dist/cli/menu/vpn-login.d.ts +0 -16
  395. package/dist/cli/menu/vpn-login.js +0 -74
  396. package/dist/web/main-NRRS5O6Z.js +0 -157
  397. /package/dist/cli/{commands/data/collect.d.ts → services/data-collect.d.ts} +0 -0
  398. /package/dist/cli/{commands/data/remove.d.ts → services/data-remove.d.ts} +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,
@@ -1,5 +1,6 @@
1
1
  import { connect, runTerminalClient } from '../../shared/pty/index.js';
2
2
  import { run } from '../runtime/paths.js';
3
+ import { session as sandboxSession } from '../runtime/session.js';
3
4
  /**
4
5
  * A session that is still being set up (its project provisioning, its agent installing): show the progress until it
5
6
  * runs.
@@ -32,7 +33,10 @@ export async function viewSession(id) {
32
33
  const socket = await connect({ ...session.attach, addresses: ['127.0.0.1'] });
33
34
  // The sandbox's own terminal: a copy in the agent reaches the sandbox's clipboard (and yours only through
34
35
  // sandbox.clipboard, which already shares it).
35
- const result = await runTerminalClient(socket, session.attach.token, { clipboardWrites: true });
36
+ const result = await runTerminalClient(socket, session.attach.token, {
37
+ clipboardWrites: true,
38
+ confirmCtrlC: sandboxSession.attach.confirmCtrlC !== false,
39
+ });
36
40
  if (result.reason === 'exit')
37
41
  console.log(session.harness + ' exited with code ' + result.exitCode + '.');
38
42
  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))
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Before the tools are set up: the folder gh uses for a handed-over token exists only when one arrived (gh's setup
3
+ * points GH_CONFIG_DIR at it while it exists). Without one, gh keeps the login you made in the sandbox.
4
+ */
5
+ export declare function prepareGithubFolder(token: string | undefined): boolean;
6
+ /** gh logs in with the token (stdin) in the sandbox-only folder, then https git to github.com asks gh. */
7
+ export declare function loginGithub(token: string, env: NodeJS.ProcessEnv): void;
@@ -0,0 +1,65 @@
1
+ // The GitHub token the host handed over (github.shareToken, only with git access "full"): gh logs in with it in a
2
+ // folder on the sandbox's own disk (gone with the sandbox, never in the persistent state), and https git asks gh. The
3
+ // token goes to gh on stdin, never in an argument, a file of ours or a log line.
4
+ import fs from 'node:fs';
5
+ import { spawnSync } from 'node:child_process';
6
+ import { ghLoginCommand, ghOutputText, ghSetupGitCommand } from '../../shared/github/index.js';
7
+ import { deliveredGhConfigDir, ghConfigDir, ghExePath } from '../toolchains/tools/cli.js';
8
+ import { addMachineEnvironment } from './environment.js';
9
+ import { dryRun } from '../runtime/paths.js';
10
+ import { exists, mkdirp } from '../runtime/fs.js';
11
+ import { log } from '../runtime/log.js';
12
+ const GH_TIMEOUT_MS = 60_000;
13
+ /**
14
+ * Before the tools are set up: the folder gh uses for a handed-over token exists only when one arrived (gh's setup
15
+ * points GH_CONFIG_DIR at it while it exists). Without one, gh keeps the login you made in the sandbox.
16
+ */
17
+ export function prepareGithubFolder(token) {
18
+ const dir = deliveredGhConfigDir();
19
+ fs.rmSync(dir, { recursive: true, force: true });
20
+ if (!token)
21
+ return false;
22
+ mkdirp(dir);
23
+ return true;
24
+ }
25
+ function runGh(command, token, what) {
26
+ const result = spawnSync(command.file, [...command.args], {
27
+ env: command.env,
28
+ encoding: 'utf8',
29
+ windowsHide: true,
30
+ timeout: GH_TIMEOUT_MS,
31
+ stdio: ['pipe', 'pipe', 'pipe'],
32
+ ...(command.input !== undefined ? { input: command.input } : {}),
33
+ });
34
+ if (result.error)
35
+ throw new Error(what + ' could not run: ' + ghOutputText(result.error.message, token));
36
+ if (result.status !== 0)
37
+ throw new Error(what + ' failed: ' + ghOutputText(String(result.stderr ?? '') || String(result.stdout ?? ''), token));
38
+ }
39
+ /** gh logs in with the token (stdin) in the sandbox-only folder, then https git to github.com asks gh. */
40
+ export function loginGithub(token, env) {
41
+ if (dryRun) {
42
+ log('(dry run) would log gh in with the GitHub token from your PC');
43
+ return;
44
+ }
45
+ const gh = ghExePath();
46
+ const dir = deliveredGhConfigDir();
47
+ if (!exists(gh) || env.GH_CONFIG_DIR !== dir) {
48
+ // Gone again, so gh set up later (a project added afterwards) keeps the login you make in the sandbox.
49
+ fs.rmSync(dir, { recursive: true, force: true });
50
+ log('A GitHub token arrived, but gh is not set up in this sandbox (toolchains.cli.gh), so it is not used.');
51
+ return;
52
+ }
53
+ try {
54
+ runGh(ghLoginCommand(gh, dir, token, env), token, 'gh auth login');
55
+ runGh(ghSetupGitCommand(gh, dir, env), token, 'gh auth setup-git');
56
+ }
57
+ catch (e) {
58
+ // gh goes back to the persistent folder (and a `gh auth login` you make inside is kept), for every session.
59
+ fs.rmSync(dir, { recursive: true, force: true });
60
+ env.GH_CONFIG_DIR = mkdirp(ghConfigDir());
61
+ addMachineEnvironment({ GH_CONFIG_DIR: env.GH_CONFIG_DIR }, []);
62
+ throw e;
63
+ }
64
+ log('gh and https git to github.com use the GitHub token from your PC (github.shareToken); the agent can read it.');
65
+ }
@@ -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]);
@@ -103,6 +129,15 @@ const activate = async (version) => {
103
129
  fs.rmSync(NVM_SYMLINK, { recursive: true, force: true });
104
130
  fs.symlinkSync(path.join(NVM_HOME, 'v' + version), NVM_SYMLINK, 'junction');
105
131
  };
132
+ /**
133
+ * Playwright works in every sandbox without setup: the browsers `npx playwright install` downloads go in the tools
134
+ * folder, so they last between sandboxes, and its MCP server (and `channel: 'msedge'` in tests) uses Edge, the one
135
+ * browser Windows Sandbox always has (Chrome is not there).
136
+ */
137
+ const PLAYWRIGHT_VARS = () => ({
138
+ PLAYWRIGHT_BROWSERS_PATH: tool('ms-playwright'),
139
+ PLAYWRIGHT_MCP_BROWSER: 'msedge',
140
+ });
106
141
  export async function setupNode(env, versions, config) {
107
142
  if (dryRun) {
108
143
  log('(dry run) would provision nvm and Node ' + versions.node.spec + ' (from ' + versions.node.source + ')');
@@ -136,7 +171,7 @@ export async function setupNode(env, versions, config) {
136
171
  warn('Node ' + version + ' from this PC: ' + errMsg(e));
137
172
  }
138
173
  }
139
- Object.assign(env.vars, { NVM_HOME, NVM_SYMLINK });
174
+ Object.assign(env.vars, { NVM_HOME, NVM_SYMLINK, ...PLAYWRIGHT_VARS() });
140
175
  // At boot, nvm's symlink selects the version. A project added later must not switch it under the sessions already
141
176
  // running: its own version's folder goes first on its sessions' PATH instead.
142
177
  if (duringSession())
@@ -146,6 +181,7 @@ export async function setupNode(env, versions, config) {
146
181
  env.prepend.push(NVM_HOME, NVM_SYMLINK);
147
182
  }
148
183
  env.summary.push('node: ' + main + ' (from ' + versions.node.source + ', switch with nvm use)');
184
+ env.summary.push("playwright: ready (Edge; browsers it downloads are kept; in tests use channel: 'msedge')");
149
185
  await packageManagers(env, config, main);
150
186
  }
151
187
  /** Global npm packages (per Node version, like nvm keeps them) and pnpm/yarn through corepack. */
@@ -7,7 +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
+ import { cargoJobs, linkSlotsFor } from '../../../shared/memory/index.js';
11
11
  /**
12
12
  * Where cargo builds: shared with the PC's target\, one on the sandbox's disk, or a path. "separate" sets no variable:
13
13
  * the cargo shim (sandbox/csharp/CargoShim.cs) gives every checkout its own target\sandbox, so git worktrees of several
@@ -41,7 +41,7 @@ export function linkGate(env, rust, host) {
41
41
  const gate = path.join(mkdirp(tool('shims')), 'linkgate.exe');
42
42
  fs.copyFileSync(ensureCompiled('linkgate'), gate);
43
43
  const memoryGb = os.totalmem() / 1024 ** 3;
44
- const slots = rust.linkSlots === 'auto' ? Math.max(2, Math.min(4, Math.floor(memoryGb / 8))) : rust.linkSlots;
44
+ const slots = rust.linkSlots === 'auto' ? linkSlotsFor(memoryGb) : rust.linkSlots;
45
45
  env.vars.AGENT_LINK_SLOTS = String(slots);
46
46
  env.vars.AGENT_REAL_LINKER = gcc;
47
47
  env.vars.CARGO_TARGET_X86_64_PC_WINDOWS_GNU_LINKER = gate;
@@ -3,6 +3,14 @@ import { type CliSettings } from '../catalogue.js';
3
3
  import { type ToolEnv } from '../tool-env.js';
4
4
  /** gh keeps `gh auth login` here, under the agent state (persists like Claude's login; never copied from the PC). */
5
5
  export declare function ghConfigDir(): string;
6
+ /**
7
+ * gh's folder for the token the host handed over (github.shareToken): on the sandbox's own disk, so it is gone with the
8
+ * sandbox and never reaches the persistent state other sandboxes and projects share. The boot creates it only when a
9
+ * token arrived; while it exists, every gh setup (later projects too) points GH_CONFIG_DIR at it.
10
+ */
11
+ export declare function deliveredGhConfigDir(): string;
12
+ /** gh.exe where the catalogue installs it. */
13
+ export declare function ghExePath(): string;
6
14
  /**
7
15
  * Installs the tools toolchains.cli asks for. Settings still "auto" here (normally the host decides) use the same
8
16
  * rules on the workspace as the host would. The gh rule is enforced here again, whatever the host sent.
@@ -2,7 +2,7 @@
2
2
  // `openssl` shim onto the copy Git for Windows already ships.
3
3
  import fs from 'node:fs';
4
4
  import path from 'node:path';
5
- import { dryRun } from '../../runtime/paths.js';
5
+ import { dryRun, localDir } 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 { stateDir } from '../../runtime/session.js';
@@ -16,9 +16,23 @@ import { ensureCompiled } from '../../runtime/csc.js';
16
16
  export function ghConfigDir() {
17
17
  return path.join(stateDir, 'gh');
18
18
  }
19
+ /**
20
+ * gh's folder for the token the host handed over (github.shareToken): on the sandbox's own disk, so it is gone with the
21
+ * sandbox and never reaches the persistent state other sandboxes and projects share. The boot creates it only when a
22
+ * token arrived; while it exists, every gh setup (later projects too) points GH_CONFIG_DIR at it.
23
+ */
24
+ export function deliveredGhConfigDir() {
25
+ return path.join(localDir, 'gh-token');
26
+ }
27
+ /** gh.exe where the catalogue installs it. */
28
+ export function ghExePath() {
29
+ const entry = CLI_TOOLS.find((t) => t.name === 'gh');
30
+ const dir = tool('cli', 'gh');
31
+ return path.join(entry?.subdir ? path.join(dir, entry.subdir) : dir, entry?.exe ?? 'gh.exe');
32
+ }
19
33
  const toolVars = {
20
34
  gh: () => ({
21
- GH_CONFIG_DIR: mkdirp(ghConfigDir()),
35
+ GH_CONFIG_DIR: exists(deliveredGhConfigDir()) ? deliveredGhConfigDir() : mkdirp(ghConfigDir()),
22
36
  GH_NO_UPDATE_NOTIFIER: '1',
23
37
  GH_NO_EXTENSION_UPDATE_NOTIFIER: '1',
24
38
  }),
@@ -5,5 +5,10 @@ export declare function openvpnDownload(arch?: string): {
5
5
  sha256: string;
6
6
  url: string;
7
7
  };
8
+ /**
9
+ * Windows Sandbox cannot answer WMI questions about services (a class whose provider is missing), and installers that
10
+ * ask (OpenVPN's MSI does) fail with 1603: the dead class is removed first (repair-wmi-services.ps1). Never throws.
11
+ */
12
+ export declare function repairServiceQueries(): void;
8
13
  /** Installs OpenVPN unless it is there; returns the path of openvpn.exe. Throws with the reason when it cannot. */
9
14
  export declare function ensureOpenVpn(): Promise<string>;
@@ -8,7 +8,8 @@ import { downloader, spawnAsync } from '../../shared/download/index.js';
8
8
  import { SYSTEM } from '../../shared/system.js';
9
9
  import { dryRun, isWin, toolsDir } from '../runtime/paths.js';
10
10
  import { exists, mkdirp } from '../runtime/fs.js';
11
- import { log } from '../runtime/log.js';
11
+ import { log, warn } from '../runtime/log.js';
12
+ import { scriptOutput } from '../runtime/process.js';
12
13
  import { OPENVPN_MSI } from '../../shared/vpn/download.js';
13
14
  /** Where the MSI puts the program. */
14
15
  export function openvpnExe(env = process.env) {
@@ -18,6 +19,17 @@ export function openvpnDownload(arch = process.arch) {
18
19
  const entry = arch === 'arm64' ? OPENVPN_MSI.arm64 : OPENVPN_MSI.x64;
19
20
  return { ...entry, url: OPENVPN_MSI.base + entry.file };
20
21
  }
22
+ /**
23
+ * Windows Sandbox cannot answer WMI questions about services (a class whose provider is missing), and installers that
24
+ * ask (OpenVPN's MSI does) fail with 1603: the dead class is removed first (repair-wmi-services.ps1). Never throws.
25
+ */
26
+ export function repairServiceQueries() {
27
+ const did = scriptOutput('repair-wmi-services.ps1');
28
+ if (did === 'removed')
29
+ log('VPN: repaired the Windows service queries installers use (a Windows Sandbox gap).');
30
+ else if (did !== 'absent' && did !== 'kept')
31
+ warn('VPN: could not check the Windows service queries installers use.');
32
+ }
21
33
  const hasHash = (file, sha256) => crypto.createHash('sha256').update(fs.readFileSync(file)).digest('hex') === sha256;
22
34
  /** Installs OpenVPN unless it is there; returns the path of openvpn.exe. Throws with the reason when it cannot. */
23
35
  export async function ensureOpenVpn() {
@@ -33,6 +45,7 @@ export async function ensureOpenVpn() {
33
45
  log('VPN: downloading OpenVPN ' + OPENVPN_MSI.version + ' (about 6 MB)');
34
46
  await downloader.file(dl.url, msi, dl.sha256);
35
47
  }
48
+ repairServiceQueries();
36
49
  log('VPN: installing OpenVPN');
37
50
  const logFile = path.join(toolsDir, 'downloads', 'openvpn-install.log');
38
51
  const code = await spawnAsync(SYSTEM.msiexec(), ['/i', msi, '/qn', '/norestart', '/l*v', logFile], {
@@ -4,6 +4,7 @@
4
4
  // warns.
5
5
  import dns from 'node:dns';
6
6
  import fs from 'node:fs';
7
+ import net from 'node:net';
7
8
  import os from 'node:os';
8
9
  import path from 'node:path';
9
10
  import { now } from '../../shared/channel/run-dir.js';
@@ -13,7 +14,7 @@ import { assertSafeWireguard, renderWireguard, routesEverything, wireguardEndpoi
13
14
  import { isIPv4 } from '../../shared/network/firewall-rules.js';
14
15
  import { killSwitchCommands, tunnelCommands } from '../../shared/vpn/kill-switch.js';
15
16
  import { errMsg } from '../../core/util/errors.js';
16
- import { run as netsh } from '../network/firewall.js';
17
+ import { longPath, run as netsh } from '../network/firewall.js';
17
18
  import { dryRun, isWin, launchDir, run } from '../runtime/paths.js';
18
19
  import { log, warn } from '../runtime/log.js';
19
20
  import { session } from '../runtime/session.js';
@@ -74,6 +75,27 @@ async function publicAddress() {
74
75
  }
75
76
  }
76
77
  const hasAdapter = (ip) => Object.values(os.networkInterfaces()).some((list) => (list ?? []).some((i) => i.address === ip));
78
+ /** Well-known public addresses a direct connection is tried to, to see that the kill switch holds. */
79
+ const PROBE_TARGETS = ['1.1.1.1', '8.8.8.8', '9.9.9.9'];
80
+ const PROBE_TIMEOUT_MS = 3000;
81
+ function connects(ip, port) {
82
+ return new Promise((resolve) => {
83
+ const socket = net.connect({ host: ip, port });
84
+ const done = (ok) => {
85
+ socket.destroy();
86
+ resolve(ok);
87
+ };
88
+ socket.setTimeout(PROBE_TIMEOUT_MS, () => done(false));
89
+ socket.once('connect', () => done(true));
90
+ socket.once('error', () => done(false));
91
+ });
92
+ }
93
+ /** True when a direct connection out (before the tunnel exists) fails: the kill switch holds. */
94
+ async function directConnectionBlocked(servers) {
95
+ const targets = PROBE_TARGETS.filter((ip) => !servers.some((s) => s.ip === ip));
96
+ const results = await Promise.all(targets.map((ip) => connects(ip, 443)));
97
+ return !results.some(Boolean);
98
+ }
77
99
  /** The whole step. Returns true when the tunnel is up. */
78
100
  export async function setupVpn(secrets, ptyHostExe) {
79
101
  const vpn = session.vpn;
@@ -81,11 +103,24 @@ export async function setupVpn(secrets, ptyHostExe) {
81
103
  return true;
82
104
  const killSwitch = vpn.killSwitch;
83
105
  let blocked = false;
84
- const block = (servers) => {
85
- if (isWin && !dryRun)
86
- netsh(killSwitchCommands(servers, ptyHostExe));
87
- blocked = true;
88
- log('VPN: kill switch on: nothing leaves the sandbox except through the tunnel.');
106
+ /** A direct connection was tried after the kill switch and failed: only then is the sandbox said to be offline. */
107
+ let held = false;
108
+ const block = async (servers) => {
109
+ if (!isWin || dryRun) {
110
+ blocked = held = true;
111
+ return;
112
+ }
113
+ try {
114
+ netsh(killSwitchCommands(servers, ptyHostExe ? longPath(ptyHostExe) : null));
115
+ }
116
+ finally {
117
+ blocked = true;
118
+ held = await directConnectionBlocked(servers);
119
+ }
120
+ if (held)
121
+ log('VPN: kill switch on: nothing leaves the sandbox except through the tunnel (checked).');
122
+ else
123
+ warn('VPN: the kill switch did not hold: a direct connection still got out. If the tunnel drops, traffic leaves through your normal connection.');
89
124
  };
90
125
  setVpnStatus('starting', 'Starting the VPN');
91
126
  try {
@@ -103,7 +138,7 @@ export async function setupVpn(secrets, ptyHostExe) {
103
138
  if (killSwitch && !routesEverything(config))
104
139
  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
140
  if (killSwitch)
106
- block(servers);
141
+ await block(servers);
107
142
  if (dryRun || !isWin) {
108
143
  log('(dry run) would start the WireGuard tunnel ' + vpn.server);
109
144
  setVpnStatus('off', 'dry run');
@@ -129,7 +164,7 @@ export async function setupVpn(secrets, ptyHostExe) {
129
164
  if (servers.length === 0)
130
165
  throw new Error('the VPN server addresses could not be found');
131
166
  if (killSwitch)
132
- block(servers);
167
+ await block(servers);
133
168
  if (dryRun || !isWin) {
134
169
  log('(dry run) would start the VPN tunnel to ' + vpn.server);
135
170
  setVpnStatus('off', 'dry run');
@@ -149,7 +184,10 @@ export async function setupVpn(secrets, ptyHostExe) {
149
184
  throw new Error('the tunnel reported it was up, but no network adapter with its address appeared');
150
185
  const publicIp = await publicAddress();
151
186
  log('VPN: up' + (publicIp ? ' (the sandbox appears as ' + publicIp + ')' : '') + '.');
152
- setVpnStatus('up', 'The tunnel is up', publicIp ? { publicIp } : {});
187
+ setVpnStatus('up', 'The tunnel is up', {
188
+ ...(publicIp ? { publicIp } : {}),
189
+ ...(killSwitch ? { killSwitchHeld: held } : {}),
190
+ });
153
191
  return true;
154
192
  }
155
193
  catch (e) {
@@ -157,18 +195,19 @@ export async function setupVpn(secrets, ptyHostExe) {
157
195
  // Without the kill switch the sandbox keeps its normal connection: the rest of the boot goes on, with a warning.
158
196
  try {
159
197
  if (killSwitch && !blocked)
160
- block([]);
198
+ await block([]);
161
199
  }
162
200
  catch (blockError) {
163
201
  warn('VPN: could not close the network after the failure: ' + errMsg(blockError));
164
202
  }
165
- const text = 'VPN failed: ' +
166
- reason +
167
- (killSwitch
203
+ // "No internet" is said only when a direct connection was tried and failed.
204
+ const after = !killSwitch
205
+ ? ' The kill switch is off, so this sandbox is using your normal connection.'
206
+ : held
168
207
  ? ' The kill switch is on, so this sandbox has NO internet access.'
169
- : ' The kill switch is off, so this sandbox is using your normal connection.');
170
- warn(text);
171
- setVpnStatus('failed', reason);
208
+ : ' The kill switch could NOT close the network, so this sandbox is ONLINE through your normal connection, without the VPN.';
209
+ warn('VPN failed: ' + reason + after);
210
+ setVpnStatus('failed', reason, killSwitch ? { killSwitchHeld: held } : {});
172
211
  return false;
173
212
  }
174
213
  }
@@ -14,6 +14,7 @@ import { handshakeDone, serviceRunning } from '../../shared/vpn/wireguard.js';
14
14
  import { dryRun, isWin, toolsDir } from '../runtime/paths.js';
15
15
  import { exists, mkdirp } from '../runtime/fs.js';
16
16
  import { log } from '../runtime/log.js';
17
+ import { repairServiceQueries } from './openvpn.js';
17
18
  /** The tunnel's name: the config file is sandbox.conf and the Windows service WireGuardTunnel$sandbox. */
18
19
  export const WIREGUARD_TUNNEL = 'sandbox';
19
20
  export function wireguardDir(env = process.env) {
@@ -41,6 +42,7 @@ export async function ensureWireguard() {
41
42
  log('VPN: downloading WireGuard ' + WIREGUARD_MSI.version + ' (about 3 MB)');
42
43
  await downloader.file(dl.url, msi, dl.sha256);
43
44
  }
45
+ repairServiceQueries();
44
46
  log('VPN: installing WireGuard');
45
47
  const logFile = path.join(toolsDir, 'downloads', 'wireguard-install.log');
46
48
  // DO_NOT_LAUNCH: the installer would otherwise start the manager window.
@@ -18,4 +18,5 @@ export declare function findUnwatchedSession(dataPath: string, harness: string,
18
18
  /** Bridges this terminal to a session. Returns the agent's exit code, or 0 when the user detaches. */
19
19
  export declare function attachTerminal(session: SessionStatus, o?: {
20
20
  clipboardWrites?: boolean;
21
+ confirmCtrlC?: boolean;
21
22
  }): Promise<number>;
@@ -74,6 +74,7 @@ export async function attachTerminal(session, o = {}) {
74
74
  const result = await runTerminalClient(socket, session.attach.token, {
75
75
  detachKey: DETACH_KEY,
76
76
  clipboardWrites: o.clipboardWrites,
77
+ confirmCtrlC: o.confirmCtrlC,
77
78
  });
78
79
  switch (result.reason) {
79
80
  case 'detach':
@@ -47,7 +47,7 @@ export declare class SandboxChannel {
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
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;
50
+ pressure(reason: string, seconds: number, level?: 'slow' | 'severe'): void;
51
51
  ping(): Promise<GuestResponse>;
52
52
  runningSessions(): SessionStatus[];
53
53
  /** Running sessions of the sandbox that is running now (its launch id), newest last, capped. */
@@ -180,8 +180,8 @@ export class SandboxChannel {
180
180
  return this.waitForResponse(requestId, 30000);
181
181
  }
182
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() });
183
+ pressure(reason, seconds, level = 'slow') {
184
+ this.send({ kind: 'pressure', id: newId(), reason, seconds, level, createdAt: now() });
185
185
  }
186
186
  async ping() {
187
187
  const requestId = newId();
@@ -19,6 +19,7 @@ export interface HcsProbes {
19
19
  imageStores(): readonly ImageStore[];
20
20
  }
21
21
  export declare function checkHcs(p: HcsProbes): EngineCheck;
22
+ export declare function system32Dir(): string;
22
23
  /** The groups of a token from `whoami /groups /fo csv /nh`: name, type, SID, attributes per line. */
23
24
  export declare function parseWhoamiGroups(csv: string): TokenGroups;
24
25
  /** The real probes: sc.exe query, whoami, file checks. They only read. */
@@ -45,7 +45,7 @@ export function checkHcs(p) {
45
45
  need('No Windows Server Core (ltsc2022) image is on this PC. ' + image.reason, 'Pull one with Docker Desktop (Windows containers mode): docker pull mcr.microsoft.com/windows/servercore:ltsc2022 - or put your own in a folder with an image.json and set AGENT_SANDBOX_HCS_IMAGE to it. This tool does not download images in this alpha.');
46
46
  return { available: reasons.length === 0, reasons, fixes };
47
47
  }
48
- function system32Dir() {
48
+ export function system32Dir() {
49
49
  return path.dirname(system32('x'));
50
50
  }
51
51
  /** The groups of a token from `whoami /groups /fo csv /nh`: name, type, SID, attributes per line. */
@@ -32,7 +32,7 @@ export declare class HcsEngine implements SandboxEngine {
32
32
  private helper;
33
33
  start(plan: EnginePlan): Promise<RunningSandbox>;
34
34
  private tryTerminate;
35
- /** Ours: the compute systems the helper lists with our owner name. */
35
+ /** Ours: the compute systems the helper lists with our owner name and this Windows user's id prefix. */
36
36
  private ours;
37
37
  running(): Promise<boolean>;
38
38
  stop(): Promise<void>;