windows-development-sandbox 1.5.0 → 1.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (709) hide show
  1. package/README.md +1 -0
  2. package/config.example.json +27 -1
  3. package/dist/app/context.d.ts +8 -0
  4. package/dist/app/context.js +12 -0
  5. package/dist/app/features/defs.d.ts +322 -0
  6. package/dist/app/features/defs.js +28 -0
  7. package/dist/app/features/sandbox/index.d.ts +2 -0
  8. package/dist/app/features/sandbox/index.js +3 -0
  9. package/dist/app/features/sandbox/sandbox.def.d.ts +193 -0
  10. package/dist/app/features/sandbox/sandbox.def.js +245 -0
  11. package/dist/app/features/sandbox/sandbox.run.d.ts +72 -0
  12. package/dist/app/features/sandbox/sandbox.run.js +98 -0
  13. package/dist/app/features/sessions/index.d.ts +2 -0
  14. package/dist/app/features/sessions/index.js +3 -0
  15. package/dist/app/features/sessions/sessions.def.d.ts +141 -0
  16. package/dist/app/features/sessions/sessions.def.js +176 -0
  17. package/dist/app/features/sessions/sessions.run.d.ts +39 -0
  18. package/dist/app/features/sessions/sessions.run.js +58 -0
  19. package/dist/{cli/menu/settings/categories.d.ts → app/features/settings/categories.def.d.ts} +4 -0
  20. package/dist/{cli/menu/settings/categories.js → app/features/settings/categories.def.js} +40 -46
  21. package/dist/app/features/settings/index.d.ts +5 -0
  22. package/dist/app/features/settings/index.js +6 -0
  23. package/dist/{cli/menu/settings/config-file.d.ts → app/features/settings/settings-file.d.ts} +1 -1
  24. package/dist/{cli/menu/settings/config-file.js → app/features/settings/settings-file.js} +1 -1
  25. package/dist/app/features/settings/settings-rows.d.ts +35 -0
  26. package/dist/app/features/settings/settings-rows.js +94 -0
  27. package/dist/app/features/settings/settings.def.d.ts +157 -0
  28. package/dist/app/features/settings/settings.def.js +179 -0
  29. package/dist/app/features/settings/settings.run.d.ts +67 -0
  30. package/dist/app/features/settings/settings.run.js +84 -0
  31. package/dist/app/kernel/define.d.ts +143 -0
  32. package/dist/app/kernel/define.js +42 -0
  33. package/dist/app/kernel/errors.d.ts +34 -0
  34. package/dist/app/kernel/errors.js +45 -0
  35. package/dist/app/kernel/fields.d.ts +70 -0
  36. package/dist/app/kernel/fields.js +243 -0
  37. package/dist/app/kernel/index.d.ts +8 -0
  38. package/dist/app/kernel/index.js +10 -0
  39. package/dist/app/kernel/io.d.ts +193 -0
  40. package/dist/app/kernel/io.js +126 -0
  41. package/dist/app/kernel/rpc.d.ts +156 -0
  42. package/dist/app/kernel/rpc.js +47 -0
  43. package/dist/{cli/menu/settings → app/kernel}/schema-node.d.ts +9 -0
  44. package/dist/{cli/menu/settings → app/kernel}/schema-node.js +26 -0
  45. package/dist/app/kernel/text.d.ts +6 -0
  46. package/dist/app/kernel/text.js +57 -0
  47. package/dist/app/kernel/view.d.ts +146 -0
  48. package/dist/app/kernel/view.js +28 -0
  49. package/dist/app/registry.d.ts +10 -0
  50. package/dist/app/registry.js +50 -0
  51. package/dist/cli/args.d.ts +18 -2
  52. package/dist/cli/args.js +101 -24
  53. package/dist/cli/commands/accounts.js +45 -17
  54. package/dist/cli/commands/context.d.ts +2 -0
  55. package/dist/cli/commands/context.js +9 -2
  56. package/dist/cli/commands/data/clean.js +32 -9
  57. package/dist/cli/commands/engine.d.ts +2 -0
  58. package/dist/cli/commands/engine.js +60 -0
  59. package/dist/cli/commands/index.d.ts +2 -1
  60. package/dist/cli/commands/index.js +2 -1
  61. package/dist/cli/commands/launch/add-root.d.ts +3 -2
  62. package/dist/cli/commands/launch/add-root.js +11 -10
  63. package/dist/cli/commands/launch/launch.js +102 -9
  64. package/dist/cli/commands/launch/prepare.js +6 -4
  65. package/dist/cli/commands/launch/running.js +2 -1
  66. package/dist/cli/commands/launch/summary.d.ts +2 -0
  67. package/dist/cli/commands/maintenance/audit.js +1 -1
  68. package/dist/cli/commands/maintenance/clean.d.ts +1 -1
  69. package/dist/cli/commands/maintenance/clean.js +17 -3
  70. package/dist/cli/commands/maintenance/config-share.d.ts +12 -0
  71. package/dist/cli/commands/maintenance/config-share.js +80 -0
  72. package/dist/cli/commands/maintenance/config.d.ts +1 -1
  73. package/dist/cli/commands/maintenance/config.js +38 -10
  74. package/dist/cli/commands/maintenance/doctor.d.ts +1 -1
  75. package/dist/cli/commands/maintenance/doctor.js +2 -4
  76. package/dist/cli/commands/maintenance/logs.js +14 -0
  77. package/dist/cli/commands/maintenance/migrate.d.ts +2 -1
  78. package/dist/cli/commands/maintenance/migrate.js +19 -4
  79. package/dist/cli/commands/maintenance/sync.js +2 -0
  80. package/dist/cli/commands/maintenance/update.d.ts +2 -0
  81. package/dist/cli/commands/maintenance/update.js +27 -4
  82. package/dist/cli/commands/projects/here.js +12 -7
  83. package/dist/cli/commands/projects/projects.js +26 -17
  84. package/dist/cli/commands/registry.js +10 -7
  85. package/dist/cli/commands/repair/checks/agents.d.ts +2 -0
  86. package/dist/cli/commands/repair/checks/agents.js +70 -0
  87. package/dist/cli/commands/repair/checks/common.d.ts +8 -0
  88. package/dist/cli/commands/repair/checks/common.js +22 -0
  89. package/dist/cli/commands/repair/checks/contained.d.ts +2 -0
  90. package/dist/cli/commands/repair/checks/contained.js +139 -0
  91. package/dist/cli/commands/repair/checks/data.d.ts +2 -0
  92. package/dist/cli/commands/repair/checks/data.js +241 -0
  93. package/dist/cli/commands/repair/checks/hcs.d.ts +7 -0
  94. package/dist/cli/commands/repair/checks/hcs.js +28 -0
  95. package/dist/cli/commands/repair/checks/index.d.ts +2 -0
  96. package/dist/cli/commands/repair/checks/index.js +24 -0
  97. package/dist/cli/commands/repair/checks/install.d.ts +5 -0
  98. package/dist/cli/commands/repair/checks/install.js +318 -0
  99. package/dist/cli/commands/repair/checks/machine.d.ts +2 -0
  100. package/dist/cli/commands/repair/checks/machine.js +232 -0
  101. package/dist/cli/commands/repair/checks/network.d.ts +6 -0
  102. package/dist/cli/commands/repair/checks/network.js +60 -0
  103. package/dist/cli/commands/repair/checks/pc.d.ts +4 -0
  104. package/dist/cli/commands/repair/checks/pc.js +86 -0
  105. package/dist/cli/commands/repair/checks/sandbox.d.ts +2 -0
  106. package/dist/cli/commands/repair/checks/sandbox.js +282 -0
  107. package/dist/cli/commands/repair/checks/settings.d.ts +2 -0
  108. package/dist/cli/commands/repair/checks/settings.js +169 -0
  109. package/dist/cli/commands/repair/checks/vpn.d.ts +2 -0
  110. package/dist/cli/commands/repair/checks/vpn.js +78 -0
  111. package/dist/cli/commands/repair/engine.d.ts +32 -0
  112. package/dist/cli/commands/repair/engine.js +127 -0
  113. package/dist/cli/commands/repair/env.d.ts +7 -0
  114. package/dist/cli/commands/repair/env.js +50 -0
  115. package/dist/cli/commands/repair/index.d.ts +1 -9
  116. package/dist/cli/commands/repair/index.js +2 -54
  117. package/dist/cli/commands/repair/log.d.ts +17 -0
  118. package/dist/cli/commands/repair/log.js +82 -0
  119. package/dist/cli/commands/repair/printer.d.ts +32 -0
  120. package/dist/cli/commands/repair/printer.js +132 -0
  121. package/dist/cli/commands/repair/run.d.ts +33 -0
  122. package/dist/cli/commands/repair/run.js +133 -0
  123. package/dist/cli/commands/repair/types.d.ts +97 -9
  124. package/dist/cli/commands/repair/types.js +11 -7
  125. package/dist/cli/commands/resume.js +2 -2
  126. package/dist/cli/commands/sessions/index.d.ts +0 -4
  127. package/dist/cli/commands/sessions/index.js +3 -7
  128. package/dist/cli/commands/setup/index.d.ts +5 -0
  129. package/dist/cli/commands/setup/index.js +25 -1
  130. package/dist/cli/commands/shared-use.d.ts +5 -2
  131. package/dist/cli/commands/shared-use.js +11 -8
  132. package/dist/cli/commands/storage/contained.d.ts +3 -0
  133. package/dist/cli/commands/storage/contained.js +179 -0
  134. package/dist/cli/commands/storage/index.d.ts +2 -0
  135. package/dist/cli/commands/storage/index.js +2 -0
  136. package/dist/cli/commands/storage/offer.d.ts +3 -0
  137. package/dist/cli/commands/storage/offer.js +91 -0
  138. package/dist/cli/commands/storage/storage.d.ts +8 -0
  139. package/dist/cli/commands/storage/storage.js +179 -0
  140. package/dist/cli/commands/user-config.js +2 -2
  141. package/dist/cli/commands/vpn.d.ts +4 -0
  142. package/dist/cli/commands/vpn.js +91 -0
  143. package/dist/cli/commands/web.d.ts +4 -0
  144. package/dist/cli/commands/web.js +97 -0
  145. package/dist/cli/history.js +1 -1
  146. package/dist/cli/index.js +48 -4
  147. package/dist/cli/manifest.d.ts +339 -97
  148. package/dist/cli/manifest.js +222 -67
  149. package/dist/cli/menu/add-agent.d.ts +47 -0
  150. package/dist/cli/menu/add-agent.js +168 -0
  151. package/dist/cli/menu/apps.d.ts +38 -3
  152. package/dist/cli/menu/apps.js +80 -5
  153. package/dist/cli/menu/command-fields.d.ts +18 -0
  154. package/dist/cli/menu/command-fields.js +61 -0
  155. package/dist/cli/menu/command-form.d.ts +22 -0
  156. package/dist/cli/menu/command-form.js +169 -46
  157. package/dist/cli/menu/data-folder.d.ts +18 -0
  158. package/dist/cli/menu/data-folder.js +99 -0
  159. package/dist/cli/menu/io-menu.d.ts +29 -0
  160. package/dist/cli/menu/io-menu.js +126 -0
  161. package/dist/cli/menu/live.d.ts +27 -0
  162. package/dist/cli/menu/live.js +69 -0
  163. package/dist/cli/menu/memory.d.ts +19 -0
  164. package/dist/cli/menu/memory.js +53 -0
  165. package/dist/cli/menu/menu.d.ts +11 -2
  166. package/dist/cli/menu/menu.js +36 -15
  167. package/dist/cli/menu/pages/accounts-page.js +55 -24
  168. package/dist/cli/menu/pages/agents-page.d.ts +68 -0
  169. package/dist/cli/menu/pages/agents-page.js +345 -0
  170. package/dist/cli/menu/pages/apps-page.d.ts +3 -1
  171. package/dist/cli/menu/pages/apps-page.js +20 -4
  172. package/dist/cli/menu/pages/list-page.d.ts +43 -3
  173. package/dist/cli/menu/pages/list-page.js +146 -12
  174. package/dist/cli/menu/pages/maintenance-page.d.ts +9 -5
  175. package/dist/cli/menu/pages/maintenance-page.js +172 -85
  176. package/dist/cli/menu/pages/sandbox-page.d.ts +7 -6
  177. package/dist/cli/menu/pages/sandbox-page.js +117 -120
  178. package/dist/cli/menu/pages/scopes.d.ts +1 -1
  179. package/dist/cli/menu/pages/sessions-page.d.ts +6 -8
  180. package/dist/cli/menu/pages/sessions-page.js +22 -75
  181. package/dist/cli/menu/pages/settings-page.d.ts +10 -14
  182. package/dist/cli/menu/pages/settings-page.js +95 -102
  183. package/dist/cli/menu/render-view.d.ts +28 -0
  184. package/dist/cli/menu/render-view.js +112 -0
  185. package/dist/cli/menu/settings/ask-path.d.ts +14 -0
  186. package/dist/cli/menu/settings/ask-path.js +14 -0
  187. package/dist/cli/menu/settings/collections/collection-editor.d.ts +8 -2
  188. package/dist/cli/menu/settings/collections/collection-editor.js +12 -8
  189. package/dist/cli/menu/settings/collections/map-editor.js +5 -1
  190. package/dist/cli/menu/settings/collections/object-list-editor.js +2 -2
  191. package/dist/cli/menu/settings/collections/object-map-editor.js +2 -2
  192. package/dist/cli/menu/settings/collections/string-list-editor.d.ts +10 -1
  193. package/dist/cli/menu/settings/collections/string-list-editor.js +23 -0
  194. package/dist/cli/menu/settings/draft.d.ts +1 -1
  195. package/dist/cli/menu/settings/format.d.ts +1 -3
  196. package/dist/cli/menu/settings/format.js +6 -36
  197. package/dist/cli/menu/settings/index.d.ts +1 -3
  198. package/dist/cli/menu/settings/index.js +3 -3
  199. package/dist/cli/menu/settings/object-editor.d.ts +6 -2
  200. package/dist/cli/menu/settings/object-editor.js +15 -4
  201. package/dist/cli/menu/settings/share.d.ts +36 -0
  202. package/dist/cli/menu/settings/share.js +131 -0
  203. package/dist/cli/menu/settings/value-editor.d.ts +10 -1
  204. package/dist/cli/menu/settings/value-editor.js +99 -63
  205. package/dist/cli/menu/several.js +7 -5
  206. package/dist/cli/menu/share-settings.d.ts +21 -0
  207. package/dist/cli/menu/share-settings.js +112 -0
  208. package/dist/cli/menu/shell.d.ts +53 -10
  209. package/dist/cli/menu/shell.js +115 -10
  210. package/dist/cli/menu/views.d.ts +7 -0
  211. package/dist/cli/menu/views.js +11 -0
  212. package/dist/cli/menu/vpn-login.d.ts +16 -0
  213. package/dist/cli/menu/vpn-login.js +74 -0
  214. package/dist/cli/operations/handler.d.ts +6 -0
  215. package/dist/cli/operations/handler.js +61 -0
  216. package/dist/cli/operations/index.d.ts +4 -0
  217. package/dist/cli/operations/index.js +5 -0
  218. package/dist/cli/operations/input.d.ts +6 -0
  219. package/dist/cli/operations/input.js +37 -0
  220. package/dist/cli/operations/io-cli.d.ts +7 -0
  221. package/dist/cli/operations/io-cli.js +62 -0
  222. package/dist/cli/operations/spec.d.ts +18 -0
  223. package/dist/cli/operations/spec.js +108 -0
  224. package/dist/cli/output/colors.d.ts +2 -2
  225. package/dist/cli/output/colors.js +2 -11
  226. package/dist/cli/output/index.d.ts +1 -0
  227. package/dist/cli/output/index.js +1 -0
  228. package/dist/cli/output/print.d.ts +5 -1
  229. package/dist/cli/output/print.js +15 -4
  230. package/dist/cli/output/progress.d.ts +15 -0
  231. package/dist/cli/output/progress.js +44 -0
  232. package/dist/cli/output/render-view.d.ts +4 -0
  233. package/dist/cli/output/render-view.js +63 -0
  234. package/dist/cli/services/accounts-service.d.ts +2 -0
  235. package/dist/cli/services/accounts-service.js +2 -2
  236. package/dist/cli/services/agent-commands-service.d.ts +2 -0
  237. package/dist/cli/services/agent-commands-service.js +11 -2
  238. package/dist/cli/services/health-service.d.ts +0 -6
  239. package/dist/cli/services/health-service.js +5 -42
  240. package/dist/cli/services/index.d.ts +3 -1
  241. package/dist/cli/services/index.js +3 -1
  242. package/dist/cli/services/logs-service.js +4 -3
  243. package/dist/cli/services/migrate-service.js +1 -1
  244. package/dist/cli/services/update-service.d.ts +2 -0
  245. package/dist/cli/services/update-service.js +13 -6
  246. package/dist/cli/services/vpn-service.d.ts +28 -0
  247. package/dist/cli/services/vpn-service.js +46 -0
  248. package/dist/cli/services/web-service.d.ts +22 -0
  249. package/dist/cli/services/web-service.js +111 -0
  250. package/dist/cli/tui/app.d.ts +12 -4
  251. package/dist/cli/tui/app.js +38 -9
  252. package/dist/cli/tui/dialogs/path-listing.d.ts +43 -0
  253. package/dist/cli/tui/dialogs/path-listing.js +127 -0
  254. package/dist/cli/tui/dialogs/path-picker.d.ts +67 -0
  255. package/dist/cli/tui/dialogs/path-picker.js +423 -0
  256. package/dist/cli/tui/dialogs/text-screen.d.ts +6 -0
  257. package/dist/cli/tui/dialogs/text-screen.js +10 -3
  258. package/dist/cli/tui/frame.d.ts +7 -10
  259. package/dist/cli/tui/frame.js +19 -10
  260. package/dist/cli/tui/index.d.ts +4 -3
  261. package/dist/cli/tui/index.js +2 -1
  262. package/dist/cli/tui/list/list-screen.d.ts +28 -2
  263. package/dist/cli/tui/list/list-screen.js +130 -66
  264. package/dist/cli/tui/list/list-types.d.ts +10 -4
  265. package/dist/cli/tui/nav.d.ts +29 -0
  266. package/dist/cli/tui/nav.js +67 -0
  267. package/dist/cli/tui/terminal.d.ts +7 -0
  268. package/dist/cli/tui/terminal.js +30 -10
  269. package/dist/cli/validate.d.ts +1 -1
  270. package/dist/cli/validate.js +3 -3
  271. package/dist/cli/version.d.ts +2 -0
  272. package/dist/cli/version.js +11 -0
  273. package/dist/core/config/defaults.js +13 -1
  274. package/dist/core/config/loader.js +12 -2
  275. package/dist/core/local-config/overrides.js +1 -0
  276. package/dist/core/prompt.d.ts +19 -0
  277. package/dist/core/prompt.js +43 -3
  278. package/dist/core/schema/common.d.ts +6 -0
  279. package/dist/core/schema/common.js +8 -0
  280. package/dist/core/schema/config.d.ts +36 -0
  281. package/dist/core/schema/config.js +26 -2
  282. package/dist/core/schema/harness.d.ts +2 -0
  283. package/dist/core/schema/harness.js +13 -2
  284. package/dist/core/schema/index.d.ts +3 -1
  285. package/dist/core/schema/index.js +3 -1
  286. package/dist/core/schema/profile.d.ts +1 -0
  287. package/dist/core/schema/profile.js +3 -0
  288. package/dist/core/schema/redact.d.ts +13 -0
  289. package/dist/core/schema/redact.js +75 -0
  290. package/dist/core/schema/sandbox.d.ts +6 -0
  291. package/dist/core/schema/sandbox.js +21 -3
  292. package/dist/core/schema/vpn.d.ts +18 -0
  293. package/dist/core/schema/vpn.js +22 -0
  294. package/dist/core/schema/web.d.ts +6 -0
  295. package/dist/core/schema/web.js +14 -0
  296. package/dist/core/types.d.ts +44 -0
  297. package/dist/core/util/fs.d.ts +2 -0
  298. package/dist/core/util/fs.js +9 -0
  299. package/dist/core/util/index.d.ts +1 -1
  300. package/dist/core/util/index.js +1 -1
  301. package/dist/guest/agents/agent-files.d.ts +54 -0
  302. package/dist/guest/agents/agent-files.js +67 -0
  303. package/dist/guest/agents/agent-installer.js +2 -9
  304. package/dist/guest/agents/local-state.d.ts +14 -0
  305. package/dist/guest/agents/local-state.js +218 -0
  306. package/dist/guest/agents/sbx-agent.d.ts +2 -0
  307. package/dist/guest/agents/sbx-agent.js +35 -0
  308. package/dist/guest/boot/boot.d.ts +4 -0
  309. package/dist/guest/boot/boot.js +51 -11
  310. package/dist/guest/daemon/daemon.js +8 -1
  311. package/dist/guest/daemon/exec.js +2 -1
  312. package/dist/guest/daemon/idle-shutdown.js +2 -0
  313. package/dist/guest/daemon/sessions.d.ts +5 -0
  314. package/dist/guest/daemon/sessions.js +20 -3
  315. package/dist/guest/entries/agent.d.ts +1 -0
  316. package/dist/guest/entries/agent.js +10 -0
  317. package/dist/guest/entries/daemon.js +5 -0
  318. package/dist/guest/governor/decide.d.ts +61 -0
  319. package/dist/guest/governor/decide.js +182 -0
  320. package/dist/guest/governor/governor.d.ts +42 -0
  321. package/dist/guest/governor/governor.js +214 -0
  322. package/dist/guest/governor/index.d.ts +2 -0
  323. package/dist/guest/governor/index.js +2 -0
  324. package/dist/guest/runtime/csc.d.ts +2 -0
  325. package/dist/guest/runtime/csc.js +2 -0
  326. package/dist/guest/runtime/secrets.d.ts +12 -0
  327. package/dist/guest/runtime/secrets.js +63 -0
  328. package/dist/guest/sessions/viewer.js +3 -1
  329. package/dist/guest/setup/claude-profile.d.ts +2 -1
  330. package/dist/guest/setup/claude-profile.js +12 -1
  331. package/dist/guest/setup/environment.d.ts +6 -0
  332. package/dist/guest/setup/environment.js +56 -6
  333. package/dist/guest/setup/path-plan.d.ts +32 -0
  334. package/dist/guest/setup/path-plan.js +67 -0
  335. package/dist/guest/setup/shell-path.d.ts +1 -0
  336. package/dist/guest/setup/shell-path.js +22 -0
  337. package/dist/guest/toolchains/languages/python.js +1 -1
  338. package/dist/guest/toolchains/native/rust-extras.js +4 -2
  339. package/dist/guest/toolchains/runtimes.js +2 -2
  340. package/dist/guest/vpn/index.d.ts +1 -0
  341. package/dist/guest/vpn/index.js +1 -0
  342. package/dist/guest/vpn/openvpn.d.ts +9 -0
  343. package/dist/guest/vpn/openvpn.js +47 -0
  344. package/dist/guest/vpn/tunnel.d.ts +17 -0
  345. package/dist/guest/vpn/tunnel.js +78 -0
  346. package/dist/guest/vpn/vpn.d.ts +6 -0
  347. package/dist/guest/vpn/vpn.js +174 -0
  348. package/dist/guest/vpn/wireguard.d.ts +32 -0
  349. package/dist/guest/vpn/wireguard.js +132 -0
  350. package/dist/host/agent-commands/commands.d.ts +4 -0
  351. package/dist/host/agent-commands/commands.js +31 -7
  352. package/dist/host/audit/log.d.ts +1 -1
  353. package/dist/host/channel/attach.d.ts +6 -2
  354. package/dist/host/channel/attach.js +6 -3
  355. package/dist/host/channel/channel.d.ts +9 -0
  356. package/dist/host/channel/channel.js +30 -2
  357. package/dist/host/engines/engine.d.ts +12 -0
  358. package/dist/host/engines/engine.js +1 -0
  359. package/dist/host/engines/hcs/check.d.ts +25 -0
  360. package/dist/host/engines/hcs/check.js +95 -0
  361. package/dist/host/engines/hcs/documents.d.ts +44 -0
  362. package/dist/host/engines/hcs/documents.js +155 -0
  363. package/dist/host/engines/hcs/engine.d.ts +39 -0
  364. package/dist/host/engines/hcs/engine.js +171 -0
  365. package/dist/host/engines/hcs/helper.d.ts +43 -0
  366. package/dist/host/engines/hcs/helper.js +173 -0
  367. package/dist/host/engines/hcs/images.d.ts +37 -0
  368. package/dist/host/engines/hcs/images.js +152 -0
  369. package/dist/host/engines/index.d.ts +7 -0
  370. package/dist/host/engines/index.js +5 -0
  371. package/dist/host/engines/registry.d.ts +16 -0
  372. package/dist/host/engines/registry.js +30 -0
  373. package/dist/host/engines/types.d.ts +58 -0
  374. package/dist/host/engines/types.js +1 -0
  375. package/dist/host/engines/windows-sandbox/engine.d.ts +16 -0
  376. package/dist/host/engines/windows-sandbox/engine.js +63 -0
  377. package/dist/host/engines/windows-sandbox/wsb-text.d.ts +2 -0
  378. package/dist/host/engines/windows-sandbox/wsb-text.js +29 -0
  379. package/dist/host/health/acl-read.d.ts +17 -0
  380. package/dist/host/health/acl-read.js +45 -0
  381. package/dist/host/health/config-backup.d.ts +11 -0
  382. package/dist/host/health/config-backup.js +73 -0
  383. package/dist/host/health/index.d.ts +6 -0
  384. package/dist/host/health/index.js +7 -0
  385. package/dist/host/health/install-state.d.ts +33 -0
  386. package/dist/host/health/install-state.js +110 -0
  387. package/dist/host/health/path-copies.d.ts +32 -0
  388. package/dist/host/health/path-copies.js +82 -0
  389. package/dist/host/health/stale.d.ts +45 -0
  390. package/dist/host/health/stale.js +120 -0
  391. package/dist/host/health/system.d.ts +37 -0
  392. package/dist/host/health/system.js +87 -0
  393. package/dist/host/install/copies.d.ts +13 -0
  394. package/dist/host/install/copies.js +63 -0
  395. package/dist/host/install/index.d.ts +1 -0
  396. package/dist/host/install/index.js +1 -0
  397. package/dist/host/launch/generate.d.ts +5 -1
  398. package/dist/host/launch/generate.js +73 -7
  399. package/dist/host/launch/index.d.ts +3 -1
  400. package/dist/host/launch/index.js +3 -1
  401. package/dist/host/launch/launch-plan.d.ts +13 -0
  402. package/dist/host/launch/launch-plan.js +26 -0
  403. package/dist/host/launch/mappings.js +2 -1
  404. package/dist/host/launch/risk-notes.d.ts +10 -0
  405. package/dist/host/launch/risk-notes.js +18 -0
  406. package/dist/host/launch/session-file.d.ts +2 -0
  407. package/dist/host/launch/session-file.js +11 -2
  408. package/dist/host/launch/stage.js +1 -1
  409. package/dist/host/launch/wsb-file.d.ts +28 -4
  410. package/dist/host/launch/wsb-file.js +61 -42
  411. package/dist/host/life/crash-report.d.ts +27 -1
  412. package/dist/host/life/crash-report.js +173 -7
  413. package/dist/host/life/host-memory.d.ts +40 -0
  414. package/dist/host/life/host-memory.js +137 -0
  415. package/dist/host/life/index.d.ts +2 -0
  416. package/dist/host/life/index.js +2 -0
  417. package/dist/host/life/stop-events.d.ts +2 -0
  418. package/dist/host/life/stop-events.js +20 -3
  419. package/dist/host/life/stop-warning.js +1 -1
  420. package/dist/host/life/watchdog.d.ts +54 -0
  421. package/dist/host/life/watchdog.js +108 -0
  422. package/dist/host/migrate/backup.js +3 -0
  423. package/dist/host/migrate/migrations/config-file.d.ts +2 -0
  424. package/dist/host/migrate/migrations/config-file.js +7 -0
  425. package/dist/host/migrate/migrations/follow-machine-storage.js +9 -1
  426. package/dist/host/migrate/migrations/pin-storage-root.d.ts +4 -0
  427. package/dist/host/migrate/migrations/pin-storage-root.js +46 -0
  428. package/dist/host/migrate/migrations/unpin-config-defaults.js +4 -1
  429. package/dist/host/migrate/registry.js +2 -1
  430. package/dist/host/native.d.ts +3 -2
  431. package/dist/host/native.js +22 -3
  432. package/dist/host/profile/accounts.d.ts +25 -1
  433. package/dist/host/profile/accounts.js +96 -13
  434. package/dist/host/profile/agent-accounts.d.ts +5 -1
  435. package/dist/host/profile/agent-accounts.js +35 -5
  436. package/dist/host/profile/claude-login.js +3 -2
  437. package/dist/host/profile/index.d.ts +2 -1
  438. package/dist/host/profile/index.js +1 -0
  439. package/dist/host/profile/plugin-share.d.ts +26 -0
  440. package/dist/host/profile/plugin-share.js +146 -0
  441. package/dist/host/projects/join.d.ts +7 -1
  442. package/dist/host/projects/join.js +21 -4
  443. package/dist/host/projects/registry.d.ts +16 -10
  444. package/dist/host/projects/registry.js +74 -6
  445. package/dist/host/sandbox/contained-deps.d.ts +2 -0
  446. package/dist/host/sandbox/contained-deps.js +17 -0
  447. package/dist/host/sandbox/index.d.ts +2 -0
  448. package/dist/host/sandbox/index.js +2 -0
  449. package/dist/host/sandbox/platform.d.ts +4 -0
  450. package/dist/host/sandbox/platform.js +23 -4
  451. package/dist/host/sandbox/tasklist.d.ts +13 -0
  452. package/dist/host/sandbox/tasklist.js +47 -0
  453. package/dist/host/secrets/index.d.ts +2 -0
  454. package/dist/host/secrets/index.js +2 -0
  455. package/dist/host/secrets/launch-secrets.d.ts +17 -0
  456. package/dist/host/secrets/launch-secrets.js +94 -0
  457. package/dist/host/secrets/secret-store.d.ts +25 -0
  458. package/dist/host/secrets/secret-store.js +89 -0
  459. package/dist/host/sharing/acl.d.ts +16 -2
  460. package/dist/host/sharing/acl.js +35 -10
  461. package/dist/host/sharing/current.js +8 -3
  462. package/dist/host/sharing/doctor.d.ts +2 -0
  463. package/dist/host/sharing/doctor.js +44 -0
  464. package/dist/host/sharing/group-access.d.ts +6 -0
  465. package/dist/host/sharing/group-access.js +24 -0
  466. package/dist/host/sharing/index.d.ts +2 -1
  467. package/dist/host/sharing/index.js +2 -1
  468. package/dist/host/sharing/lifecycle.d.ts +8 -2
  469. package/dist/host/sharing/lifecycle.js +16 -7
  470. package/dist/host/storage/cleanup.d.ts +2 -0
  471. package/dist/host/storage/cleanup.js +2 -1
  472. package/dist/host/storage/contained/check.d.ts +9 -0
  473. package/dist/host/storage/contained/check.js +37 -0
  474. package/dist/host/storage/contained/contained-storage.d.ts +66 -0
  475. package/dist/host/storage/contained/contained-storage.js +168 -0
  476. package/dist/host/storage/contained/disk-port.d.ts +43 -0
  477. package/dist/host/storage/contained/disk-port.js +122 -0
  478. package/dist/host/storage/contained/index.d.ts +9 -0
  479. package/dist/host/storage/contained/index.js +8 -0
  480. package/dist/host/storage/contained/layout.d.ts +28 -0
  481. package/dist/host/storage/contained/layout.js +45 -0
  482. package/dist/host/storage/contained/resolve.d.ts +4 -0
  483. package/dist/host/storage/contained/resolve.js +30 -0
  484. package/dist/host/storage/contained/setting.d.ts +1 -0
  485. package/dist/host/storage/contained/setting.js +5 -0
  486. package/dist/host/storage/contained/state.d.ts +89 -0
  487. package/dist/host/storage/contained/state.js +166 -0
  488. package/dist/host/storage/contained/transfer.d.ts +24 -0
  489. package/dist/host/storage/contained/transfer.js +121 -0
  490. package/dist/host/storage/history.d.ts +21 -0
  491. package/dist/host/storage/history.js +49 -0
  492. package/dist/host/storage/index.d.ts +6 -0
  493. package/dist/host/storage/index.js +6 -0
  494. package/dist/host/storage/move.d.ts +95 -0
  495. package/dist/host/storage/move.js +384 -0
  496. package/dist/host/storage/offer.d.ts +11 -0
  497. package/dist/host/storage/offer.js +36 -0
  498. package/dist/host/storage/providers/contained.d.ts +15 -0
  499. package/dist/host/storage/providers/contained.js +39 -0
  500. package/dist/host/storage/providers/folder.d.ts +12 -0
  501. package/dist/host/storage/providers/folder.js +20 -0
  502. package/dist/host/storage/providers/index.d.ts +4 -0
  503. package/dist/host/storage/providers/index.js +3 -0
  504. package/dist/host/storage/providers/registry.d.ts +9 -0
  505. package/dist/host/storage/providers/registry.js +14 -0
  506. package/dist/host/storage/providers/types.d.ts +33 -0
  507. package/dist/host/storage/providers/types.js +1 -0
  508. package/dist/host/storage/root.d.ts +18 -0
  509. package/dist/host/storage/root.js +77 -0
  510. package/dist/host/tools/conpty.js +3 -2
  511. package/dist/host/tools/windows-terminal.d.ts +2 -0
  512. package/dist/host/tools/windows-terminal.js +31 -12
  513. package/dist/host/update/index.d.ts +1 -1
  514. package/dist/host/update/index.js +1 -1
  515. package/dist/host/update/notice.d.ts +2 -0
  516. package/dist/host/update/notice.js +13 -1
  517. package/dist/host/update/update-state.d.ts +6 -0
  518. package/dist/host/update/update-state.js +2 -0
  519. package/dist/host/update/updater.d.ts +19 -0
  520. package/dist/host/update/updater.js +88 -12
  521. package/dist/host/vpn/index.d.ts +4 -0
  522. package/dist/host/vpn/index.js +4 -0
  523. package/dist/host/vpn/login.d.ts +24 -0
  524. package/dist/host/vpn/login.js +79 -0
  525. package/dist/host/vpn/nordvpn.d.ts +21 -0
  526. package/dist/host/vpn/nordvpn.js +77 -0
  527. package/dist/host/vpn/prepare.d.ts +19 -0
  528. package/dist/host/vpn/prepare.js +138 -0
  529. package/dist/host/vpn/status.d.ts +12 -0
  530. package/dist/host/vpn/status.js +53 -0
  531. package/dist/host/web/auth.d.ts +38 -0
  532. package/dist/host/web/auth.js +96 -0
  533. package/dist/host/web/backend.d.ts +14 -0
  534. package/dist/host/web/backend.js +1 -0
  535. package/dist/host/web/bridge.d.ts +27 -0
  536. package/dist/host/web/bridge.js +144 -0
  537. package/dist/host/web/browser.d.ts +3 -0
  538. package/dist/host/web/browser.js +23 -0
  539. package/dist/host/web/control.d.ts +17 -0
  540. package/dist/host/web/control.js +67 -0
  541. package/dist/host/web/index.d.ts +10 -0
  542. package/dist/host/web/index.js +9 -0
  543. package/dist/host/web/rpc.d.ts +24 -0
  544. package/dist/host/web/rpc.js +149 -0
  545. package/dist/host/web/runner.d.ts +16 -0
  546. package/dist/host/web/runner.js +44 -0
  547. package/dist/host/web/server.d.ts +47 -0
  548. package/dist/host/web/server.js +238 -0
  549. package/dist/host/web/state.d.ts +20 -0
  550. package/dist/host/web/state.js +41 -0
  551. package/dist/host/web/static-files.d.ts +13 -0
  552. package/dist/host/web/static-files.js +57 -0
  553. package/dist/runtime/agent.js +27675 -0
  554. package/dist/runtime/daemon.js +1950 -1122
  555. package/dist/runtime/db.js +302 -111
  556. package/dist/runtime/main.js +1560 -275
  557. package/dist/runtime/netproxy.js +168 -8
  558. package/dist/runtime/view.js +186 -13
  559. package/dist/selftest/accounts.test.js +40 -0
  560. package/dist/selftest/agent-paths.test.d.ts +2 -0
  561. package/dist/selftest/agent-paths.test.js +125 -0
  562. package/dist/selftest/app.test.d.ts +2 -0
  563. package/dist/selftest/app.test.js +324 -0
  564. package/dist/selftest/channel.test.js +18 -0
  565. package/dist/selftest/cli.test.js +35 -4
  566. package/dist/selftest/config.test.js +59 -0
  567. package/dist/selftest/contained.test.d.ts +2 -0
  568. package/dist/selftest/contained.test.js +544 -0
  569. package/dist/selftest/credentials.test.d.ts +2 -0
  570. package/dist/selftest/credentials.test.js +198 -0
  571. package/dist/selftest/engine.test.d.ts +2 -0
  572. package/dist/selftest/engine.test.js +127 -0
  573. package/dist/selftest/generate.test.js +1 -1
  574. package/dist/selftest/hcs.test.d.ts +2 -0
  575. package/dist/selftest/hcs.test.js +482 -0
  576. package/dist/selftest/health.test.d.ts +2 -0
  577. package/dist/selftest/health.test.js +470 -0
  578. package/dist/selftest/heavy-load.test.d.ts +2 -0
  579. package/dist/selftest/heavy-load.test.js +329 -0
  580. package/dist/selftest/host-sim.test.js +2 -0
  581. package/dist/selftest/index.js +31 -0
  582. package/dist/selftest/layout.test.js +14 -0
  583. package/dist/selftest/menu-perf.test.d.ts +2 -0
  584. package/dist/selftest/menu-perf.test.js +248 -0
  585. package/dist/selftest/menu-screens.d.ts +7 -0
  586. package/dist/selftest/menu-screens.js +68 -17
  587. package/dist/selftest/menu.test.js +603 -22
  588. package/dist/selftest/migrate.test.js +1 -1
  589. package/dist/selftest/personalization.test.js +3 -1
  590. package/dist/selftest/projects.test.js +39 -1
  591. package/dist/selftest/shared-use.test.js +64 -2
  592. package/dist/selftest/sharing.test.js +50 -1
  593. package/dist/selftest/storage.test.d.ts +2 -0
  594. package/dist/selftest/storage.test.js +363 -0
  595. package/dist/selftest/support/engine-cases.d.ts +8 -0
  596. package/dist/selftest/support/engine-cases.js +65 -0
  597. package/dist/selftest/support/engine-golden.d.ts +3 -0
  598. package/dist/selftest/support/engine-golden.js +66 -0
  599. package/dist/selftest/support/fixtures.d.ts +20 -0
  600. package/dist/selftest/support/hcs-probes.d.ts +17 -0
  601. package/dist/selftest/support/hcs-probes.js +41 -0
  602. package/dist/selftest/support/releases.js +1 -1
  603. package/dist/selftest/toolchains.test.js +21 -0
  604. package/dist/selftest/update-service.test.js +87 -3
  605. package/dist/selftest/vpn.test.d.ts +2 -0
  606. package/dist/selftest/vpn.test.js +412 -0
  607. package/dist/selftest/web.test.d.ts +2 -0
  608. package/dist/selftest/web.test.js +402 -0
  609. package/dist/shared/apps/catalogue.js +31 -0
  610. package/dist/shared/channel/index.d.ts +1 -0
  611. package/dist/shared/channel/index.js +1 -0
  612. package/dist/shared/channel/run-dir.d.ts +9 -1
  613. package/dist/shared/channel/run-dir.js +18 -1
  614. package/dist/shared/channel/schema.d.ts +53 -0
  615. package/dist/shared/channel/schema.js +38 -0
  616. package/dist/shared/channel/secrets.d.ts +29 -0
  617. package/dist/shared/channel/secrets.js +50 -0
  618. package/dist/shared/memory/index.d.ts +1 -0
  619. package/dist/shared/memory/index.js +1 -0
  620. package/dist/shared/memory/policy.d.ts +33 -0
  621. package/dist/shared/memory/policy.js +55 -0
  622. package/dist/shared/pty/output-filter.d.ts +14 -1
  623. package/dist/shared/pty/output-filter.js +22 -4
  624. package/dist/shared/pty/terminal.d.ts +4 -1
  625. package/dist/shared/pty/terminal.js +4 -2
  626. package/dist/shared/system.d.ts +1 -0
  627. package/dist/shared/system.js +1 -0
  628. package/dist/shared/vpn/download.d.ts +29 -0
  629. package/dist/shared/vpn/download.js +30 -0
  630. package/dist/shared/vpn/kill-switch.d.ts +18 -0
  631. package/dist/shared/vpn/kill-switch.js +81 -0
  632. package/dist/shared/vpn/ovpn.d.ts +23 -0
  633. package/dist/shared/vpn/ovpn.js +244 -0
  634. package/dist/shared/vpn/wireguard.d.ts +51 -0
  635. package/dist/shared/vpn/wireguard.js +227 -0
  636. package/dist/shared/web/index.d.ts +1 -0
  637. package/dist/shared/web/index.js +1 -0
  638. package/dist/shared/web/protocol.d.ts +61 -0
  639. package/dist/shared/web/protocol.js +35 -0
  640. package/dist/sync/sync-cycle.js +9 -5
  641. package/dist/sync/sync-state.d.ts +1 -0
  642. package/dist/sync/sync-state.js +1 -0
  643. package/dist/sync/watcher.d.ts +13 -0
  644. package/dist/sync/watcher.js +102 -3
  645. package/dist/web/index.html +14 -0
  646. package/dist/web/main-NRRS5O6Z.js +157 -0
  647. package/dist/web/main-T5DMCHYV.css +1 -0
  648. package/native/csharp/FolderSecurity.cs +29 -0
  649. package/native/csharp/HcsHost/AssemblyInfo.cs +14 -0
  650. package/native/csharp/HcsHost/Guard.cs +100 -0
  651. package/native/csharp/HcsHost/Hcs.cs +256 -0
  652. package/native/csharp/HcsHost/Json.cs +78 -0
  653. package/native/csharp/HcsHost/Program.cs +185 -0
  654. package/native/csharp/HcsHost/VirtualDisk.cs +95 -0
  655. package/native/csharp/HostTools/AssemblyInfo.cs +14 -0
  656. package/native/csharp/HostTools/MemoryStatus.cs +107 -0
  657. package/native/csharp/HostTools/Program.cs +58 -17
  658. package/native/scripts/check-contained.ps1 +28 -0
  659. package/native/scripts/contained-common.ps1 +241 -0
  660. package/native/scripts/dismount-contained.ps1 +66 -0
  661. package/native/scripts/memory-status.ps1 +34 -0
  662. package/native/scripts/mount-contained.ps1 +139 -0
  663. package/native/scripts/protect-data.ps1 +29 -0
  664. package/native/scripts/read-folder-sddl.ps1 +10 -0
  665. package/native/scripts/run-elevated.ps1 +17 -4
  666. package/native/scripts/set-folder-access.ps1 +49 -7
  667. package/native/scripts/stop-events.ps1 +23 -8
  668. package/node_modules/ws/LICENSE +20 -0
  669. package/node_modules/ws/README.md +548 -0
  670. package/node_modules/ws/browser.js +8 -0
  671. package/node_modules/ws/index.js +22 -0
  672. package/node_modules/ws/lib/buffer-util.js +131 -0
  673. package/node_modules/ws/lib/constants.js +19 -0
  674. package/node_modules/ws/lib/event-target.js +292 -0
  675. package/node_modules/ws/lib/extension.js +203 -0
  676. package/node_modules/ws/lib/limiter.js +55 -0
  677. package/node_modules/ws/lib/permessage-deflate.js +530 -0
  678. package/node_modules/ws/lib/receiver.js +743 -0
  679. package/node_modules/ws/lib/sender.js +607 -0
  680. package/node_modules/ws/lib/stream.js +161 -0
  681. package/node_modules/ws/lib/subprotocol.js +62 -0
  682. package/node_modules/ws/lib/validation.js +152 -0
  683. package/node_modules/ws/lib/websocket-server.js +562 -0
  684. package/node_modules/ws/lib/websocket.js +1421 -0
  685. package/node_modules/ws/package.json +74 -0
  686. package/node_modules/ws/wrapper.mjs +21 -0
  687. package/package.json +22 -5
  688. package/sandbox/csharp/EnvBroadcast.cs +33 -0
  689. package/sandbox/csharp/MemGov.cs +398 -0
  690. package/sandbox/csharp/PathSync.cs +90 -0
  691. package/sandbox/csharp/PtyHost.cs +111 -5
  692. package/sandbox/scripts/install-shell-path.ps1 +73 -0
  693. package/sandbox/scripts/set-machine-environment.ps1 +37 -4
  694. package/schemas/config.schema.json +136 -7
  695. package/schemas/sandbox-config.schema.json +17 -4
  696. package/dist/cli/commands/repair/checks.d.ts +0 -2
  697. package/dist/cli/commands/repair/checks.js +0 -246
  698. package/dist/cli/commands/sessions/control.d.ts +0 -4
  699. package/dist/cli/commands/sessions/control.js +0 -66
  700. package/dist/cli/commands/sessions/list.d.ts +0 -2
  701. package/dist/cli/commands/sessions/list.js +0 -45
  702. package/dist/cli/commands/sessions/status.d.ts +0 -2
  703. package/dist/cli/commands/sessions/status.js +0 -93
  704. package/dist/cli/commands/sessions/window.d.ts +0 -3
  705. package/dist/cli/commands/sessions/window.js +0 -18
  706. package/dist/cli/menu/pages/start-page.d.ts +0 -43
  707. package/dist/cli/menu/pages/start-page.js +0 -192
  708. package/dist/cli/tui/tab-bar.d.ts +0 -9
  709. package/dist/cli/tui/tab-bar.js +0 -33
