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
@@ -5,6 +5,10 @@ import fs from 'node:fs';
5
5
  import path from 'node:path';
6
6
  import { ALL_CHECKS } from '../cli/commands/repair/checks/index.js';
7
7
  import { proxyProblem } from '../cli/commands/repair/checks/network.js';
8
+ import { healthPage, ioEvents, summaryText as featureSummaryText, } from '../app/features/health/index.js';
9
+ import { fakeIO } from '../app/kernel/index.js';
10
+ import { runDefinition } from '../app/registry.js';
11
+ import { menuView } from '../cli/menu/render-view.js';
8
12
  import { HealthRun, summarize } from '../cli/commands/repair/engine.js';
9
13
  import { logText, writeRepairLog } from '../cli/commands/repair/log.js';
10
14
  import { HealthPrinter, summaryText } from '../cli/commands/repair/printer.js';
@@ -455,6 +459,72 @@ function backups(t) {
455
459
  t.equal(found[0].label, '1.4.0 to 1.5.0 at 2026-10-14 09:30:12', 'a backup is labelled with its versions and time');
456
460
  t.equal(lineDiff('{\n "a": 1,\n "x": 3\n}', '{\n "a": 2,\n "x": 3\n}'), ['- "a": 1,', '+ "a": 2,'], 'the difference shows the lines only in one file');
457
461
  }
462
+ /** The shared core: the same run as the menu and the web use, the saved results, and the health page. */
463
+ async function sharedCore(t) {
464
+ t.section('the shared core');
465
+ const bad = (plan, over = {}) => ({
466
+ status: 'needs-you',
467
+ detail: 'broken',
468
+ plan,
469
+ means: 'it hurts',
470
+ ...over,
471
+ });
472
+ const cracks = [];
473
+ const three = [
474
+ check('p1', 'This PC', { name: 'Windows' }),
475
+ check('p2', 'Data folder', {
476
+ name: 'Cracks',
477
+ run: () => (cracks.length ? { status: 'ok', detail: 'whole' } : bad('mend the cracks', { fix: undefined })),
478
+ fix: () => {
479
+ cracks.push('mended');
480
+ return { done: 'mended the cracks' };
481
+ },
482
+ }),
483
+ check('p3', 'Network', {
484
+ name: 'GitHub',
485
+ run: () => ({ status: 'warn', detail: 'no answer', fix: 'check your VPN' }),
486
+ }),
487
+ ];
488
+ const fake = fakeIO();
489
+ await runHealth(fakeEnv(t, 'events'), ioEvents(fake.io), { yes: true, ask: false, question: () => '' }, three, () => Promise.resolve(true));
490
+ const logged = fake.seen.logs.map((l) => l.text).join('\n');
491
+ t.includes(logged, ' [ok] Windows: p1 is fine', 'ioEvents writes a line per result, like the printer');
492
+ t.includes(logged, 'What it means: it hurts', 'ioEvents says what a problem means');
493
+ t.includes(logged, 'Fix: sandbox repair will mend the cracks', 'ioEvents says what repair would do');
494
+ t.includes(logged, 'Repair will change:', 'ioEvents lists the changes before they are made');
495
+ t.includes(logged, '[fixed] Cracks: mended the cracks', 'ioEvents reports a fix on its own line');
496
+ t.equal(featureSummaryText({ total: 9, ok: 6, fixed: 1, warn: 1, needsYou: 1, skipped: 0, fixable: 0 }), '6 ok, 1 fixed, 1 needs you, 1 warning', 'summaryText moved to the feature and reads the same');
497
+ t.equal(featureSummaryText({ total: 3, ok: 0, fixed: 0, warn: 0, needsYou: 2, skipped: 1, fixable: 0 }), '0 ok, 2 need you, 1 skipped', 'summaryText plurals (feature)');
498
+ const saved = {
499
+ at: '2026-10-15T09:30:12.000Z',
500
+ mode: 'check',
501
+ checks: [{ id: 'p1', group: 'This PC', name: 'Windows', status: 'ok', detail: 'fine', fixable: false }],
502
+ summary: { total: 1, ok: 1, fixed: 0, warn: 0, needsYou: 0, skipped: 0, fixable: 0 },
503
+ details: [['Config file', 'valid']],
504
+ setup: { checks: [{ name: 'Edition', status: 'ok', detail: 'Pro' }], canEnable: false, error: null },
505
+ log: null,
506
+ };
507
+ const storage = t.dir('health', 'saved', 'storage');
508
+ const ctx = { storageRoot: storage };
509
+ const none = await runDefinition('health.last', ctx, {}, fakeIO().io);
510
+ t.equal(none, { last: null }, 'nothing saved yet: no results');
511
+ t.writeJson(path.join(storage, 'health', 'last.json'), saved);
512
+ const read = await runDefinition('health.last', ctx, {}, fakeIO().io);
513
+ t.equal(read, { last: saved }, 'a check is saved and read back by health.last');
514
+ t.write(path.join(storage, 'health', 'last.json'), '{ this is not json');
515
+ t.equal(await runDefinition('health.last', ctx, {}, fakeIO().io), { last: null }, 'a damaged results file reads as nothing saved');
516
+ const empty = healthPage({ last: null });
517
+ const emptyIds = empty.sections.flatMap((s) => (s.items ?? []).map((i) => i.id));
518
+ t.equal(emptyIds, ['health:check', 'repair:run'], 'before any check the page offers the check and the repair');
519
+ t.equal(menuView(empty, { category: 'health', refresh: false }).enterLabel('health', 'repair:run'), 'check and fix', 'Enter on the repair row says check and fix');
520
+ const page = healthPage({ last: saved });
521
+ const ids = page.sections.flatMap((s) => (s.items ?? []).map((i) => i.id));
522
+ t.check(['doctor:0', 'repair:0', 'setup:0'].every((id) => ids.includes(id)), 'with results the page lists the doctor, repair and setup rows', ids.join(', '));
523
+ const keys = menuView(page, { category: 'health', refresh: false }).actions.map((a) => a.key);
524
+ t.equal(keys, ['f', 'l'], 'the health keys are f and l (e only when Windows Sandbox can be turned on)');
525
+ const canEnable = healthPage({ last: { ...saved, setup: { ...saved.setup, canEnable: true } } });
526
+ t.equal(menuView(canEnable, { category: 'health', refresh: false }).actions.map((a) => a.key), ['f', 'l', 'e'], 'e turns Windows Sandbox on when the setup says it can be');
527
+ }
458
528
  export const healthSuite = {
459
529
  name: 'health',
460
530
  title: 'doctor and repair (engine, printer, log, the install, stale state, permissions, settings backups)',
@@ -466,5 +536,6 @@ export const healthSuite = {
466
536
  await staleState(t);
467
537
  parsing(t);
468
538
  backups(t);
539
+ await sharedCore(t);
469
540
  },
470
541
  };
