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,9 +4,8 @@ import fs from 'node:fs';
4
4
  import path from 'node:path';
5
5
  import { Draft } from '../cli/menu/settings/draft.js';
6
6
  import { ConfigFile } from '../app/features/settings/index.js';
7
- import { AppsModel } from '../cli/menu/apps.js';
8
7
  import { accountsOptions } from '../cli/menu/pages/accounts-page.js';
9
- import { appsOptions } from '../cli/menu/pages/apps-page.js';
8
+ import { appsOptions, stageTicks } from '../cli/menu/pages/apps-page.js';
10
9
  import { maintenanceOptions } from '../cli/menu/pages/maintenance-page.js';
11
10
  import { sandboxOptions } from '../cli/menu/pages/sandbox-page.js';
12
11
  import { sessionsOptions } from '../cli/menu/pages/sessions-page.js';
@@ -23,7 +22,7 @@ export const SIZES = [
23
22
  ];
24
23
  const NOW = Date.now();
25
24
  const API_FOLDER = 'C:\\Projects\\api';
26
- /** The Data folder row of General (in the menu it comes from data-folder.ts). */
25
+ /** The Data folder row of General (in the menu it comes from settings-page.ts). */
27
26
  const DATA_FOLDER_ROW = [
28
27
  {
29
28
  value: '@data-folder',
@@ -34,18 +33,25 @@ const DATA_FOLDER_ROW = [
34
33
  },
35
34
  ];
36
35
  const ACCOUNTS = {
36
+ agent: 'claude',
37
37
  agents: [
38
- { name: 'claude', kept: true, canSavePc: true },
39
- { name: 'codex', kept: true, canSavePc: true },
40
- { name: 'opencode', kept: false, canSavePc: false },
38
+ {
39
+ name: 'claude',
40
+ kept: true,
41
+ canSavePc: true,
42
+ accounts: [
43
+ { name: 'work', email: 'me@work.example', isDefault: true, isActive: true, savedAt: '2026-10-13T09:30:00Z' },
44
+ { name: 'home', email: null, isDefault: false, isActive: false, savedAt: '2026-10-01T18:00:00Z' },
45
+ ],
46
+ },
47
+ {
48
+ name: 'codex',
49
+ kept: true,
50
+ canSavePc: true,
51
+ accounts: [{ name: 'main', email: null, isDefault: true, isActive: false, savedAt: '2026-10-12T08:00:00Z' }],
52
+ },
53
+ { name: 'opencode', kept: false, canSavePc: false, accounts: [] },
41
54
  ],
42
- rows: {
43
- claude: [
44
- { name: 'work', email: 'me@work.example', isDefault: true, isActive: true, savedAt: '2026-10-13T09:30:00Z' },
45
- { name: 'home', email: null, isDefault: false, isActive: false, savedAt: '2026-10-01T18:00:00Z' },
46
- ],
47
- codex: [{ name: 'main', email: null, isDefault: true, isActive: false, savedAt: '2026-10-12T08:00:00Z' }],
48
- },
49
55
  };
50
56
  const SESSIONS = [
51
57
  {
@@ -164,54 +170,163 @@ export function renderScreens(dir) {
164
170
  };
165
171
  const maintenance = {
166
172
  data: {
173
+ dataPath: 'C:\\AgentData',
174
+ storageRoot: 'C:\\ProgramData\\AgentSandbox\\users\\me',
175
+ totalBytes: 8.9e9,
176
+ total: '8.9 GB',
177
+ storageShare: '<- sandbox storage (8.9 GB of it)',
178
+ areaTotals: { tools: '7.1 GB', state: '1.8 GB', host: '4.0 KB' },
167
179
  entries: [
168
- { area: 'tools', name: 'rust', path: 'C:\\AgentData\\tools\\rust', bytes: 4.6e9 },
169
- { area: 'tools', name: 'vscode', path: 'C:\\AgentData\\tools\\vscode', bytes: 2.5e9 },
170
- { area: 'state', name: 'state/claude', path: 'C:\\AgentData\\state\\claude', bytes: 1.8e9 },
171
- { area: 'host', name: 'accounts', path: 'C:\\Users\\me\\AppData\\Local\\AgentSandbox\\accounts', bytes: 4096 },
180
+ {
181
+ area: 'tools',
182
+ name: 'rust',
183
+ path: 'C:\\AgentData\\tools\\rust',
184
+ bytes: 4.6e9,
185
+ size: '4.6 GB',
186
+ target: 'tools/rust',
187
+ },
188
+ {
189
+ area: 'tools',
190
+ name: 'vscode',
191
+ path: 'C:\\AgentData\\tools\\vscode',
192
+ bytes: 2.5e9,
193
+ size: '2.5 GB',
194
+ target: 'tools/vscode',
195
+ },
196
+ {
197
+ area: 'state',
198
+ name: 'state/claude',
199
+ path: 'C:\\AgentData\\state\\claude',
200
+ bytes: 1.8e9,
201
+ size: '1.8 GB',
202
+ target: 'state/claude',
203
+ },
204
+ {
205
+ area: 'host',
206
+ name: 'accounts',
207
+ path: 'C:\\Users\\me\\AppData\\Local\\AgentSandbox\\accounts',
208
+ bytes: 4096,
209
+ size: '4.0 KB',
210
+ target: null,
211
+ },
212
+ ],
213
+ disks: [
214
+ {
215
+ drive: 'C:\\',
216
+ totalBytes: 40e9,
217
+ freeBytes: 22e9,
218
+ usedBytes: 18e9,
219
+ holdsStorage: true,
220
+ used: '18 GB',
221
+ usedPercent: 45,
222
+ free: '22 GB',
223
+ total: '40 GB',
224
+ },
172
225
  ],
173
- disks: [{ drive: 'C:\\', totalBytes: 40e9, freeBytes: 22e9, usedBytes: 18e9, holdsStorage: true }],
174
- total: 8.9e9,
175
226
  },
176
- logs: [
177
- { name: 'guest-2026-10-14.log', path: 'x', bytes: 120000, modified: NOW - 60000 },
178
- { name: 'daemon-2026-10-14.log', path: 'y', bytes: 8000, modified: NOW - 600000 },
179
- ],
180
- doctor: [
181
- ['Windows Sandbox', 'available'],
182
- ['Config file', 'C:\\Users\\me\\.agent-sandbox\\config.json'],
183
- ],
184
- repair: [
185
- { name: 'Config file', status: 'ok', detail: 'valid' },
186
- { name: 'Stale run folders', status: 'fixed', detail: 'removed 2' },
187
- { name: 'Toolchain cache', status: 'needs-you', detail: 'close the sandbox first' },
188
- ],
189
- settings: { pending: ['2026-10-unpin-config-defaults: stop pinning old defaults in your config.json'] },
190
- dataFolder: {
191
- label: 'Data folder',
192
- hint: 'C:\\ProgramData\\AgentSandbox\\users\\me',
193
- description: 'Where the sandbox keeps its tools, databases and agent state. Enter changes it.',
227
+ logs: {
228
+ folder: 'C:\\AgentData\\logs',
229
+ files: [
230
+ {
231
+ name: 'guest-2026-10-14.log',
232
+ path: 'C:\\AgentData\\logs\\guest-2026-10-14.log',
233
+ bytes: 120000,
234
+ modified: NOW - 60000,
235
+ },
236
+ {
237
+ name: 'daemon-2026-10-14.log',
238
+ path: 'C:\\AgentData\\logs\\daemon-2026-10-14.log',
239
+ bytes: 8000,
240
+ modified: NOW - 600000,
241
+ },
242
+ ],
243
+ crash: true,
244
+ },
245
+ health: {
246
+ last: {
247
+ at: new Date(NOW - 180000).toISOString(),
248
+ mode: 'check',
249
+ checks: [
250
+ { id: 'config', group: 'Settings', name: 'Config file', status: 'ok', detail: 'valid', fixable: false },
251
+ {
252
+ id: 'stale',
253
+ group: 'Data folder',
254
+ name: 'Stale run folders',
255
+ status: 'fixed',
256
+ detail: 'removed 2',
257
+ fixable: false,
258
+ },
259
+ {
260
+ id: 'cache',
261
+ group: 'Sandbox',
262
+ name: 'Toolchain cache',
263
+ status: 'needs-you',
264
+ detail: 'close the sandbox first',
265
+ fixable: false,
266
+ },
267
+ ],
268
+ summary: { total: 3, ok: 1, fixed: 1, warn: 0, needsYou: 1, skipped: 0, fixable: 0 },
269
+ details: [
270
+ ['Windows Sandbox', 'available'],
271
+ ['Config file', 'C:\\Users\\me\\.agent-sandbox\\config.json'],
272
+ ],
273
+ setup: {
274
+ checks: [
275
+ { name: 'Windows edition', status: 'ok', detail: 'Pro' },
276
+ { name: 'Windows Sandbox feature', status: 'action', detail: 'turned off' },
277
+ ],
278
+ canEnable: true,
279
+ error: null,
280
+ },
281
+ log: null,
282
+ },
283
+ },
284
+ storage: {
285
+ show: {
286
+ root: 'C:\\ProgramData\\AgentSandbox\\users\\me',
287
+ exists: true,
288
+ holdsData: true,
289
+ measured: null,
290
+ size: 'not measured yet',
291
+ defaultRoot: 'C:\\ProgramData\\AgentSandbox\\users\\me',
292
+ defaultInUse: true,
293
+ otherFoldersWithData: [],
294
+ },
295
+ contained: {
296
+ setting: 'off',
297
+ maxGb: 60,
298
+ available: true,
299
+ reasons: [],
300
+ fixes: [],
301
+ image: 'C:\\ProgramData\\AgentSandbox\\users\\me\\contained\\agent.vhdx',
302
+ imageExists: false,
303
+ imageBytes: null,
304
+ mountFolder: 'C:\\ProgramData\\AgentSandbox\\users\\me\\mount',
305
+ recordedMounted: false,
306
+ holders: 0,
307
+ mountedNow: null,
308
+ },
194
309
  },
195
310
  updates: {
196
- managed: true,
197
- version: '1.4.3',
198
- scope: 'user',
199
- autoUpdate: true,
200
- autoOffText: null,
201
- state: {
311
+ status: {
312
+ managed: true,
313
+ version: '1.4.3',
314
+ scope: 'user',
315
+ autoUpdate: true,
316
+ autoOffText: null,
202
317
  previous: '1.4.2',
203
- failed: { version: '1.4.4', at: '2026-10-13T03:00:00Z', reason: 'it did not start' },
318
+ failed: { version: '1.4.4', reason: 'it did not start' },
319
+ npmCommand: 'npm install -g windows-development-sandbox',
320
+ error: null,
321
+ settings: {
322
+ pending: ['2026-10-unpin-config-defaults: stop pinning old defaults in your config.json'],
323
+ error: null,
324
+ },
204
325
  },
326
+ latest: '1.5.0',
205
327
  },
206
- latest: '1.5.0',
207
- setup: {
208
- facts: {},
209
- canEnable: true,
210
- checks: [
211
- { name: 'Windows edition', status: 'ok', detail: 'Pro' },
212
- { name: 'Windows Sandbox feature', status: 'action', detail: 'turned off' },
213
- ],
214
- },
328
+ engine: null,
329
+ web: { running: true, port: 47800, address: 'http://127.0.0.1:47800/' },
215
330
  more: [
216
331
  { name: 'sync', summary: 'sync history now' },
217
332
  { name: 'audit', summary: 'show what the sandbox did' },
@@ -225,9 +340,9 @@ export function renderScreens(dir) {
225
340
  draft.stage(user, harness.key, 'codex');
226
341
  if (flag)
227
342
  draft.stage(user, flag.key, !user.userConfig()[flag.key[0]]);
228
- const apps = new AppsModel(user, path.join(dir, 'data'));
343
+ const appsData = path.join(dir, 'data');
229
344
  const appsDraft = new Draft();
230
- apps.stageTicks(appsDraft, ['git-gui']);
345
+ stageTicks(user, appsDraft, ['git-gui']);
231
346
  const out = [];
232
347
  for (const size of SIZES) {
233
348
  const agentsModel = {
@@ -249,7 +364,7 @@ export function renderScreens(dir) {
249
364
  out.push(block('3 Sandbox (stopped) · ' + category, draw(2, { ...sandboxOptions(stopped), startCategory: category }, size), size));
250
365
  for (const category of ['status', 'projects', 'databases'])
251
366
  out.push(block('3 Sandbox · ' + category, draw(2, { ...sandboxOptions(sandbox), startCategory: category }, size, status), size));
252
- out.push(block('4 Apps (1 unsaved)', draw(3, appsOptions(apps, user, appsDraft, 'user'), size, status, appsDraft.count()), size));
367
+ out.push(block('4 Apps (1 unsaved)', draw(3, appsOptions(user, appsData, appsDraft, 'user'), size, status, appsDraft.count()), size));
253
368
  for (const category of CATEGORIES)
254
369
  out.push(block('5 Settings · ' + category.label + ' (2 unsaved)', draw(4, { ...settingsOptions(files, 'user', draft, DATA_FOLDER_ROW), startCategory: category.id }, size, status, draft.count()), size));
255
370
  // The leave dialog over the settings screen.
@@ -5,19 +5,21 @@ import fs from 'node:fs';
5
5
  import { CATEGORIES, ConfigFile, SETTINGS_ELSEWHERE } from '../app/features/settings/index.js';
6
6
  import { Draft } from '../cli/menu/settings/draft.js';
7
7
  import { z } from 'zod';
8
- import { AddAgent, agentNameProblem, commandFromPackage, makeDefaultAgent, newAgent, removeAgent, } from '../cli/menu/add-agent.js';
9
- import { APP_GROUPS, AppsModel, REQUEST_APP, requestAppUrl } from '../cli/menu/apps.js';
8
+ import { addedAgents, agentNameProblem, commandFromPackage, newAgent } from '../app/features/agents/index.js';
9
+ import { APP_GROUPS, appRows, REQUEST_APP, requestAppUrl } from '../app/features/apps/index.js';
10
+ import { appsCategories, appsItems } from '../cli/menu/pages/apps-page.js';
10
11
  import { readMenuView, saveMenuView } from '../cli/menu/memory.js';
11
12
  import path from 'node:path';
12
13
  import { argPathKind, optionLabel, optionPathKind, plainDescription } from '../cli/menu/command-fields.js';
13
14
  import { CommandForm } from '../cli/menu/command-form.js';
14
15
  import { COMMANDS, commandSpec } from '../cli/manifest.js';
15
16
  import { accountsOptions } from '../cli/menu/pages/accounts-page.js';
16
- import { addedAgents, agentActions, agentCategories, agentsOptions, hostAgentsToShow, startArgv, } from '../cli/menu/pages/agents-page.js';
17
+ import { agentCategories, agentsHaveStagedChanges, agentsOptions, hostAgentsToShow, startArgv, } from '../cli/menu/pages/agents-page.js';
17
18
  import { ListPage } from '../cli/menu/pages/list-page.js';
18
19
  import { sandboxOptions } from '../cli/menu/pages/sandbox-page.js';
19
20
  import { sessionsOptions } from '../cli/menu/pages/sessions-page.js';
20
- import { pathKindOf, SchemaNode } from '../app/kernel/index.js';
21
+ import { fakeIO, pathKindOf, SchemaNode } from '../app/kernel/index.js';
22
+ import { runDefinition } from '../app/registry.js';
21
23
  import { numberProblem, ValueEditor } from '../cli/menu/settings/value-editor.js';
22
24
  import { APP_CATEGORIES } from '../shared/apps/index.js';
23
25
  import { settingsOf, settingsOptions } from '../cli/menu/pages/settings-page.js';
@@ -266,15 +268,11 @@ export const menuSuite = {
266
268
  ], 'a new agent name is checked (letters, no duplicates); the command is guessed from the package');
267
269
  const agentFile = ConfigFile.user(t.at('agents', 'config.json'));
268
270
  t.write(agentFile.file, '{\n // mine\n "defaultHarness": "claude"\n}\n');
269
- const typed = (...answers) => ({
270
- choose: () => Promise.resolve(answers.shift() === 'host' ? 'host' : 'npm'),
271
- text: () => Promise.resolve(answers.shift()),
272
- pickPath: () => Promise.resolve(answers.shift()),
273
- });
274
- const added = await new AddAgent(typed('npm', 'my-agent', '@me/my-agent@latest', 'my-agent'), agentFile, new Draft(), ['claude', 'codex']).run();
271
+ const agentCtx = { loaded: { configPath: agentFile.file } };
272
+ const added = (await runDefinition('agents.add', agentCtx, {}, fakeIO(['npm', 'my-agent', '@me/my-agent@latest', 'my-agent']).io));
275
273
  const saved = agentFile.userConfig().harnesses['my-agent'];
276
- t.check(added?.name === 'my-agent' &&
277
- added.saved &&
274
+ t.check(added.name === 'my-agent' &&
275
+ added.message === 'Added my-agent.' &&
278
276
  saved?.npmPackage === '@me/my-agent@latest' &&
279
277
  saved.npmBin === 'my-agent' &&
280
278
  agentFile.text().includes('// mine') &&
@@ -284,9 +282,7 @@ export const menuSuite = {
284
282
  ...agentFile.valueInFile(['harnesses']),
285
283
  other: newAgent('uv', 'tool', 'tool'),
286
284
  });
287
- const joined = await new AddAgent(typed('host', 'third', 'third.exe'), agentFile, stagedAgents, ['claude']).run();
288
- t.check(joined?.saved === false &&
289
- Object.keys(stagedAgents.value(agentFile, ['harnesses'])).join() === 'my-agent,other,third', 'with unsaved changes to the agents, the new agent joins them instead of being written');
285
+ t.check(agentsHaveStagedChanges(agentFile, stagedAgents) && !agentsHaveStagedChanges(agentFile, new Draft()), 'with unsaved changes to the agents, the menu asks to save them first (the agent operations wait)');
290
286
  t.section('list keys follow the highlighted entry');
291
287
  const keyList = new ListScreen({
292
288
  path: [],
@@ -344,6 +340,30 @@ export const menuSuite = {
344
340
  .body.map((l) => Text.strip(l))
345
341
  .find((l) => l.includes('›'));
346
342
  t.check(marked?.includes('Beta') === true, 'a section opens with the cursor on the entry it asks for');
343
+ class Broken extends ListPage {
344
+ label = 'Broken';
345
+ load() {
346
+ return Promise.resolve();
347
+ }
348
+ options() {
349
+ return { path: [], items: [{ value: 'a', label: 'Alpha' }] };
350
+ }
351
+ act() {
352
+ return Promise.reject(new Error('the action broke'));
353
+ }
354
+ }
355
+ const broken = new Broken(new App());
356
+ await broken.open();
357
+ let survived = true;
358
+ try {
359
+ await broken.handle(press('return'));
360
+ }
361
+ catch {
362
+ survived = false;
363
+ }
364
+ const brokenFrame = broken.render({ width: 100, height: 12 });
365
+ const brokenScreen = Text.strip([...brokenFrame.body, brokenFrame.notice ?? ''].join('\n'));
366
+ t.check(survived && brokenScreen.includes('the action broke'), 'an action that fails shows its problem in the menu instead of closing it', brokenScreen);
347
367
  t.section('proper inputs');
348
368
  const user2 = ConfigFile.user(t.at('inputs', 'config.json'));
349
369
  const leaf = (...key) => {
@@ -549,9 +569,10 @@ export const menuSuite = {
549
569
  hostAgents: [],
550
570
  removable: addedAgents(['claude', 'codex', 'mine']),
551
571
  };
552
- const startActions = agentActions(actionModel);
553
- const keyFor = (key, id) => startActions.find((a) => a.key === key)?.applies?.(id) ?? false;
554
572
  const actionList = agentsOptions(actionModel, 'C:\\code\\api');
573
+ const keyFor = (key, id) => (typeof actionList.actions === 'function' ? actionList.actions('start') : [])
574
+ .find((a) => a.key === key)
575
+ ?.applies?.(id) ?? false;
555
576
  t.check(JSON.stringify(actionModel.removable) === '["mine"]' &&
556
577
  keyFor('d', 'agent:codex') &&
557
578
  !keyFor('d', 'agent:claude') &&
@@ -563,20 +584,15 @@ export const menuSuite = {
563
584
  actionList.actions('options').length === 0, 'Make default shows on any other agent, Remove only on one you added; neither on built-in agents or other rows');
564
585
  const agentsFile = ConfigFile.user(t.at('agents-actions', 'config.json'));
565
586
  t.write(agentsFile.file, '{\n // mine\n "defaultHarness": "claude"\n}\n');
566
- const agentDraft = new Draft();
567
- await new AddAgent({
568
- choose: () => Promise.resolve('npm'),
569
- text: (() => {
570
- const answers = ['mine', '@me/mine@latest', 'mine'];
571
- return () => Promise.resolve(answers.shift());
572
- })(),
573
- }, agentsFile, agentDraft, ['claude']).run();
574
- makeDefaultAgent(agentsFile, agentDraft, 'mine');
587
+ const agentsCtx = { loaded: { configPath: agentsFile.file } };
588
+ const quiet = (answers = []) => fakeIO(answers).io;
589
+ await runDefinition('agents.add', agentsCtx, {}, quiet(['npm', 'mine', '@me/mine@latest', 'mine']));
590
+ await runDefinition('agents.makeDefault', agentsCtx, { name: 'mine' }, quiet());
575
591
  const becameDefault = agentsFile.userConfig().defaultHarness;
576
- await t.throws(() => removeAgent(agentsFile, agentDraft, 'mine'), 'default agent', 'the default agent is not removed');
577
- makeDefaultAgent(agentsFile, agentDraft, 'codex');
578
- removeAgent(agentsFile, agentDraft, 'mine');
579
- await t.throws(() => removeAgent(agentsFile, agentDraft, 'claude'), 'built in', 'a built-in agent is not removed');
592
+ await t.throws(() => runDefinition('agents.remove', agentsCtx, { name: 'mine' }, quiet([true])), 'default agent', 'the default agent is not removed');
593
+ await runDefinition('agents.makeDefault', agentsCtx, { name: 'codex' }, quiet());
594
+ await runDefinition('agents.remove', agentsCtx, { name: 'mine' }, quiet([true]));
595
+ await t.throws(() => runDefinition('agents.remove', agentsCtx, { name: 'claude' }, quiet([true])), 'built in', 'a built-in agent is not removed');
580
596
  t.check(becameDefault === 'mine' &&
581
597
  agentsFile.userConfig().defaultHarness === 'codex' &&
582
598
  !agentsFile.text().includes('mine"') &&
@@ -600,18 +616,19 @@ export const menuSuite = {
600
616
  stoppedItems.some((i) => i.value === 'act:start' && i.category === 'databases') &&
601
617
  stoppedItems.some((i) => i.value === '@add-root') &&
602
618
  !stoppedItems.some((i) => i.value === 'act:stop'), 'with no sandbox running, Sandbox offers to start one (Status and Databases) instead of dead rows');
603
- const noLogins = accountsOptions({ agents: [{ name: 'claude', kept: true, canSavePc: true }], rows: {} });
619
+ const noLogins = accountsOptions({
620
+ agent: 'claude',
621
+ agents: [{ name: 'claude', kept: true, canSavePc: true, accounts: [] }],
622
+ });
604
623
  t.equal(noLogins.items.map((i) => i.value), ['@save-sandbox', '@save-pc'], 'with no saved login, Accounts offers to save the sandbox login or this PC login (Enter)');
605
624
  t.section('apps in a few groups');
606
- const appsUser = new AppsModel(user2, t.dir('apps-groups'));
607
- const groupIds = appsUser.categories().map((c) => c.id);
608
- const appItems = appsUser.items(new Draft()).filter((i) => i.value !== REQUEST_APP);
625
+ const groupIds = appsCategories(user2).map((c) => c.id);
626
+ const appItems = appsItems(user2, t.dir('apps-groups'), new Draft()).filter((i) => i.value !== REQUEST_APP);
609
627
  t.check(groupIds.length <= 6 &&
610
- new AppsModel(ConfigFile.project(t.dir('apps-groups', 'project')), t.dir('apps-groups')).categories().length <=
611
- 5 &&
628
+ appsCategories(ConfigFile.project(t.dir('apps-groups', 'project'))).length <= 5 &&
612
629
  APP_GROUPS.every((g) => appItems.some((i) => i.category === g.id)) &&
613
630
  appItems.every((i) => groupIds.includes(i.category ?? '')) &&
614
- appItems.length === appsUser.entries().length &&
631
+ appItems.length === appRows(user2, t.dir('apps-groups')).length &&
615
632
  APP_CATEGORIES.every((c) => APP_GROUPS.some((g) => g.members.includes(c.id))), 'the apps are in at most 6 groups; every catalogue category belongs to one and every app is listed', groupIds.join());
616
633
  t.section('dialog box');
617
634
  const box = dialogBox({ title: 'Unsaved changes', lines: ['You have 2 unsaved changes.'], buttons: 'Save' }, 61);
@@ -6,7 +6,8 @@ import { parse } from 'jsonc-parser';
6
6
  import { DEFAULTS, loadConfig, machineConfigPath } from '../core/config/index.js';
7
7
  import { MIGRATIONS, MigrationBackup, MigrationMarker, MigrationRunner, } from '../host/migrate/index.js';
8
8
  import { DEFAULTS_SNAPSHOTS } from '../host/migrate/snapshots.js';
9
- import { runMigrate } from '../cli/commands/index.js';
9
+ import { migrateWith } from '../app/features/updates/updates.run.js';
10
+ import { fakeIO } from '../app/kernel/index.js';
10
11
  import { autoMigrates, continuesAfterFailedMigration, MigrateService } from '../cli/services/index.js';
11
12
  const readText = (file) => fs.readFileSync(file, 'utf8');
12
13
  export const migrateSuite = {
@@ -163,26 +164,27 @@ export const migrateSuite = {
163
164
  const cmdConfig = t.write(t.at('cmd-user', 'config.json'), JSON.stringify({ ...JSON.parse(JSON.stringify(old.defaults)), sandbox: { memoryMb: 4096 } }, null, 2));
164
165
  const service = new MigrateService({ storageRoot: t.dir('cmd-storage'), userDir: cmdUser, configPath: cmdConfig }, '9.9.9', MIGRATIONS, () => false);
165
166
  const noticed = service.versionChanged();
166
- const lines = [];
167
- const say = (line) => void lines.push(line);
168
- await runMigrate(service, { dryRun: true }, say);
169
- const dry = lines.join('\n');
167
+ // sandbox migrate's work through the feature: the logged lines, then the line it returns.
168
+ const runM = async (input) => {
169
+ const { io, seen } = fakeIO(input.answers ?? []);
170
+ const restore = input.restore ? { restore: input.restore } : {};
171
+ const out = await migrateWith(service, { dryRun: input.dryRun, ...restore }, io);
172
+ return [...seen.logs.map((l) => l.text), out.line].join('\n');
173
+ };
174
+ const dry = await runM({ dryRun: true });
170
175
  const untouched = readText(cmdConfig).includes('"toolchains"');
171
- lines.length = 0;
172
- await runMigrate(service, { dryRun: false }, say);
173
- const ran = lines.join('\n');
174
- lines.length = 0;
175
- await runMigrate(service, { dryRun: true }, say);
176
+ const ran = await runM({ dryRun: false });
177
+ const again = await runM({ dryRun: true });
176
178
  t.check(noticed &&
177
179
  dry.includes('2026-10-unpin-config-defaults') &&
178
180
  untouched &&
179
181
  ran.includes('Updated: 2026-10-unpin-config-defaults.') &&
180
182
  !service.versionChanged() &&
181
- lines.join() === 'Nothing to update.', 'a dry run lists the pending change and changes nothing; a run applies it and moves the record on');
183
+ again === 'Nothing to update.', 'a dry run lists the pending change and changes nothing; a run applies it and moves the record on');
182
184
  const backups = t.at('cmd-storage', 'backups');
183
185
  const made = fs.readdirSync(backups);
184
186
  fs.writeFileSync(cmdConfig, 'broken by hand');
185
- await runMigrate(service, { dryRun: false, restore: path.join(backups, made[0]), yes: true }, say);
187
+ await runM({ dryRun: false, restore: path.join(backups, made[0]), answers: [true] });
186
188
  t.check(made.length === 1 && readText(cmdConfig).includes('"toolchains"'), 'the run kept one backup and --restore puts the saved files back');
187
189
  t.check(!autoMigrates('self-test', false) &&
188
190
  !autoMigrates('migrate', false) &&
@@ -1,5 +1,5 @@
1
1
  import { isSandboxAddress } from '../host/channel/index.js';
2
- import { buildFirewallCommands, cidrToRange, ipv4DnsServers, parseDefaultGateways, parseDnsServers, subtractIps, } from '../shared/network/index.js';
2
+ import { attachRuleCommands, hasShortName, buildFirewallCommands, cidrToRange, ipv4DnsServers, parseDefaultGateways, parseDnsServers, subtractIps, } from '../shared/network/index.js';
3
3
  import { expandAllowList, hostAllowed, withBlockedHosts } from '../shared/network/index.js';
4
4
  export const networkSuite = {
5
5
  name: 'network',
@@ -11,6 +11,25 @@ export const networkSuite = {
11
11
  const remaining = subtractIps([[start172, end172]], ['172.28.0.1']);
12
12
  t.check(remaining.every(([start, end]) => gateway < start || gateway > end), 'the private block minus the gateway no longer contains the gateway');
13
13
  t.excludes(buildFirewallCommands(['8.8.8.8']).join(), '8.8.8.8', 'public addresses are not part of the private-range rules');
14
+ t.section('the attach rule');
15
+ const exe = 'C:\\Users\\Me\\AppData\\Local\\Temp\\agent-sandbox\\ptyhost-abc.exe';
16
+ const attach = attachRuleCommands(exe);
17
+ t.equal(attach[0], ['advfirewall', 'firewall', 'delete', 'rule', 'name=AgentSandbox-Attach'], 'attach: the old rule is deleted first');
18
+ t.equal(attach[1].slice(2), [
19
+ 'add',
20
+ 'rule',
21
+ 'name=AgentSandbox-Attach',
22
+ 'dir=in',
23
+ 'action=allow',
24
+ 'enable=yes',
25
+ 'profile=any',
26
+ 'protocol=tcp',
27
+ 'remoteip=LocalSubnet',
28
+ 'program=' + exe,
29
+ ], 'attach: inbound TCP to ptyhost only, from the local subnet only');
30
+ t.check(hasShortName('C:\\Users\\WDAGUT~1\\AppData\\Local\\Temp\\agent-sandbox\\ptyhost.exe'), 'an 8.3 short path is recognised (netsh cannot resolve it: "The application name could not be resolved")');
31
+ t.check(!hasShortName('C:\\Users\\WDAGUtilityAccount\\AppData\\Local\\Temp\\ptyhost.exe'), 'a long path is left alone');
32
+ t.check(!hasShortName('C:\\Users\\a~b\\x.exe'), 'a tilde not followed by a digit is not a short name');
14
33
  t.section('reading Windows output');
15
34
  t.equal(parseDefaultGateways(' 0.0.0.0 0.0.0.0 172.28.80.1 172.28.85.200 5\n'), ['172.28.80.1'], 'the default gateway is read from the route table');
16
35
  t.equal(parseDnsServers(' DNS Servers . . . : 172.28.80.1\n 8.8.8.8\n NetBIOS : x\n'), ['172.28.80.1', '8.8.8.8'], 'DNS servers are read from ipconfig output');
@@ -0,0 +1,2 @@
1
+ import type { Suite } from './support/suite.js';
2
+ export declare const powershellVariablesSuite: Suite;
@@ -0,0 +1,61 @@
1
+ // PowerShell variable hygiene: scripts/check-variables.ps1 reads every .ps1 of the repository with the PowerShell 5.1
2
+ // parser and reports automatic variables used as ordinary ones, names that differ from a parameter only in case,
3
+ // preference variables leaking out of functions, names a dot-sourced file shares with its caller, and reads before an
4
+ // assignment. It must find nothing in the repository, and it must still find each kind in a deliberately bad file.
5
+ import fs from 'node:fs';
6
+ import path from 'node:path';
7
+ import { spawnSync } from 'node:child_process';
8
+ import { isWin } from '../core/util/index.js';
9
+ import { SYSTEM } from '../shared/system.js';
10
+ const SKIP_FOLDERS = new Set(['node_modules', 'dist', '.git', '.claude']);
11
+ function powershellFiles(folder, found = []) {
12
+ for (const entry of fs.readdirSync(folder, { withFileTypes: true })) {
13
+ const full = path.join(folder, entry.name);
14
+ if (entry.isDirectory()) {
15
+ if (!SKIP_FOLDERS.has(entry.name))
16
+ powershellFiles(full, found);
17
+ }
18
+ else if (entry.name.endsWith('.ps1'))
19
+ found.push(full);
20
+ }
21
+ return found;
22
+ }
23
+ function check(checker, files) {
24
+ const run = spawnSync(SYSTEM.powershell(), ['-NoProfile', '-NonInteractive', '-ExecutionPolicy', 'Bypass', '-File', checker, ...files], { encoding: 'utf8', windowsHide: true, timeout: 180000 });
25
+ return { status: run.status, report: run.stderr.trim() };
26
+ }
27
+ const BAD_SCRIPT = [
28
+ 'param([switch]$AutoUpdate)',
29
+ '$autoUpdate = 1',
30
+ '$matches = 3',
31
+ 'foreach ($input in 1, 2) { }',
32
+ 'function F { $global:ErrorActionPreference = "Stop"; $env:SOME_VARIABLE = 1; $x = $notSetAnywhere }',
33
+ '',
34
+ ].join('\r\n');
35
+ export const powershellVariablesSuite = {
36
+ name: 'ps-variables',
37
+ title: 'PowerShell scripts: no automatic-variable, case or scope collisions',
38
+ run: (t) => {
39
+ const checker = path.join(t.packageRoot, 'scripts', 'check-variables.ps1');
40
+ if (!isWin || !fs.existsSync(checker)) {
41
+ t.skip('needs Windows PowerShell and scripts/check-variables.ps1');
42
+ return;
43
+ }
44
+ t.section('the checker finds each kind of collision');
45
+ const bad = t.at('bad-variables.ps1');
46
+ fs.writeFileSync(bad, BAD_SCRIPT);
47
+ const caught = check(checker, [bad]);
48
+ t.equal(caught.status, 1, 'a bad script fails the check');
49
+ for (const kind of ['automatic', 'case', 'preference', 'env', 'unassigned', 'script-scope']) {
50
+ t.check(caught.report.includes(': ' + kind + ':'), 'it reports "' + kind + '"');
51
+ }
52
+ t.section('the repository scripts are clean');
53
+ const files = ['native', 'sandbox', 'scripts']
54
+ .map((folder) => path.join(t.packageRoot, folder))
55
+ .filter((folder) => fs.existsSync(folder))
56
+ .flatMap((folder) => powershellFiles(folder));
57
+ t.check(files.length > 0, 'it found the scripts (' + files.length + ')');
58
+ const clean = check(checker, files);
59
+ t.equal(clean.status, 0, 'no collisions' + (clean.report ? ':\n' + clean.report : ''));
60
+ },
61
+ };
@@ -59,6 +59,7 @@ export const LAUNCH_FILES_GOLDEN = [
59
59
  'sandbox/scripts/install-shell-path.ps1',
60
60
  'sandbox/scripts/link-profile-dirs.ps1',
61
61
  'sandbox/scripts/new-shortcuts.ps1',
62
+ 'sandbox/scripts/repair-wmi-services.ps1',
62
63
  'sandbox/scripts/set-desktop.ps1',
63
64
  'sandbox/scripts/set-dns-servers.ps1',
64
65
  'sandbox/scripts/set-machine-environment.ps1',
@@ -103,6 +103,9 @@ export declare function createFixtures(t: Harness): {
103
103
  configFile: string;
104
104
  killSwitch: boolean;
105
105
  };
106
+ github: {
107
+ shareToken: boolean;
108
+ };
106
109
  provisioning: {
107
110
  copyFromHost: boolean;
108
111
  fallbackToInstall: boolean;
@@ -253,10 +256,12 @@ export declare function createFixtures(t: Harness): {
253
256
  reuseSession: boolean;
254
257
  auto: boolean;
255
258
  autoHide: boolean;
259
+ confirmCtrlC: boolean;
256
260
  };
257
261
  web: {
258
262
  enabled: boolean;
259
263
  port: number;
264
+ openOnLaunch: boolean;
260
265
  };
261
266
  agentCommands: {
262
267
  enabled: boolean;
@@ -0,0 +1 @@
1
+ export {};