@@ -182,7 +182,7 @@ export const migrateSuite = {
182
182
  const backups = t.at('cmd-storage', 'backups');
183
183
  const made = fs.readdirSync(backups);
184
184
  fs.writeFileSync(cmdConfig, 'broken by hand');
185
- await runMigrate(service, { dryRun: false, restore: path.join(backups, made[0]) }, say);
185
+ await runMigrate(service, { dryRun: false, restore: path.join(backups, made[0]), yes: true }, say);
186
186
  t.check(made.length === 1 && readText(cmdConfig).includes('"toolchains"'), 'the run kept one backup and --restore puts the saved files back');
187
187
  t.check(!autoMigrates('self-test', false) &&
188
188
  !autoMigrates('migrate', false) &&
@@ -45,7 +45,9 @@ export const personalizationSuite = {
45
45
  const read = () => JSON.parse(fs.readFileSync(settings, 'utf8'));
46
46
  syncPwshProfile(data, true);
47
47
  const untouched = fs.readFileSync(settings, 'utf8');
48
- t.check(untouched.startsWith('// edited'), 'untouched until pwsh is installed (comments and trailing commas survive)');
48
+ t.check(untouched.startsWith('// edited') &&
49
+ /"guid": "\{x\}",\s*\}/.test(untouched) &&
50
+ /"reloadEnvironmentVariables":\s*true/.test(untouched), 'until pwsh is installed only the reload switch is added (comments and trailing commas survive)');
49
51
  t.write(path.join(data, 'tools', 'pwsh', 'pwsh.exe'), 'MZ');
50
52
  syncPwshProfile(data, true);
51
53
  const added = read();
@@ -9,7 +9,7 @@ import { buildWsb } from '../host/launch/index.js';
9
9
  import { projectSpec, joinRunning } from '../host/projects/index.js';
10
10
  import { allProjectsToMap, KnownProjects } from '../host/projects/index.js';
11
11
  import { NeedsRestart, projectMapper } from '../host/projects/index.js';
12
- import { parseWsbBoot, ProjectRegistry } from '../host/projects/index.js';
12
+ import { parseWsbBoot, ProjectRegistry, runningRegistry } from '../host/projects/index.js';
13
13
  import { RepoGuardStore } from '../host/guard/index.js';
14
14
  import { findWsb, parseCommands, parseIp, parseList, parseVersion } from '../host/sandbox/index.js';
15
15
  import { baseEnvFor } from '../guest/boot/project-env.js';
@@ -131,6 +131,44 @@ export const projectsSuite = {
131
131
  web.guest === 'C:\\Projects\\proj-root\\web' &&
132
132
  noWsb.register(inRoot).name === web.name &&
133
133
  new ProjectRegistry(storage).read()?.projects.filter((p) => p.host === inRoot).length === 1, 'a folder inside a root joins at once, and only once');
134
+ t.section('projects: a registry another copy wrote, lost or left from an earlier sandbox');
135
+ const lostStorage = t.at('proj-lost');
136
+ const lostData = t.dir('proj-lost', 'data', 'testdata');
137
+ const lostRoot = t.dir('proj-lost-root');
138
+ const lostWeb = t.dir('proj-lost-root', 'web');
139
+ const lostLaunch = '1700000000001-abc123';
140
+ t.write(path.join(lostStorage, 'launches', lostLaunch, 'session.json'), JSON.stringify({
141
+ launchId: lostLaunch,
142
+ workspace: 'C:\\Workspace\\work-a',
143
+ workspaceHost: f.workA,
144
+ roots: ['C:\\Projects\\proj-lost-root'],
145
+ }));
146
+ t.write(path.join(lostStorage, 'launches', lostLaunch, 'agent.wsb'), '<Configuration><MappedFolders><MappedFolder><HostFolder>' +
147
+ lostRoot +
148
+ '</HostFolder><SandboxFolder>C:\\Projects\\proj-lost-root</SandboxFolder><ReadOnly>false</ReadOnly>' +
149
+ '</MappedFolder></MappedFolders></Configuration>');
150
+ const lostStatus = (id) => t.write(path.join(lostData, 'run', 'sandbox.json'), JSON.stringify({
151
+ launchId: id,
152
+ phase: 'ready',
153
+ message: 'ready',
154
+ updatedAt: new Date().toISOString(),
155
+ workspaceHost: f.workA,
156
+ guestWorkspace: 'C:\\Workspace\\work-a',
157
+ headless: false,
158
+ }));
159
+ lostStatus(lostLaunch);
160
+ const lostRebuilt = runningRegistry(lostStorage, lostData, base);
161
+ t.check(lostRebuilt?.launchId === lostLaunch &&
162
+ lostRebuilt.projects[0]?.host === f.workA &&
163
+ new ProjectRegistry(lostStorage).rootHere(lostWeb)?.guest === 'C:\\Projects\\proj-lost-root', 'no registry file: it is rebuilt from the launch folder, and a folder in a root is found');
164
+ const lostFile = path.join(lostStorage, 'run', 'projects.json');
165
+ const lostWritten = JSON.parse(fs.readFileSync(lostFile, 'utf8'));
166
+ fs.writeFileSync(lostFile, JSON.stringify({ ...lostWritten, addedByANewerVersion: { x: 1 } }));
167
+ t.check(new ProjectRegistry(lostStorage).read()?.launchId === lostLaunch, 'a registry with fields from another version is still read');
168
+ fs.writeFileSync(lostFile, JSON.stringify({ ...lostWritten, launchId: '1699999999999-000000', roots: [] }));
169
+ t.check(runningRegistry(lostStorage, lostData, base)?.roots.length === 1, "a registry left from an earlier sandbox is replaced by the running one's");
170
+ lostStatus('1700000000002-def456');
171
+ t.check(runningRegistry(lostStorage, lostData, base) === null, 'no launch folder for the running sandbox: no registry (never an earlier sandbox one)');
134
172
  const needs = caught(() => noWsb.register(outside));
135
173
  const shareOff = caught(() => projectMapper({ storageRoot: storage, packageRoot: f.install, wsbShare: false }).register(outside));
136
174
  const listFails = caught(() => projectMapper({
@@ -4,7 +4,8 @@
4
4
  import fs from 'node:fs';
5
5
  import path from 'node:path';
6
6
  import { BorrowedRegistry } from '../host/projects/index.js';
7
- import { ActiveRecords, beginSharing, endSharing, launchBlocked, ownerLine, ownerRule, sharedDataPath, takeSharingNotice, } from '../host/sharing/index.js';
7
+ import { ActiveRecords, beginSharing, endSharing, launchBlocked, ownerLine, ownerRule, sharedDataPath, sharingDeps, takeSharingNotice, } from '../host/sharing/index.js';
8
+ import { takeHiddenInstallNotice } from '../host/install/index.js';
8
9
  const SID = 'S-1-5-21-1-2-3-1001';
9
10
  const GROUP = 'S-1-5-21-1-2-3-5000';
10
11
  const record = (dataPath, shared) => ({
@@ -80,10 +81,71 @@ function lifecycle(t) {
80
81
  beginSharing(deps, { ...start, shared: false });
81
82
  endSharing({ ...deps, groupSid: null }, dataPath);
82
83
  t.check(calls[2].every((c) => !c.add) && records.readAll().length === 0, 'sharing off applies the removal, never a grant; without a group only the record goes');
84
+ // Sharing off and no group entry anywhere: nothing to remove, so the permission script is not run at all.
85
+ const before = calls.length;
86
+ const asked = [];
87
+ const quiet = {
88
+ ...deps,
89
+ groupHasAccess: (dirs) => {
90
+ asked.push([...dirs]);
91
+ return false;
92
+ },
93
+ };
94
+ beginSharing(quiet, { ...start, shared: false });
95
+ t.check(calls.length === before && asked.length === 1 && asked[0].length === 4, 'sharing off with no group entry on the four folders skips the permission change');
96
+ beginSharing({ ...quiet, groupHasAccess: () => true }, { ...start, shared: false });
97
+ beginSharing({ ...quiet, groupHasAccess: () => false }, start);
98
+ t.check(calls.length === before + 2 && !calls[before].some((c) => c.add) && calls[before + 1].every((c) => c.add), 'a group entry (or one that cannot be read) is removed as before, and sharing on always writes its entries');
99
+ const failing = {
100
+ ...deps,
101
+ groupHasAccess: () => true,
102
+ apply: () => {
103
+ throw new Error('denied');
104
+ },
105
+ };
106
+ let refused = false;
107
+ try {
108
+ beginSharing(failing, { ...start, shared: false });
109
+ }
110
+ catch {
111
+ refused = true;
112
+ }
113
+ t.check(refused, 'a removal that fails refuses the launch');
114
+ endSharing(deps, dataPath);
115
+ // The all-users install is found from any copy of the program (an npm copy, a checkout), never skipped silently.
116
+ const savedPf = process.env.ProgramFiles;
117
+ const pf = t.dir('shared', 'Program Files');
118
+ const copy = path.join(t.dir('shared', 'npm'), 'node_modules', 'windows-development-sandbox');
119
+ try {
120
+ process.env.ProgramFiles = pf;
121
+ t.check(sharingDeps(copy) === null, 'with no all-users install on the PC an npm copy shares nothing');
122
+ t.writeJson(path.join(pf, 'WindowsDevelopmentSandbox', 'install.json'), {
123
+ scope: 'machine',
124
+ groupSid: GROUP,
125
+ autoUpdate: false,
126
+ installedAt: '2026-10-01T00:00:00Z',
127
+ });
128
+ t.equal(sharingDeps(copy)?.groupSid, GROUP, 'with one, an npm copy uses its group and shares like the installed copy');
129
+ }
130
+ finally {
131
+ if (savedPf === undefined)
132
+ delete process.env.ProgramFiles;
133
+ else
134
+ process.env.ProgramFiles = savedPf;
135
+ }
136
+ const env = { ProgramFiles: pf };
137
+ const managedPackage = path.join(pf, 'WindowsDevelopmentSandbox', 'versions', '1.5.0', 'package');
138
+ const line = takeHiddenInstallNotice(storage, copy, '1.5.0', env);
139
+ t.check((line ?? '').includes(copy) && (line ?? '').includes(path.join(pf, 'WindowsDevelopmentSandbox')), 'a copy that is not the managed install says where it runs and which install it hides');
140
+ t.check(takeHiddenInstallNotice(storage, copy, '1.5.0', env) === null &&
141
+ takeHiddenInstallNotice(storage, copy, '1.5.1', env) !== null &&
142
+ takeHiddenInstallNotice(storage, managedPackage, '1.5.2', env) === null &&
143
+ takeHiddenInstallNotice(storage, copy, '1.5.3', {}) === null, 'the line comes once per version, never from the managed install itself, and never without an install to hide');
144
+ const callsBefore = calls.length;
83
145
  const own = path.join(t.dir('shared', 'profile', 'AgentSandbox'), 'data', 'default');
84
146
  beginSharing(deps, { ...start, dataPath: own });
85
147
  endSharing(deps, own);
86
- t.check(calls.length === 3 && !fs.existsSync(path.join(own, 'run')), 'a data folder outside machine\\users is never opened to the group, even with sharing on');
148
+ t.check(calls.length === callsBefore && !fs.existsSync(path.join(own, 'run')), 'a data folder outside machine\\users is never opened to the group, even with sharing on');
87
149
  const notice = takeSharingNotice(storage);
88
150
  t.check((notice ?? '').includes('Agent Sandbox Users can attach to and run commands') &&
89
151
  takeSharingNotice(storage) === null, 'the first sharing launch explains it, once');
@@ -7,7 +7,7 @@ import { spawnSync } from 'node:child_process';
7
7
  import { DEFAULTS, loadConfig, machineConfigPath } from '../core/config/index.js';
8
8
  import { ConfigSchema, LocalOverridesSchema } from '../core/schema/index.js';
9
9
  import { nativeFile, nativeScript } from '../host/native.js';
10
- import { ActiveRecords, parseTasklist, runFolderAccess, sharingAclPlan, whoOwnsSandbox, } from '../host/sharing/index.js';
10
+ import { ActiveRecords, parseTasklist, runFolderAccess, runPrivateFolder, sharingAclPlan, whoOwnsSandbox, } from '../host/sharing/index.js';
11
11
  import { SYSTEM } from '../shared/system.js';
12
12
  const ALICE_SID = 'S-1-5-21-1111111111-2222222222-3333333333-1001';
13
13
  const BOB_SID = 'S-1-5-21-1111111111-2222222222-3333333333-1002';
@@ -174,6 +174,55 @@ function folderScript(t) {
174
174
  refused = e.message;
175
175
  }
176
176
  t.includes(refused, 'link', 'a junction is refused, and nothing is changed through it');
177
+ // Nothing to change: removing an entry that is not there leaves the permissions as they were.
178
+ const before = icacls();
179
+ runFolderAccess([{ path: target, sid, rights: 'Read', scope: 'this-folder', add: false }]);
180
+ t.equal(icacls(), before, 'removing an entry that is not there changes nothing');
181
+ // A this-folder entry is written to that folder only: it stays auto-inherited (D:AI) and a child is not touched; an
182
+ // entry that changes nothing writes nothing (the change time of the folder does not move).
183
+ const sddls = (folder, also) => (spawnSync(SYSTEM.powershell(), nativeScript('read-folder-sddl.ps1', ['-Path', folder, ...(also ? ['-Also', also] : [])]), {
184
+ encoding: 'utf8',
185
+ windowsHide: true,
186
+ }).stdout ?? '')
187
+ .split(/\r?\n/)
188
+ .filter(Boolean);
189
+ const inherits = t.dir('acl-inherits');
190
+ // Set-Acl marks the folder auto-inherited, whatever its parent says: start from that state.
191
+ runFolderAccess([{ path: inherits, sid, rights: 'Modify', scope: 'inherit', add: true }]);
192
+ const child = path.join(inherits, 'child');
193
+ fs.mkdirSync(child);
194
+ fs.writeFileSync(path.join(child, 'a.txt'), 'x');
195
+ const [folderBefore, childBefore] = sddls(inherits, child);
196
+ if (!folderBefore?.includes('D:AI')) {
197
+ t.skip('the temp folder does not inherit its permissions here, so D:AI cannot be checked');
198
+ }
199
+ else {
200
+ const childChange = fs.statSync(child).ctimeMs;
201
+ runFolderAccess([{ path: inherits, sid, rights: 'Read', scope: 'this-folder', add: true }]);
202
+ const [folderAfter, childAfter] = sddls(inherits, child);
203
+ t.check(folderAfter !== folderBefore && folderAfter.includes('D:AI'), 'a this-folder entry changes the folder and keeps it auto-inherited (D:AI)');
204
+ t.check(childAfter === childBefore && fs.statSync(child).ctimeMs === childChange, 'a this-folder entry leaves a child’s permissions byte for byte the same');
205
+ runFolderAccess([{ path: inherits, sid, rights: 'Read', scope: 'this-folder', add: true }]);
206
+ const changed = fs.statSync(inherits).ctimeMs;
207
+ runFolderAccess([{ path: inherits, sid, rights: 'Read', scope: 'this-folder', add: true }]);
208
+ runFolderAccess([{ path: inherits, sid, rights: 'Modify', scope: 'this-folder', add: false }]);
209
+ t.check(fs.statSync(inherits).ctimeMs === changed && sddls(inherits)[0] === folderAfter, 'an entry that changes nothing writes nothing');
210
+ runFolderAccess([{ path: inherits, sid, rights: 'Read', scope: 'this-folder', add: false }]);
211
+ t.equal(sddls(inherits)[0], folderBefore, 'removing the entry restores the folder exactly');
212
+ }
213
+ // A private folder: inheritance off, only you, SYSTEM and Administrators; a second run changes nothing.
214
+ const own = t.dir('acl-private');
215
+ runPrivateFolder(own);
216
+ const first = (spawnSync(SYSTEM.icacls(), [own], { encoding: 'utf8', windowsHide: true }).stdout ?? '').toLowerCase();
217
+ runPrivateFolder(own);
218
+ const second = (spawnSync(SYSTEM.icacls(), [own], { encoding: 'utf8', windowsHide: true }).stdout ?? '').toLowerCase();
219
+ t.check(first === second &&
220
+ first.includes('nt authority\\system:(oi)(ci)(f)') &&
221
+ first.includes('builtin\\administrators:(oi)(ci)(f)') &&
222
+ !first.includes('builtin\\users'), 'a private folder is closed to other users, and making it private again changes nothing');
223
+ fs.mkdirSync(path.join(own, 'child'));
224
+ fs.writeFileSync(path.join(own, 'child', 'a.txt'), 'x');
225
+ t.check(fs.readFileSync(path.join(own, 'child', 'a.txt'), 'utf8') === 'x', 'the owner can still use a private folder');
177
226
  }
178
227
  export const sharingSuite = {
179
228
  name: 'sharing',
@@ -0,0 +1,2 @@
1
+ import type { Suite } from './support/suite.js';
2
+ export declare const storageSuite: Suite;
@@ -0,0 +1,363 @@
1
+ // The data folder: the pin migration and the two that must leave it alone, the move engine (rename, copy, failures),
2
+ // the estimate and checks, the offer to move, and the path picker's helpers and screen. Temp folders only.
3
+ import fs from 'node:fs';
4
+ import path from 'node:path';
5
+ import { parse } from 'jsonc-parser';
6
+ import { DEFAULTS, machineConfigPath } from '../core/config/index.js';
7
+ import { MIGRATIONS } from '../host/migrate/index.js';
8
+ import { DEFAULTS_SNAPSHOTS } from '../host/migrate/snapshots.js';
9
+ import { describeDuration, ensureStorageRoot, estimateMove, holdsData, measureFolder, moveProblem, moveWarning, oldDefaultRoot, pinValue, planOffer, privateFolderOf, sharedDefaultRoot, StorageHistory, StorageMove, usersFolder, BIG_BYTES, } from '../host/storage/index.js';
10
+ import { PathPickerScreen } from '../cli/tui/dialogs/path-picker.js';
11
+ import { completeInput, folderNameProblem, listFolder, narrow, parentOf, splitInput, } from '../cli/tui/dialogs/path-listing.js';
12
+ const GB = 1024 ** 3;
13
+ const readText = (file) => fs.readFileSync(file, 'utf8');
14
+ const key = (name, text = '', ctrl = false) => ({ name, text, ctrl, meta: false, shift: false });
15
+ function migration(id) {
16
+ const found = MIGRATIONS.find((m) => m.id === id);
17
+ if (!found)
18
+ throw new Error('no migration ' + id);
19
+ return found;
20
+ }
21
+ function ctxOf(userDir, configPath) {
22
+ return { storageRoot: path.join(userDir, 'storage'), userDir, configPath, sandboxRunning: () => false };
23
+ }
24
+ /** Three files in two levels, 10 + 100 + 1 bytes. */
25
+ function makeTree(root) {
26
+ fs.mkdirSync(path.join(root, 'sub', 'deep'), { recursive: true });
27
+ fs.writeFileSync(path.join(root, 'a.txt'), '0123456789');
28
+ fs.writeFileSync(path.join(root, 'sub', 'b.bin'), 'b'.repeat(100));
29
+ fs.writeFileSync(path.join(root, 'sub', 'deep', 'c.txt'), 'c');
30
+ }
31
+ const noBusy = () => null;
32
+ const idle = () => false;
33
+ async function pinMigration(t) {
34
+ t.section('pin-storage-root: nobody loses sight of their data on upgrade');
35
+ const pin = migration('2026-10-pin-storage-root');
36
+ const oldDir = path.join(process.env.LOCALAPPDATA ?? '', 'AgentSandbox');
37
+ const machineFile = machineConfigPath();
38
+ fs.rmSync(oldDir, { recursive: true, force: true });
39
+ const user = t.dir('pin', 'user');
40
+ t.check(!pin.applies(ctxOf(user, null)), 'a fresh install (no config, no old folder) keeps the shared default: nothing to pin');
41
+ fs.mkdirSync(oldDir, { recursive: true });
42
+ t.check(!pin.applies(ctxOf(user, null)), 'an old folder with nothing in it is not data: nothing to pin');
43
+ fs.writeFileSync(path.join(oldDir, 'x.txt'), 'data');
44
+ t.check(pin.applies(ctxOf(user, null)), 'an old folder with data and no config.json: pinned');
45
+ const notes = [];
46
+ {
47
+ await pin.run(ctxOf(user, null), (line) => notes.push(line));
48
+ const created = path.join(user, 'config.json');
49
+ t.check(fs.existsSync(created) &&
50
+ parse(readText(created)).storage.root === '%LOCALAPPDATA%\\AgentSandbox' &&
51
+ !pin.applies(ctxOf(user, created)) &&
52
+ notes.length === 1, 'the config is created with storage.root = %LOCALAPPDATA%\\AgentSandbox, and it is applied once');
53
+ const commented = t.write(t.at('pin', 'c.json'), '// mine\n{\n "sandbox": { "memoryMb": 4096 } // keep\n}\n');
54
+ await pin.run(ctxOf(user, commented), () => undefined);
55
+ const text = readText(commented);
56
+ t.check(text.includes('// mine') &&
57
+ text.includes('// keep') &&
58
+ parse(text).sandbox.memoryMb === 4096 &&
59
+ text.includes('AgentSandbox'), 'an existing config keeps its comments and values; the pin is added');
60
+ const chosen = t.write(t.at('pin', 'chosen.json'), JSON.stringify({ storage: { root: 'D:\\Mine' } }));
61
+ t.check(!pin.applies(ctxOf(user, chosen)), 'a storage.root the user chose is never replaced');
62
+ const broken = t.write(t.at('pin', 'broken.json'), '{ not json');
63
+ t.check(!pin.applies(ctxOf(user, broken)), 'a config that cannot be read is left alone');
64
+ t.write(machineFile, JSON.stringify({ storage: { root: t.at('machine-data') } }));
65
+ try {
66
+ t.check(!pin.applies(ctxOf(user, commented.replace('c.json', 'none.json'))), 'an all-users install: nothing pinned');
67
+ }
68
+ finally {
69
+ fs.rmSync(machineFile);
70
+ }
71
+ t.section('unpin-config-defaults and follow-machine-storage leave the pin alone');
72
+ const unpin = migration('2026-10-unpin-config-defaults');
73
+ const old = DEFAULTS_SNAPSHOTS.find((s) => s.defaults.storage?.root);
74
+ if (!old)
75
+ return t.check(false, 'a snapshot with a storage.root exists');
76
+ const full = t.write(t.at('pin', 'full.json'), JSON.stringify({ ...JSON.parse(JSON.stringify(old.defaults)), sandbox: { memoryMb: 4096 } }));
77
+ await unpin.run(ctxOf(user, full), () => undefined);
78
+ const rest = parse(readText(full));
79
+ t.check(rest.storage?.root === old.defaults.storage.root && rest.sandbox?.memoryMb === 4096, 'a full old config keeps its pinned storage.root (the old default no longer is the default)');
80
+ t.check(!unpin.applies(ctxOf(user, full)), 'and the migration is then done');
81
+ const follow = migration('2026-10-follow-machine-storage');
82
+ const pinned = t.write(t.at('pin', 'pinned.json'), JSON.stringify({ storage: { root: DEFAULTS.storage.root } }));
83
+ const oldPinned = t.write(t.at('pin', 'oldpinned.json'), JSON.stringify(old.defaults));
84
+ const partial = t.write(t.at('pin', 'partial.json'), JSON.stringify({ storage: { root: '%LOCALAPPDATA%\\AgentSandbox' } }));
85
+ const shared = t.at('machine-users');
86
+ t.write(machineFile, JSON.stringify({ storage: { root: shared } }));
87
+ try {
88
+ t.check(!follow.applies(ctxOf(user, oldPinned)), 'with an all-users install, a pin that holds the data is kept while the shared folder is empty');
89
+ t.check(follow.applies(ctxOf(user, pinned)), 'a pin of the shared default still follows the machine config');
90
+ t.check(!follow.applies(ctxOf(user, partial)), 'a pin written on its own (not an old full init) is never dropped');
91
+ fs.mkdirSync(shared, { recursive: true });
92
+ fs.writeFileSync(path.join(shared, 'x'), '1');
93
+ t.check(follow.applies(ctxOf(user, oldPinned)), 'once the shared folder has data, the old pin is dropped as before');
94
+ }
95
+ finally {
96
+ fs.rmSync(machineFile);
97
+ fs.rmSync(oldDir, { recursive: true, force: true });
98
+ }
99
+ }
100
+ }
101
+ async function moveEngine(t) {
102
+ t.section('storage move: rename on one drive');
103
+ const root = t.dir('mv');
104
+ const config = t.write(t.at('mv', 'config.json'), '// mine\n{ "sandbox": { "memoryMb": 4096 } }\n');
105
+ const src = t.at('mv', 'src');
106
+ makeTree(src);
107
+ const measured = measureFolder(src);
108
+ t.equal({ files: measured.files, bytes: measured.bytes, partial: measured.partial }, { files: 3, bytes: 111, partial: false }, 'measuring counts files and bytes');
109
+ const mover = new StorageMove(config, noBusy, idle);
110
+ const dst = t.at('mv', 'dst');
111
+ const renamed = await mover.move(src, dst);
112
+ t.check(!renamed.copied &&
113
+ renamed.removedOld &&
114
+ !fs.existsSync(src) &&
115
+ measureFolder(dst).files === 3 &&
116
+ fs.readFileSync(path.join(dst, 'sub', 'b.bin'), 'utf8').length === 100, 'same drive: the folders are renamed, nothing is copied, the old folder is gone');
117
+ const written = readText(config);
118
+ t.check(written.includes('// mine') &&
119
+ parse(written).storage.root === dst &&
120
+ renamed.pinned === dst, 'storage.root is pinned in the config (the literal path), comments kept');
121
+ t.section('storage move: copy to another drive (forced), progress, keep old');
122
+ const src2 = t.at('mv', 'src2');
123
+ makeTree(src2);
124
+ const progress = [];
125
+ const copied = await mover.move(src2, t.at('mv', 'dst2'), { forceCopy: true, onProgress: (p) => progress.push(p) });
126
+ const last = progress[progress.length - 1];
127
+ t.check(copied.copied &&
128
+ copied.files === 3 &&
129
+ copied.bytes === 111 &&
130
+ copied.removedOld &&
131
+ !fs.existsSync(src2) &&
132
+ measureFolder(t.at('mv', 'dst2')).bytes === 111 &&
133
+ last?.files === 3, 'copy: files and bytes verified, progress reported, the old folder removed');
134
+ const src3 = t.at('mv', 'src3');
135
+ makeTree(src3);
136
+ const kept = await mover.move(src3, t.at('mv', 'dst3'), { keepOld: true });
137
+ t.check(kept.copied && !kept.removedOld && measureFolder(src3).files === 3 && kept.notes.join().includes('kept'), '--keep-old copies even on one drive and leaves the old folder');
138
+ t.section('storage move: failures leave everything as it was');
139
+ const blocker = t.write(t.at('mv', 'blocker'), 'a file where a folder should be');
140
+ const badConfig = path.join(blocker, 'config.json');
141
+ const failing = new StorageMove(badConfig, noBusy, idle);
142
+ for (const force of [false, true]) {
143
+ const s = t.at('mv', force ? 'fail-copy' : 'fail-rename');
144
+ makeTree(s);
145
+ const target = t.at('mv', force ? 'fail-copy-to' : 'fail-rename-to');
146
+ await t.throws(() => failing.move(s, target, { forceCopy: force }), /./, 'a config that cannot be written fails the move');
147
+ t.check(measureFolder(s).files === 3 && !fs.existsSync(target), (force ? 'copy' : 'rename') + ': the old folder is whole and nothing is left in the target');
148
+ }
149
+ const existing = t.dir('mv', 'empty-target');
150
+ const s4 = t.at('mv', 'fail-existing');
151
+ makeTree(s4);
152
+ await t.throws(() => failing.move(s4, existing, { forceCopy: true }), /./, 'a failed copy into an existing empty folder');
153
+ t.check(fs.existsSync(existing) && fs.readdirSync(existing).length === 0, 'the empty folder you gave stays, emptied again');
154
+ t.section('storage move: checks before anything happens');
155
+ const a = t.dir('mv', 'chk', 'a');
156
+ fs.writeFileSync(path.join(a, 'f'), '1');
157
+ await t.throws(() => new StorageMove(config, noBusy, () => true).move(a, t.at('mv', 'chk', 'b')), 'Sandbox is running', 'refused while a sandbox runs');
158
+ await t.throws(() => new StorageMove(config, () => 'The background sync is still finishing.', idle).move(a, t.at('mv', 'chk', 'b')), 'background sync', 'refused while the background sync runs');
159
+ t.equal(moveProblem(a, path.join(a, 'in'))?.message, 'The new folder is inside the old one.', 'the target may not be inside the source');
160
+ t.equal(moveProblem(path.join(a, 'in'), a)?.message, 'The old folder is inside the new one.', 'the source may not be inside the target');
161
+ t.equal(moveProblem(a, a)?.message, 'Your data is in that folder already.', 'the same folder is refused');
162
+ t.check(moveProblem(a, path.parse(a).root)?.message.includes('whole drive') === true, 'a whole drive is refused');
163
+ const full = t.dir('mv', 'chk', 'full');
164
+ fs.writeFileSync(path.join(full, 'x'), '1');
165
+ t.check(moveProblem(a, full)?.message.includes('not empty') === true && moveProblem(a, full, true) === null, 'a target with files is refused, unless only switching');
166
+ const link = t.at('mv', 'chk', 'link');
167
+ fs.symlinkSync(t.dir('mv', 'chk', 'real'), link, 'junction');
168
+ t.check(moveProblem(a, link)?.message.includes('link') === true, 'a link is refused as a target');
169
+ t.check(moveProblem(a, t.at('mv', 'chk', 'new')) === null, 'a new folder is fine');
170
+ void root;
171
+ }
172
+ async function privateRoot(t) {
173
+ t.section('the storage root is created private under the users folder');
174
+ const users = usersFolder();
175
+ const calls = [];
176
+ const spy = (dir) => {
177
+ calls.push(dir + ':' + fs.readdirSync(dir).length);
178
+ };
179
+ const mine = path.join(users, 'alice');
180
+ fs.rmSync(users, { recursive: true, force: true });
181
+ ensureStorageRoot(mine, spy);
182
+ t.check(fs.existsSync(mine) && calls.join() === mine + ':0' && privateFolderOf(mine) === mine, 'a missing root is created (with the users folder as the user) and made private while empty');
183
+ ensureStorageRoot(mine, spy);
184
+ t.check(calls.length === 2, 'an existing empty root is made private again; ');
185
+ fs.writeFileSync(path.join(mine, 'x'), '1');
186
+ ensureStorageRoot(mine, spy);
187
+ t.check(calls.length === 2, 'a root that holds data is not touched');
188
+ const plain = t.at('plain', 'root');
189
+ ensureStorageRoot(plain, spy);
190
+ t.check(fs.existsSync(plain) && calls.length === 3 && calls[2] === plain + ':0' && privateFolderOf(plain) === null, 'elsewhere: created and made private while empty');
191
+ ensureStorageRoot(plain, spy);
192
+ t.check(calls.length === 3, 'an existing folder elsewhere is never touched (it may hold a big tree)');
193
+ const noPermissions = t.at('plain2', 'root');
194
+ ensureStorageRoot(noPermissions, () => {
195
+ throw new Error('drive without permissions');
196
+ });
197
+ t.check(fs.existsSync(noPermissions), 'a drive that cannot be made private is still used');
198
+ const deep = path.join(users, 'bob', 'sub');
199
+ ensureStorageRoot(deep, spy);
200
+ t.check(calls[calls.length - 1] === path.join(users, 'bob') + ':0' && fs.existsSync(deep), 'a deeper root: the user folder is the private one');
201
+ const failing = () => {
202
+ throw new Error('no access');
203
+ };
204
+ await t.throws(() => ensureStorageRoot(path.join(users, 'carol'), failing), 'Could not make', 'a folder that cannot be made private stops the command');
205
+ const src = t.at('mvp', 'src');
206
+ makeTree(src);
207
+ const seen = [];
208
+ const config = t.at('mvp', 'config.json');
209
+ const mover = new StorageMove(config, noBusy, idle, (dir) => seen.push(dir + ':' + fs.readdirSync(dir).length));
210
+ const target = path.join(users, 'dave');
211
+ await mover.move(src, target);
212
+ t.check(seen[0] === target + ':0' && measureFolder(target).files === 3, 'a move into the users folder makes the target private before writing');
213
+ }
214
+ function estimates(t) {
215
+ t.section('estimate, warnings and the pinned form');
216
+ const same = estimateMove({ bytes: 50 * GB, files: 400000, partial: false }, true, null);
217
+ t.check(same.sameVolume && same.seconds <= 10 && same.fits && same.big, 'same drive: seconds, always fits, big when big');
218
+ const copy = estimateMove({ bytes: 8 * GB, files: 100000, partial: false }, false, 100 * GB);
219
+ t.check(copy.seconds > 250 && copy.seconds < 350 && copy.fits && copy.big, '8 GB, 100k files: about 5 minutes (80 MB/s plus a cost per file)');
220
+ const tight = estimateMove({ bytes: 8 * GB, files: 10, partial: false }, false, 8 * GB);
221
+ t.check(!tight.fits && (moveWarning({ bytes: 8 * GB, files: 10, partial: false }, tight) ?? '').includes('free'), 'not enough room: it does not fit, and the warning says so');
222
+ const small = estimateMove({ bytes: 10 * 1024 * 1024, files: 20, partial: false }, false, 100 * GB);
223
+ t.check(!small.big && moveWarning({ bytes: 10, files: 1, partial: false }, small) === null, 'a small move has no warning');
224
+ t.check(estimateMove({ bytes: 1, files: 1, partial: false }, false, null).fits, 'unknown free space is assumed to be enough');
225
+ t.check(BIG_BYTES === 5 * GB, 'big starts at 5 GB');
226
+ t.equal([describeDuration(3), describeDuration(45), describeDuration(180), describeDuration(7200)], ['a few seconds', 'about 50 seconds', 'about 3 minutes', 'about 2 hours'], 'durations are spoken');
227
+ t.equal(pinValue(sharedDefaultRoot()), DEFAULTS.storage.root, 'the shared default is pinned in its %VARIABLE% form');
228
+ const old = oldDefaultRoot();
229
+ t.equal(old ? pinValue(old) : '', '%LOCALAPPDATA%\\AgentSandbox', 'the old default is pinned in its %VARIABLE% form');
230
+ t.equal(pinValue('D:\\Sandbox\\x'), 'D:\\Sandbox\\x', 'any other folder is pinned as the full path');
231
+ }
232
+ async function offers(t) {
233
+ t.section('the offer to move');
234
+ const savedProgramData = process.env.ProgramData;
235
+ process.env.ProgramData = t.dir('offer', 'programdata');
236
+ const oldDir = path.join(process.env.LOCALAPPDATA ?? '', 'AgentSandbox');
237
+ fs.rmSync(oldDir, { recursive: true, force: true });
238
+ try {
239
+ const history = new StorageHistory(t.dir('offer', 'user'));
240
+ const shared = sharedDefaultRoot();
241
+ t.check(planOffer(shared, history) === null, 'a fresh install: nothing to offer');
242
+ fs.mkdirSync(oldDir, { recursive: true });
243
+ fs.writeFileSync(path.join(oldDir, 'x'), '1');
244
+ const upgraded = planOffer(oldDir, history);
245
+ t.check(upgraded?.from === oldDir && upgraded.to === shared && upgraded.toIsDefault, 'after an upgrade (pinned to the old folder): offer the shared default');
246
+ const switched = planOffer(shared, history);
247
+ t.check(switched?.from === oldDir && switched.to === shared && switched.toIsDefault, 'after switching to an all-users install (empty shared folder): offer to move the old data there');
248
+ const mine = t.at('offer', 'mine');
249
+ const named = planOffer(mine, history);
250
+ t.check(named?.from === oldDir && named.to === mine && !named.toIsDefault, 'a configured folder that is empty while another holds data: offer to move it there');
251
+ const chosen = t.dir('offer', 'chosen');
252
+ fs.writeFileSync(path.join(chosen, 'y'), '1');
253
+ t.check(planOffer(chosen, history) === null, 'a folder with data that was chosen on purpose is never asked about');
254
+ history.setNotNow();
255
+ t.check(planOffer(oldDir, history) === null, '"Not now" is not asked again at once');
256
+ t.check(history.mayAsk(new Date(Date.now() + 15 * 24 * 3600 * 1000)), '... but after two weeks it is');
257
+ history.setDontAsk();
258
+ t.check(planOffer(oldDir, history) === null && !history.mayAsk(new Date(Date.now() + 400 * 24 * 3600 * 1000)), '"Don\'t ask again" is final');
259
+ const fresh = new StorageHistory(t.dir('offer', 'user2'));
260
+ fs.rmSync(oldDir, { recursive: true, force: true });
261
+ const earlier = t.dir('offer', 'earlier');
262
+ fs.writeFileSync(path.join(earlier, 'z'), '1');
263
+ fresh.remember(earlier);
264
+ fresh.remember(earlier);
265
+ t.check(fresh.read().previous.length === 1 && planOffer(shared, fresh)?.from === earlier, 'a folder remembered earlier (downgrade, then upgrade) is offered');
266
+ t.check(!holdsData(t.at('offer', 'nowhere')) && holdsData(earlier), 'holdsData: missing is no data');
267
+ }
268
+ finally {
269
+ if (savedProgramData === undefined)
270
+ delete process.env.ProgramData;
271
+ else
272
+ process.env.ProgramData = savedProgramData;
273
+ fs.rmSync(oldDir, { recursive: true, force: true });
274
+ }
275
+ }
276
+ function pickerHelpers(t) {
277
+ t.section('path picker: helpers');
278
+ t.equal(splitInput(''), { dir: '', prefix: '' }, 'nothing typed: This PC');
279
+ t.equal(splitInput('C:'), { dir: 'C:\\', prefix: '' }, 'a drive letter is its root');
280
+ t.equal(splitInput('D:\\Sand'), { dir: 'D:\\', prefix: 'Sand' }, 'the last name is the prefix');
281
+ t.equal(splitInput('D:\\Sandbox\\'), { dir: 'D:\\Sandbox\\', prefix: '' }, 'a trailing backslash names the folder');
282
+ t.equal([parentOf('D:\\a\\b'), parentOf('D:\\a'), parentOf('D:\\'), parentOf('')], ['D:\\a', 'D:\\', '', ''], 'up one level; above a drive is This PC');
283
+ const root = t.dir('pick');
284
+ for (const name of ['alpha', 'alpine', 'beta'])
285
+ fs.mkdirSync(path.join(root, name));
286
+ fs.writeFileSync(path.join(root, 'f.txt'), '1');
287
+ fs.symlinkSync(path.join(root, 'alpha'), path.join(root, 'jlink'), 'junction');
288
+ const folders = listFolder(root);
289
+ t.equal(folders.entries.map((e) => e.kind + ':' + e.name), ['dir:alpha', 'dir:alpine', 'dir:beta', 'link:jlink'], 'folders only, sorted, links marked');
290
+ t.equal(listFolder(root, true).entries.map((e) => e.name), ['alpha', 'alpine', 'beta', 'jlink', 'f.txt'], 'files come last when asked for');
291
+ const capped = listFolder(root, false, 2);
292
+ t.check(capped.entries.length === 2 && capped.more === 2, 'the listing is capped and counts the rest');
293
+ t.check(listFolder(path.join(root, 'nope')).error === 'No such folder', 'a missing folder reports why');
294
+ t.equal(narrow(folders.entries, 'al').map((e) => e.name), ['alpha', 'alpine'], 'typing narrows by start');
295
+ t.equal(narrow(folders.entries, '', 'ET').map((e) => e.name), ['beta'], 'the filter matches anywhere, ignoring case');
296
+ t.equal(completeInput(root + '\\al', folders.entries), root + '\\alp', 'Tab completes the shared start');
297
+ t.equal(completeInput(root + '\\alph', folders.entries), root + '\\alpha\\', 'a single match gets its backslash');
298
+ t.equal(completeInput(root + '\\zz', folders.entries), root + '\\zz', 'no match: unchanged');
299
+ t.equal(completeInput(root + '\\ALPH', folders.entries), root + '\\alpha\\', 'completion ignores case');
300
+ t.equal([
301
+ folderNameProblem('ok name'),
302
+ folderNameProblem(''),
303
+ folderNameProblem('a:b') !== null,
304
+ folderNameProblem('con') !== null,
305
+ folderNameProblem('x.') !== null,
306
+ ], [null, 'Type a name.', true, true, true], 'folder names are checked');
307
+ }
308
+ async function pickerScreen(t) {
309
+ t.section('path picker: the screen');
310
+ const root = t.at('pick');
311
+ const size = { width: 100, height: 30 };
312
+ const text = (s) => s.render(size).body.join('\n');
313
+ const only = (name) => (p) => p.toLowerCase().endsWith('\\' + name) ? true : 'only ' + name;
314
+ const screen = new PathPickerScreen({ path: ['T'], title: 'Pick', initial: root + '\\', validate: only('new1') });
315
+ const first = text(screen);
316
+ t.check(first.includes('alpha') && first.includes('jlink') && !first.includes('f.txt'), 'it opens on the folders of the start path, links shown, files hidden');
317
+ for (const ch of 'al')
318
+ await screen.handle(key(ch, ch));
319
+ const narrowed = text(screen);
320
+ t.check(narrowed.includes('alpha') && narrowed.includes('alpine') && !narrowed.includes('beta'), 'typing narrows the list live');
321
+ await screen.handle(key('tab'));
322
+ await screen.handle(key('h', 'h'));
323
+ await screen.handle(key('tab'));
324
+ t.includes(text(screen), root + '\\alpha\\', 'Tab completes the name');
325
+ const shown = screen.render(size);
326
+ t.check(shown.keys.some(([k]) => k === 'tab') && shown.keys.some(([k]) => k === 'esc'), 'the key bar lists the keys');
327
+ const cancelled = await screen.handle(key('escape'));
328
+ t.check(cancelled !== undefined && cancelled.done === undefined, 'Esc cancels');
329
+ const second = new PathPickerScreen({ path: ['T'], title: 'Pick', initial: root + '\\', validate: only('new1') });
330
+ await second.handle(key('down'));
331
+ await second.handle(key('n', 'n'));
332
+ for (const ch of 'new1')
333
+ await second.handle(key(ch, ch));
334
+ await second.handle(key('return'));
335
+ t.check(fs.existsSync(path.join(root, 'new1')), 'n asks for a name and creates the folder, then opens it');
336
+ const picked = await second.handle(key('s', 's'));
337
+ t.check(picked?.done === path.join(root, 'new1'), 's picks the folder shown');
338
+ const nope = new PathPickerScreen({ path: ['T'], title: 'Pick', initial: root + '\\', validate: only('zzz') });
339
+ await nope.handle(key('down'));
340
+ const refused = await nope.handle(key('s', 's'));
341
+ t.check(refused === undefined && text(nope).includes('only zzz'), 'a path the check refuses is not picked and says why');
342
+ // Move onto the link row: names sort alpha, alpine, beta, jlink, new1 -> jlink is the fifth entry after "..".
343
+ const fourth = new PathPickerScreen({ path: ['T'], title: 'Pick', initial: root + '\\' });
344
+ await fourth.handle(key('down'));
345
+ for (let i = 0; i < 4; i++)
346
+ await fourth.handle(key('down'));
347
+ await fourth.handle(key('return'));
348
+ t.check((fourth.render(size).notice ?? '').includes('link'), 'a link is not opened: a notice says so');
349
+ t.check(!text(fourth).includes('Nothing here'), 'and the folder stays as it was');
350
+ }
351
+ export const storageSuite = {
352
+ name: 'storage',
353
+ title: 'data folder: pin migration, move engine, offer, path picker',
354
+ run: async (t) => {
355
+ await pinMigration(t);
356
+ await moveEngine(t);
357
+ await privateRoot(t);
358
+ estimates(t);
359
+ await offers(t);
360
+ pickerHelpers(t);
361
+ await pickerScreen(t);
362
+ },
363
+ };
@@ -0,0 +1,8 @@
1
+ import type { Config, ResolvedMapping } from '../../core/types.js';
2
+ export interface EngineCase {
3
+ readonly name: string;
4
+ readonly config: Config;
5
+ readonly memoryMb: number;
6
+ readonly mappings: readonly ResolvedMapping[];
7
+ }
8
+ export declare const ENGINE_CASES: readonly EngineCase[];