@@ -8,11 +8,13 @@ import { DEFAULTS } from '../core/config/index.js';
8
8
  import { isWin } from '../core/util/index.js';
9
9
  import { SYSTEM } from '../shared/system.js';
10
10
  import { GuestRequestSchema, PressureStatusSchema } from '../shared/channel/index.js';
11
- import { GUEST_THRESHOLDS, HOST_THRESHOLDS, cargoJobs, effectiveCpuLimit, gentleCpuLimit, } from '../shared/memory/index.js';
12
- import { agentMemoryLimitMb, decideMemory, memoryNote, resolveMemoryMb } from '../host/launch/index.js';
11
+ import { compilersAllowed, DIAL_CALM_SAMPLES, DIAL_STEPS, HOST_THRESHOLDS, buildJobs, cargoJobs, linkSlotsFor, usableCpus, effectiveCpuLimit, gentleCpuLimit, maxCompilers, } from '../shared/memory/index.js';
12
+ import { cappedMemoryMb, decideMemory, memoryNote, residentCapMb, residentNote, resolveMemoryMb, } from '../host/launch/index.js';
13
13
  import { vgpuWarning, GENTLE_NOTE } from '../host/launch/risk-notes.js';
14
- import { bugcheckCode, cpuPercentBetween, explainStop, findDumps, findingLines, freezeLine, hostPressure, lowReason, pageFileRisk, parseHostMemory, crashReportText, watchdogStep, WATCHDOG_START, } from '../host/life/index.js';
15
- import { canLower, canPause, decide, GovernorCore, INITIAL_STATE, } from '../guest/governor/index.js';
14
+ import { bugcheckCode, cpuPercentBetween, explainStop, findDumps, findingLines, freezeLine, hostPressure, lowReason, severeHost, pageFileRisk, parseHostMemory, crashReportText, watchdogStep, WATCHDOG_START, } from '../host/life/index.js';
15
+ // The pure governor files only: governor.ts loads the guest runtime (session.json), which exists only in a sandbox.
16
+ import { canLower, canPause, decide, INITIAL_STATE, ROTATE_MS, } from '../guest/governor/decide.js';
17
+ import { GovernorCore } from '../guest/governor/core.js';
16
18
  const GB = 1024;
17
19
  const MB_SANDBOX = DEFAULTS.sandbox;
18
20
  const proc = (pid, name, mb, prio = 'normal') => ({
@@ -59,17 +61,22 @@ export const heavyLoadSuite = {
59
61
  refused = String(e instanceof Error ? e.message : e);
60
62
  }
61
63
  t.check(refused.includes('RAM plus paging file'), 'too little commit refuses with the real reason', refused);
62
- t.check(resolveMemoryMb({ ...MB_SANDBOX, memoryMb: 5000 }, 1, 1, 1) === 5000, 'a fixed memoryMb is never changed');
63
- t.section("the agents' memory limit");
64
- t.check(agentMemoryLimitMb(32768, 'auto') === 32768 - 3276, 'auto: sandbox memory minus 10 %');
65
- t.check(agentMemoryLimitMb(8192, 'auto') === 8192 - 2048, 'auto: at least 2 GB of headroom');
66
- t.check(agentMemoryLimitMb(4096, 'auto') === 2048, 'a small sandbox keeps at least half for agents');
67
- t.check(agentMemoryLimitMb(16384, 1000) === 15384 && agentMemoryLimitMb(16384, 'none') === null, 'a number, or none');
64
+ t.check(resolveMemoryMb({ ...MB_SANDBOX, memoryMb: 5000 }, 1, 1, 1) === 5000, 'a fixed memoryMb is taken as asked (the resident cap below is the only limit on it)');
65
+ t.check(residentCapMb({ ...MB_SANDBOX, vGpu: false }, 32768) === 24576 &&
66
+ residentCapMb({ ...MB_SANDBOX, vGpu: true }, 32768) === 22528 &&
67
+ residentCapMb({ ...MB_SANDBOX, vGpu: false }, 65536) === 49152, 'what this PC can keep in RAM for the sandbox: its RAM minus max(reserve, 25 %), 2 GB more with vGPU');
68
+ t.check(cappedMemoryMb(32768, { ...MB_SANDBOX, vGpu: true }, 32768) === 22528 &&
69
+ cappedMemoryMb(16384, { ...MB_SANDBOX, vGpu: true }, 32768) === 16384 &&
70
+ cappedMemoryMb(8192, MB_SANDBOX, 6144) === 8192, 'a fixed memoryMb above it is lowered to it (never on a PC too small to keep 4 GB)');
71
+ t.check(residentNote(32768, 22528)?.includes('gets 22528 MB') === true && residentNote(16384, 16384) === null, 'and the launch says so');
72
+ t.check(decideMemory({ ...MB_SANDBOX, maxMemoryPercent: 100, maxMemoryMb: 65536, reserveHostMb: 1024 }, 32000, 32768, null).limitedBy === 'resident', '"auto" never goes above it either');
73
+ t.section("the agents' job backstop");
68
74
  const gen = await f.launch({}, { args: ['--version'] });
69
75
  const session = JSON.parse(fs.readFileSync(gen.sessionPath, 'utf8'));
70
- t.check(session.agentMemoryLimitMb === agentMemoryLimitMb(gen.memoryMb, 'auto') &&
76
+ t.check(session.agentBackstop === 'auto' &&
77
+ session.agentMemoryLimitMb === undefined &&
71
78
  session.pressureResponse === true &&
72
- session.loadProfile === 'normal', 'session.json carries the limit, the governor switch and the profile (all on by default)', JSON.stringify(session.agentMemoryLimitMb));
79
+ session.loadProfile === 'normal', 'session.json carries the backstop setting (no limit from RAM any more), the governor switch and the profile', JSON.stringify(session.agentBackstop));
73
80
  const vgpuConfig = f.makeConfig();
74
81
  vgpuConfig.sandbox.vGpu = true;
75
82
  const gentle = await f.launch({}, { args: ['--version'], config: vgpuConfig });
@@ -82,9 +89,13 @@ export const heavyLoadSuite = {
82
89
  t.check(cargoJobs(16, 32, 1) === 4, '16 CPUs, 32 GB: 4 jobs');
83
90
  t.check(cargoJobs(16, 8, 1) === 2 && cargoJobs(16, 3, 1) === 1, 'about 4 GB per job');
84
91
  t.check(cargoJobs(16, 32, 3) === 2 && cargoJobs(16, 32, 8) === 1, 'fewer when several sessions build');
92
+ t.check(cargoJobs(32, 32, 1) === 8, '32 processors, 32 GB: 8 jobs for one session');
93
+ t.check(buildJobs(32, 32) === 8 && buildJobs(32, 64) === 19 && buildJobs(4, 64) === 4 && buildJobs(16, 4) === 2, 'the build budget: 3 GB per job after 6 GB, never more than the processors, at least 2');
94
+ t.check(linkSlotsFor(32) === 8 && linkSlotsFor(16) === 3 && linkSlotsFor(8) === 2 && linkSlotsFor(128) === 8, 'links at once ("auto"): one per 3 GB after 6 GB, 2 to 8');
95
+ t.check(usableCpus(32, 80) === 25 && usableCpus(32, null) === 32 && usableCpus(4, 10) === 2 && usableCpus(8, 100) === 8, 'the processors programs see follow the CPU cap (at least 2)');
85
96
  t.section('the governor: which programs');
86
97
  t.check(canPause('rustc.exe') && canPause('link.exe') && canPause('ld.lld.exe'), 'compilers and linkers may be paused');
87
- t.check(!canPause('node.exe') && canLower('node.exe') && canLower('MSBuild.exe'), 'build tools are only slowed down');
98
+ t.check(!canPause('node.exe') && !canLower('node.exe') && canLower('MSBuild.exe'), 'build tools are only slowed down');
88
99
  t.check([
89
100
  'claude.exe',
90
101
  'codex.exe',
@@ -95,65 +106,106 @@ export const heavyLoadSuite = {
95
106
  'svchost.exe',
96
107
  'memgov.exe',
97
108
  ].every((n) => !canLower(n) && !canPause(n)), 'agents, shells, ptyhost, git and system programs are never touched');
98
- t.section('the governor: thresholds and hysteresis');
99
- const normal = GUEST_THRESHOLDS.normal;
109
+ t.section('the governor: the dial turns up in steps, down slowly');
100
110
  const procs = [
101
111
  proc(1, 'rustc.exe', 2800),
102
112
  proc(2, 'rustc.exe', 2000),
103
113
  proc(3, 'claude.exe', 900),
104
114
  proc(4, 'link.exe', 500),
105
115
  ];
106
- const calm = decide(INITIAL_STATE, sample(50, 50, procs), 1000, normal, 'normal');
107
- t.check(calm.level === 'ok' && calm.actions.length === 0, '50 % free: nothing happens');
108
- const edge = decide(INITIAL_STATE, sample(9, 91, procs), 1000, normal, 'normal');
109
- t.check(edge.level === 'ok', '9 % free and 91 % commit are still fine (limits: 8 % and 92 %)');
110
- const low = decide(INITIAL_STATE, sample(7, 50, procs), 1000, normal, 'normal');
111
- t.check(low.level === 'throttled' &&
112
- low.actions
113
- .filter((a) => a.kind === 'priority')
114
- .map((a) => a.kind === 'priority' && a.pid)
115
- .join() === '1,2,4', '7 % free: the compilers go below normal, the agent does not');
116
- const lowCommit = decide(INITIAL_STATE, sample(50, 93, procs), 1000, normal, 'normal');
117
- t.check(lowCommit.level === 'throttled', 'commit above 92 % counts too');
118
- let s2 = INITIAL_STATE;
119
- const kinds = [];
120
- for (let i = 0; i < 6; i++) {
121
- const d = decide(s2, sample(5, 50, procs), i * 2000, normal, 'normal');
122
- s2 = d.state;
123
- kinds.push(d.level);
116
+ const of = (d, kind) => d.actions.filter((a) => a.kind === kind);
117
+ const pids = (actions) => actions
118
+ .map((a) => ('pid' in a ? a.pid : 0))
119
+ .sort()
120
+ .join();
121
+ const calm = decide(INITIAL_STATE, sample(50, 50, procs), 1000, 'normal');
122
+ t.check(calm.state.step === 0 && calm.level === 'ok' && calm.actions.length === 0, '50 % free: nothing happens');
123
+ t.check(decide(INITIAL_STATE, sample(16, 79, procs), 1000, 'normal').state.step === 0, '16 % free and 79 % commit are fine (step 1 starts at 15 % and 80 %)');
124
+ const one = decide(INITIAL_STATE, sample(14, 50, procs), 1000, 'normal');
125
+ t.check(one.state.step === 1 &&
126
+ of(one, 'priority').every((a) => a.kind === 'priority' && a.to === 'below') &&
127
+ pids(of(one, 'priority')) === '1,2,4' &&
128
+ pids(of(one, 'memory')) === '1,2,4' &&
129
+ of(one, 'hold').length === 0 &&
130
+ of(one, 'cpu').length === 0, 'step 1: build programs get lower CPU and memory priority, nothing waits, the agent is untouched');
131
+ const two = decide(INITIAL_STATE, sample(9, 50, procs), 1000, 'normal');
132
+ t.check(two.state.step === 2 &&
133
+ of(two, 'priority').every((a) => a.kind === 'priority' && a.to === 'idle') &&
134
+ pids(of(two, 'throttle')) === '1,2,4' &&
135
+ of(two, 'cpu').some((a) => a.kind === 'cpu' && a.percent === 60) &&
136
+ of(two, 'hold').length === 1, 'step 2: idle priority, compilers share 60 % of the CPU, 2 of 3 compilers go on');
137
+ const three = decide(INITIAL_STATE, sample(5, 50, procs), 1000, 'normal');
138
+ t.check(three.state.step === 3 &&
139
+ three.level === 'suspending' &&
140
+ of(three, 'cpu').some((a) => a.kind === 'cpu' && a.percent === 35) &&
141
+ of(three, 'hold').length === 2 &&
142
+ pids(of(three, 'trim')) === pids(of(three, 'hold')), 'step 3: 35 % CPU, one compiler goes on, the waiting ones are trimmed to the paging file');
143
+ t.check(decide(INITIAL_STATE, sample(50, 98, procs), 1000, 'normal').state.step === 4, 'commit above 97 % is step 4');
144
+ // A build that grabs 2 GB in two seconds while most memory is free is normal: no false alarm (it paused compilers).
145
+ const burst = decide({ ...INITIAL_STATE, lastCommitMb: 4000, lastAt: 1000 }, sample(50, 60, procs), 3000, 'normal');
146
+ t.check(burst.state.step === 0, 'a burst of commit while most memory is free does not turn the dial', 'step ' + burst.state.step);
147
+ const growing = decide({ ...INITIAL_STATE, lastCommitMb: 8400, lastAt: 1000, growth: 200 }, sample(50, 88, procs), 3000, 'normal');
148
+ t.check(growing.state.step === 4, 'commit near its limit that keeps growing (about 6 s left) turns the dial up before it gets there', 'step ' + growing.state.step);
149
+ const everything = [one, two, three, growing].flatMap((d) => d.actions);
150
+ t.check(everything.every((a) => !('pid' in a) || a.pid !== 3), 'the agent is never slowed, held or trimmed at any step');
151
+ // Hysteresis: step 2 holds while memory is between its entry (10 %) and its exit (15 %).
152
+ let h = decide(INITIAL_STATE, sample(9, 50, procs), 0, 'normal').state;
153
+ h = decide(h, sample(12, 50, procs), 2000, 'normal').state;
154
+ t.check(h.step === 2, 'between the entry and the exit of a step it stays (no flapping)');
155
+ const steps = [];
156
+ for (let i = 0; i < 2 * DIAL_CALM_SAMPLES; i++) {
157
+ h = decide(h, sample(40, 40, procs), 4000 + i * 2000, 'normal').state;
158
+ steps.push(h.step);
124
159
  }
125
- t.check(kinds[0] === 'throttled' && kinds.includes('suspending'), 'throttled first, paused later: ' + kinds.join());
126
- const oneStep = decide(INITIAL_STATE, sample(5, 50, procs), 0, normal, 'normal');
127
- t.check(!oneStep.actions.some((a) => a.kind === 'pause'), 'never paused on the first sample');
128
- // Hysteresis: 10 % free is above the 8 % entry but below the 12 % exit: still under pressure.
129
- let h = decide(INITIAL_STATE, sample(5, 50, procs), 0, normal, 'normal').state;
130
- h = decide(h, sample(10, 50, procs), 2000, normal, 'normal').state;
131
- t.check(h.pressure, 'between the entry and exit limits it stays under pressure (no flapping)');
132
- for (let i = 0; i < normal.calmSamples; i++)
133
- h = decide(h, sample(30, 50, procs), 4000 + i * 2000, normal, 'normal').state;
134
- t.check(!h.pressure, 'ends only after ' + normal.calmSamples + ' calm samples');
135
- const back = decide({ ...INITIAL_STATE, pressure: true, calm: normal.calmSamples - 1, lowered: { 1: 'below' } }, sample(30, 50, [proc(1, 'rustc.exe', 2800, 'below')]), 9000, normal, 'normal');
136
- t.check(back.level === 'ok' &&
137
- back.actions.length === 1 &&
138
- back.actions[0].kind === 'priority' &&
139
- back.actions[0].to === 'normal', 'when it ends the compilers get their normal priority back');
140
- t.check(decide(INITIAL_STATE, sample(5, 50, [proc(1, 'rustc.exe', 100)]), 0, normal, 'normal').actions.length === 1 &&
141
- !decide({ ...INITIAL_STATE, samples: 10 }, sample(5, 50, [proc(1, 'rustc.exe', 100)]), 99999, normal, 'normal').actions.some((a) => a.kind === 'pause'), 'a small compiler (under 300 MB) is never worth pausing');
142
- const hostAsked = decide({ ...INITIAL_STATE, hostUntil: 5000 }, sample(60, 40, procs), 1000, normal, 'normal');
143
- t.check(hostAsked.level === 'throttled', 'the PC asking for it counts as pressure while the request lasts');
160
+ t.check(steps.join() === '2,2,1,1,1,0', 'it turns down one step after ' + DIAL_CALM_SAMPLES + ' calm samples: ' + steps.join());
161
+ const back = decide({ ...INITIAL_STATE, step: 1, calm: DIAL_CALM_SAMPLES - 1, lowered: { 1: 'below' }, memLow: [1], cpuRate: 100 }, sample(40, 40, [proc(1, 'rustc.exe', 2800, 'below')]), 9000, 'normal');
162
+ t.check(back.state.step === 0 &&
163
+ of(back, 'priority').some((a) => a.kind === 'priority' && a.to === 'normal') &&
164
+ of(back, 'memory').some((a) => a.kind === 'memory' && !a.low), 'at step 0 the compilers get their normal CPU and memory priority back');
165
+ const uncapped = decide({ ...INITIAL_STATE, step: 2, calm: DIAL_CALM_SAMPLES - 1, cpuRate: 60 }, sample(40, 40, procs), 9000, 'normal');
166
+ t.check(uncapped.state.step === 1 && of(uncapped, 'cpu').some((a) => a.kind === 'cpu' && a.percent === 100), 'the CPU cap is lifted when the dial goes below step 2');
167
+ const hostAsked = decide({ ...INITIAL_STATE, hostUntil: 5000, hostStep: 2 }, sample(60, 40, procs), 1000, 'normal');
168
+ t.check(hostAsked.state.step >= 2, 'the PC asking for it turns the dial to at least step 2 while it lasts');
169
+ const turnA = of(decide(INITIAL_STATE, sample(5, 50, procs), 0, 'normal'), 'hold');
170
+ const turnB = of(decide(INITIAL_STATE, sample(5, 50, procs), ROTATE_MS, 'normal'), 'hold');
171
+ t.check(pids(turnA) !== pids(turnB), 'who waits changes over time, so no compiler waits for ever');
172
+ t.check(compilersAllowed(2, 10) === 6 && compilersAllowed(3, 10) === 4 && compilersAllowed(4, 10) === 1, 'how many compilers go on at steps 2, 3 and 4');
144
173
  t.section('the gentle profile (vGPU on)');
145
- const gentleT = GUEST_THRESHOLDS.gentle;
146
- t.check(gentleT.enterAvailablePercent === 15 && normal.enterAvailablePercent === 8, 'it acts at 15 % free instead of 8 %');
147
- const g = decide(INITIAL_STATE, sample(50, 50, procs), 0, gentleT, 'gentle');
148
- t.check(g.level === 'ok' &&
174
+ t.check(DIAL_STEPS.gentle[0].availablePercent > DIAL_STEPS.normal[0].availablePercent &&
175
+ DIAL_STEPS.gentle[0].commitPercent < DIAL_STEPS.normal[0].commitPercent, 'every step starts earlier');
176
+ const g = decide(INITIAL_STATE, sample(50, 50, procs), 0, 'gentle');
177
+ t.check(g.state.step === 0 &&
149
178
  g.actions.length === 3 &&
150
- g.actions.every((a) => a.kind === 'priority' && a.to === 'below' && a.pid !== 3), 'compilers start below normal even with plenty of memory (the agent does not)');
151
- t.check(decide(INITIAL_STATE, sample(14, 50, procs), 0, gentleT, 'gentle').level === 'throttled', '14 % free is pressure');
179
+ g.actions.every((a) => a.kind === 'priority' && a.to === 'below' && a.pid !== 3), 'compilers rest below normal even with plenty of memory (the agent does not)');
180
+ t.check(decide(INITIAL_STATE, sample(19, 50, procs), 0, 'gentle').state.step === 1, '19 % free is step 1');
181
+ t.section('the compiler cap (vGPU on)');
182
+ t.check(maxCompilers(16, 'gentle') === 4 && maxCompilers(4, 'gentle') === 2 && maxCompilers(16, 'normal') === null, 'at most max(2, cores / 4) with vGPU, none otherwise');
183
+ const many = [
184
+ proc(10, 'rustc.exe', 800),
185
+ proc(11, 'rustc.exe', 900),
186
+ proc(12, 'clippy-driver.exe', 700),
187
+ proc(13, 'link.exe', 100),
188
+ proc(3, 'claude.exe', 500),
189
+ proc(14, 'node.exe', 400),
190
+ ];
191
+ // The cap is a memory measure: with memory to spare no compiler waits (a waiting rustc stalls cargo's whole build).
192
+ const free = decide(INITIAL_STATE, sample(60, 40, many), 0, 'gentle', 2);
193
+ t.check(of(free, 'hold').length === 0, 'with memory to spare, nothing waits even beyond the cap');
194
+ const capped = decide(INITIAL_STATE, sample(14, 40, many), 0, 'gentle', 2);
195
+ const holds = of(capped, 'hold').map((a) => ('pid' in a ? a.pid : 0));
196
+ t.check(capped.state.step === 2 && holds.length === 2, 'under memory pressure (step 2), compilers beyond the cap wait their turn', holds.join(','));
197
+ t.check(!holds.includes(3) && !holds.includes(14), 'agents (claude, and codex or gemini as node) are never held');
198
+ t.check(of(decide(INITIAL_STATE, sample(60, 40, many), 0, 'gentle', null), 'hold').length === 0, 'no cap, no pressure: nothing waits');
199
+ t.check(of(decide(INITIAL_STATE, sample(60, 40, many.slice(0, 2)), 0, 'gentle', 2), 'hold').length === 0, 'under the cap nothing waits');
152
200
  t.section('the governor over a port (fake Windows)');
153
201
  const calls = [];
154
202
  const port = {
155
203
  setPriority: (pid, to) => calls.push('prio ' + pid + ' ' + to),
156
- pause: (pid, ms) => calls.push('pause ' + pid + ' ' + ms),
204
+ hold: (pid, ms) => calls.push('hold ' + pid + ' ' + ms),
205
+ setMemoryPriority: (pid, low) => calls.push('mem ' + pid + ' ' + (low ? 'low' : 'normal')),
206
+ throttle: (pid) => calls.push('throttle ' + pid),
207
+ setCpuRate: (percent) => calls.push('cpu ' + percent),
208
+ trim: (pid) => calls.push('trim ' + pid),
157
209
  };
158
210
  const logs = [];
159
211
  const statuses = [];
@@ -164,15 +216,29 @@ export const heavyLoadSuite = {
164
216
  now: () => clock,
165
217
  logLine: (l) => logs.push(l),
166
218
  saveStatus: (s) => statuses.push(s),
219
+ warn: (line) => logs.push(line),
167
220
  });
168
- for (let i = 0; i < 6; i++) {
221
+ for (let i = 0; i < 4; i++) {
169
222
  clock = i * 2000;
170
- core.onSample(sample(4, 60, procs));
223
+ core.onSample(sample(5, 60, procs));
171
224
  }
172
- t.check(calls.includes('prio 1 below') && calls.includes('pause 1 5000') && !calls.some((c) => c.includes(' 3 ')), 'priority first, then a pause of the biggest; the agent untouched', calls.join('; '));
173
- t.check(logs.some((l) => l.includes('memory is low')) && logs.some((l) => l.includes('paused rustc')), 'what it did goes to the memory log');
174
- t.check(statuses.length > 0 && PressureStatusSchema.safeParse(statuses[statuses.length - 1]).success, 'its status file matches the schema the host reads');
225
+ t.check(calls.includes('prio 1 idle') &&
226
+ calls.includes('mem 1 low') &&
227
+ calls.includes('cpu 35') &&
228
+ calls.some((c) => c.startsWith('hold ')) &&
229
+ calls.some((c) => c.startsWith('trim ')) &&
230
+ !calls.some((c) => / 3( |$)/.test(c)), 'priority, memory priority, CPU cap, turns and trimming go through the port; the agent untouched', calls.join('; '));
231
+ t.check(logs.some((l) => l.includes('step 3')) && logs.some((l) => l.includes('waits its turn')), 'what it did goes to the memory log');
232
+ const lastStatus = statuses[statuses.length - 1];
233
+ t.check(statuses.length > 0 && PressureStatusSchema.safeParse(lastStatus).success, 'its status file matches the schema the host reads', JSON.stringify(lastStatus));
234
+ core.hostPressure(60, 'test');
235
+ t.check(core.current.hostStep === 2, 'the PC being low asks for step 2');
236
+ core.hostPressure(60, 'test', 'severe');
237
+ t.check(core.current.hostStep === 4, 'the PC nearly out of memory asks for step 4 at once');
175
238
  core.hostPressure(60, 'test');
239
+ t.check(core.current.hostStep === 4, 'a later "low" does not lower a severe request while it lasts');
240
+ t.check(decide({ ...INITIAL_STATE, hostUntil: clock + 60000, hostStep: 4 }, sample(60, 40, procs), clock, 'normal').state
241
+ .step === 4, 'with plenty of memory in the sandbox, a severe PC still turns the dial to 4');
176
242
  t.check(core.current.hostUntil === clock + 60000, 'a host request is remembered for its seconds');
177
243
  t.section("the PC's watchdog");
178
244
  const input = (memory, nowMs, cpu = 10, profile = 'normal') => ({
@@ -184,6 +250,12 @@ export const heavyLoadSuite = {
184
250
  profile,
185
251
  });
186
252
  t.check(watchdogStep(WATCHDOG_START, input(pc(), 0)).state.low === false, 'plenty of memory: quiet');
253
+ t.check(severeHost(pc({ availableMb: 1200 })) &&
254
+ severeHost(pc({ commitAvailableMb: 1500 })) &&
255
+ !severeHost(pc({ availableMb: 3000 })), 'nearly out of memory: under 1.5 GB of RAM free, or commit within max(2 GB, 4 %) of its limit');
256
+ const lowState = watchdogStep(WATCHDOG_START, input(pc({ availableMb: 3000 }), 100000)).state;
257
+ const severeNow = watchdogStep(lowState, input(pc({ availableMb: 1200 }), 105000));
258
+ t.check(severeNow.severe && severeNow.send, 'getting severe is sent at once (not after the usual 30 seconds)');
187
259
  t.check(lowReason(pc({ availableMb: 3000 }), 8192, HOST_THRESHOLDS.normal, 1) !== '', 'under 4 GB of RAM: low');
188
260
  t.check(lowReason(pc({ availableMb: 5000 }), 8192, HOST_THRESHOLDS.normal, 1) === '', '5 GB free on a 32 GB PC is fine (limit max(4 GB, 12 % = 3.9 GB))');
189
261
  t.check(lowReason(pc({ totalMb: 65536, availableMb: 7000 }), 8192, HOST_THRESHOLDS.normal, 1) !== '', '12 % of 64 GB = 7.7 GB: 7 GB is low');
@@ -209,6 +281,16 @@ export const heavyLoadSuite = {
209
281
  }
210
282
  t.check(sent && cpu.low, 'CPU above 95 % for 60 s: asks the sandbox to slow down');
211
283
  t.check(!watchdogStep(WATCHDOG_START, input(pc(), 0, 97)).state.low, 'a short CPU peak is nothing');
284
+ let disk = WATCHDOG_START;
285
+ let diskSent = false;
286
+ for (let i = 0; i < 5; i++) {
287
+ const st = watchdogStep(disk, input(pc({ diskQueue: 40 }), i * 10000));
288
+ disk = st.state;
289
+ diskSent = diskSent || st.send;
290
+ }
291
+ t.check(diskSent && disk.low, 'a disk queue of 32 or more for 40 s: asks the sandbox to slow down');
292
+ t.check(!watchdogStep(WATCHDOG_START, input(pc({ diskQueue: 40 }), 0)).state.low, 'a short disk peak is nothing');
293
+ t.check(!watchdogStep(WATCHDOG_START, input(pc(), 0)).state.low, 'no disk counter: memory and CPU only');
212
294
  t.check(cpuPercentBetween([{ times: { user: 0, nice: 0, sys: 0, idle: 0, irq: 0 } }], [{ times: { user: 90, nice: 0, sys: 0, idle: 10, irq: 0 } }]) === 90, 'CPU use from two readings');
213
295
  t.check(GuestRequestSchema.safeParse({ kind: 'pressure', id: 'p1', reason: 'low', seconds: 90, createdAt: 'now' })
214
296
  .success &&
@@ -314,13 +396,17 @@ export const heavyLoadSuite = {
314
396
  const firstLine = (served.stdout ?? '').split(/\r?\n/)[0] ?? '';
315
397
  let sampleOk = false;
316
398
  try {
399
+ // In a sandbox started by 1.10 or later the real agents job exists, so the list may hold its processes.
317
400
  const parsedSample = JSON.parse(firstLine);
318
- sampleOk = Array.isArray(parsedSample.procs) && parsedSample.procs.length === 0 && parsedSample.ramTotalMb > 0;
401
+ sampleOk =
402
+ Array.isArray(parsedSample.procs) &&
403
+ parsedSample.procs.every((p) => typeof p.pid === 'number' && typeof p.name === 'string') &&
404
+ parsedSample.ramTotalMb > 0;
319
405
  }
320
406
  catch {
321
407
  // Not JSON: the check below fails.
322
408
  }
323
- t.check(sampleOk && served.status === 0, 'memgov serve prints a sample (no job yet: no processes) and ends when its input closes', firstLine);
409
+ t.check(sampleOk && served.status === 0, 'memgov serve prints a well-formed sample and ends when its input closes', firstLine);
324
410
  for (const script of ['memory-status.ps1', 'stop-events.ps1']) {
325
411
  const text = fs.readFileSync(path.join(t.packageRoot, 'native', 'scripts', script), 'utf8');
326
412
  t.check(text.startsWith('#Requires -Version 5.1'), script + ' says its PowerShell version');
@@ -13,6 +13,7 @@ import { liveTestsAvailable } from './support/live.js';
13
13
  import { accountsSuite } from './accounts.test.js';
14
14
  import { credentialsSuite } from './credentials.test.js';
15
15
  import { vpnSuite } from './vpn.test.js';
16
+ import { githubSuite } from './github.test.js';
16
17
  import { agentCommandsSuite } from './agent-commands.test.js';
17
18
  import { channelSuite } from './channel.test.js';
18
19
  import { heavyLoadSuite } from './heavy-load.test.js';
@@ -24,6 +25,7 @@ import { migrateSuite } from './migrate.test.js';
24
25
  import { storageSuite } from './storage.test.js';
25
26
  import { updateSuite } from './update.test.js';
26
27
  import { installSuite } from './install.test.js';
28
+ import { powershellVariablesSuite } from './powershell-variables.test.js';
27
29
  import { sharedUseSuite } from './shared-use.test.js';
28
30
  import { updateServiceSuite } from './update-service.test.js';
29
31
  import { sharingSuite } from './sharing.test.js';
@@ -74,6 +76,7 @@ export const SUITES = [
74
76
  updateSuite,
75
77
  updateServiceSuite,
76
78
  installSuite,
79
+ powershellVariablesSuite,
77
80
  sharingSuite,
78
81
  sharedUseSuite,
79
82
  workspaceSuite,
@@ -92,6 +95,7 @@ export const SUITES = [
92
95
  accountsSuite,
93
96
  credentialsSuite,
94
97
  vpnSuite,
98
+ githubSuite,
95
99
  workspaceViewSuite,
96
100
  auditSuite,
97
101
  // an untrusted sandbox: what the host refuses to do with what it finds
@@ -21,6 +21,8 @@ const PURE_GUEST = [
21
21
  'guest/setup/path-plan.js',
22
22
  'guest/toolchains/catalogue.js',
23
23
  'guest/toolchains/tools/vscode-profile.js',
24
+ 'guest/governor/decide.js',
25
+ 'guest/governor/core.js',
24
26
  ];
25
27
  const SIDE_EFFECTS = ['guest/runtime/session.js', 'guest/runtime/log.js'];
26
28
  const readModules = (dist) => {
@@ -110,6 +112,12 @@ export const layoutSuite = {
110
112
  .filter((d) => area(d) === 'guest')
111
113
  .map((d) => f + ' -> ' + d));
112
114
  t.check(!intoGuest.length, 'host, cli, core, sync and shared import no guest file', list(intoGuest));
115
+ // The self-test runs outside a sandbox too (GitHub Actions): no test may load the guest runtime, which reads
116
+ // C:\AgentLaunch\session.json and creates the log folder on import.
117
+ const testsIntoRuntime = files
118
+ .filter((f) => area(f) === 'selftest')
119
+ .flatMap((f) => [...closure(modules, f)].filter((d) => SIDE_EFFECTS.includes(d)).map((d) => f + ' -> ' + d));
120
+ t.check(!testsIntoRuntime.length, 'no test loads the guest runtime (session.json, log folder), so the self-test runs outside a sandbox', list(testsIntoRuntime));
113
121
  t.section('pure guest files');
114
122
  for (const pure of PURE_GUEST) {
115
123
  t.check(modules.has(pure), pure + ' exists');
@@ -10,6 +10,7 @@ import path from 'node:path';
10
10
  import { spawn, spawnSync } from 'node:child_process';
11
11
  import { pathToFileURL } from 'node:url';
12
12
  import { encodeFrame, FrameType } from '../shared/pty/index.js';
13
+ import { SYSTEM } from '../shared/system.js';
13
14
  import { CSC, openViewer, tail, WAIT } from './support/live.js';
14
15
  /**
15
16
  * The stand-in agent: VT input and SGR mouse tracking on, echoes what it reads ("got <1b>[<0;10;5M"). L prints 500
@@ -89,6 +90,79 @@ export const liveTerminalSuite = {
89
90
  t.check(!errors, 'ptyhost and the stand-in agent compile', errors);
90
91
  if (errors)
91
92
  return;
93
+ t.section("the agents' job: a last-resort backstop, set once");
94
+ {
95
+ // A job of the test's own (PTYHOST_JOB_NAME), never the agents' real one.
96
+ const jobName = 'Local\\AgentSandboxAgentsJobTest' + process.pid + '-' + Date.now();
97
+ const ping = path.join(process.env.SystemRoot ?? 'C:\\Windows', 'System32', 'PING.EXE');
98
+ const holder = '"' + SYSTEM.cmd() + '" /c "' + ping + '" -n 6 127.0.0.1 >nul';
99
+ const startHost = async (backstop, log) => spawn(ptyhost, [String(await freePort()), 'tok', '80', '24', t.at(log), holder], {
100
+ env: { ...process.env, PTYHOST_BIND: 'loopback', PTYHOST_BACKSTOP_MB: backstop, PTYHOST_JOB_NAME: jobName },
101
+ stdio: 'ignore',
102
+ windowsHide: true,
103
+ });
104
+ const read = (log) => (fs.existsSync(t.at(log)) ? fs.readFileSync(t.at(log), 'utf8') : '');
105
+ const first = await startHost('auto', 'job-first.log');
106
+ await t.waitFor(() => read('job-first.log').includes('agent job:'), WAIT.screen);
107
+ const second = await startHost('500000', 'job-second.log');
108
+ await t.waitFor(() => read('job-second.log').includes('agent job:'), WAIT.screen);
109
+ // The governor's hands on the same test job (MEMGOV_JOB_NAME): it sees the job's processes, and the dial's
110
+ // commands (memory priority, the throttle job and its CPU cap, trimming) leave them running.
111
+ const memgovExe = t.at('memgov.exe');
112
+ const memgovErrors = compile(path.join(t.packageRoot, 'sandbox', 'csharp', 'MemGov.cs'), memgovExe);
113
+ t.check(!memgovErrors, 'memgov compiles', memgovErrors);
114
+ if (!memgovErrors) {
115
+ const memgov = spawn(memgovExe, ['serve', '--interval', '1'], {
116
+ env: { ...process.env, MEMGOV_JOB_NAME: jobName },
117
+ stdio: ['pipe', 'pipe', 'ignore'],
118
+ windowsHide: true,
119
+ });
120
+ let lines = '';
121
+ memgov.stdout.on('data', (chunk) => (lines += chunk.toString('utf8')));
122
+ const procsSeen = () => {
123
+ const last = lines.trim().split(/\r?\n/).pop() ?? '';
124
+ try {
125
+ return JSON.parse(last).procs;
126
+ }
127
+ catch {
128
+ return [];
129
+ }
130
+ };
131
+ await t.waitFor(() => procsSeen().length > 0, WAIT.screen);
132
+ const target = procsSeen().find((p) => /ping|cmd/i.test(p.name));
133
+ t.check(target !== undefined, "memgov sees the processes in the agents' job", lines.slice(-300));
134
+ if (target) {
135
+ for (const command of [
136
+ 'memprio ' + target.pid + ' low',
137
+ 'throttle ' + target.pid,
138
+ 'cpurate 20',
139
+ 'trim ' + target.pid,
140
+ ])
141
+ memgov.stdin.write(command + '\n');
142
+ await new Promise((r) => setTimeout(r, 1500));
143
+ memgov.stdin.write('cpurate 100\n');
144
+ await new Promise((r) => setTimeout(r, 1200));
145
+ t.check(memgov.exitCode === null && procsSeen().some((p) => p.pid === target.pid), 'memory priority, the throttle job, a CPU cap and trimming leave the process running (and memgov too)');
146
+ }
147
+ memgov.stdin.end();
148
+ await t.waitFor(() => memgov.exitCode !== null, 5000);
149
+ t.check(memgov.exitCode === 0, 'memgov ends cleanly when the daemon goes away (and lifts the cap)');
150
+ }
151
+ for (const c of [first, second]) {
152
+ try {
153
+ if (c.exitCode === null)
154
+ c.kill();
155
+ }
156
+ catch {
157
+ /* it ended between the check and the kill: nothing left to stop */
158
+ }
159
+ }
160
+ const set = /backstop at (\d+) MB committed \(the sandbox's commit limit is (\d+) MB\)/.exec(read('job-first.log'));
161
+ const commit = Number(set?.[2] ?? 0);
162
+ t.check(set !== null &&
163
+ Number(set[1]) === Math.max(Math.floor(commit / 2), commit - Math.max(1536, Math.floor((commit * 3) / 100))), 'the ptyhost that creates the job sets the backstop just below the commit limit (auto: 3 %, at least 1.5 GB)', read('job-first.log'));
164
+ t.check(read('job-second.log').includes('joined the existing one'), 'a ptyhost that joins the job never changes its backstop', read('job-second.log'));
165
+ }
92
166
  const client = t.at('client.mjs');
93
167
  fs.writeFileSync(client, [
94
168
  'import { connect, runTerminalClient } from ' +