windows-development-sandbox 1.10.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 (378) hide show
  1. package/README.md +64 -17
  2. package/config.example.json +5 -1
  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 +21 -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 -89
  149. package/dist/cli/commands/web.d.ts +2 -1
  150. package/dist/cli/commands/web.js +14 -78
  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 +2 -1
  200. package/dist/core/schema/config.d.ts +5 -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/types.d.ts +6 -3
  211. package/dist/guest/boot/boot.js +8 -0
  212. package/dist/guest/daemon/daemon.js +1 -1
  213. package/dist/guest/daemon/sessions.d.ts +3 -2
  214. package/dist/guest/daemon/sessions.js +38 -7
  215. package/dist/guest/governor/core.d.ts +44 -0
  216. package/dist/guest/governor/core.js +124 -0
  217. package/dist/guest/governor/decide.d.ts +46 -18
  218. package/dist/guest/governor/decide.js +136 -82
  219. package/dist/guest/governor/governor.d.ts +2 -38
  220. package/dist/guest/governor/governor.js +33 -113
  221. package/dist/guest/governor/index.d.ts +1 -0
  222. package/dist/guest/governor/index.js +1 -0
  223. package/dist/guest/network/firewall.d.ts +2 -0
  224. package/dist/guest/network/firewall.js +3 -3
  225. package/dist/guest/sessions/viewer.js +5 -1
  226. package/dist/guest/setup/github-token.d.ts +7 -0
  227. package/dist/guest/setup/github-token.js +65 -0
  228. package/dist/guest/toolchains/languages/node.js +11 -1
  229. package/dist/guest/toolchains/native/rust-extras.js +2 -2
  230. package/dist/guest/toolchains/tools/cli.d.ts +8 -0
  231. package/dist/guest/toolchains/tools/cli.js +16 -2
  232. package/dist/guest/vpn/openvpn.d.ts +5 -0
  233. package/dist/guest/vpn/openvpn.js +14 -1
  234. package/dist/guest/vpn/vpn.js +55 -16
  235. package/dist/guest/vpn/wireguard.js +2 -0
  236. package/dist/host/channel/attach.d.ts +1 -0
  237. package/dist/host/channel/attach.js +1 -0
  238. package/dist/host/channel/channel.d.ts +1 -1
  239. package/dist/host/channel/channel.js +2 -2
  240. package/dist/host/engines/hcs/check.d.ts +1 -0
  241. package/dist/host/engines/hcs/check.js +1 -1
  242. package/dist/host/engines/hcs/engine.d.ts +1 -1
  243. package/dist/host/engines/hcs/engine.js +6 -4
  244. package/dist/host/engines/hcs/helper.d.ts +11 -0
  245. package/dist/host/engines/hcs/helper.js +31 -2
  246. package/dist/host/engines/hcs/leftovers.d.ts +42 -0
  247. package/dist/host/engines/hcs/leftovers.js +101 -0
  248. package/dist/host/engines/index.d.ts +1 -0
  249. package/dist/host/engines/index.js +1 -0
  250. package/dist/host/github/deliver.d.ts +13 -0
  251. package/dist/host/github/deliver.js +51 -0
  252. package/dist/host/github/index.d.ts +2 -0
  253. package/dist/host/github/index.js +3 -0
  254. package/dist/host/github/token.d.ts +21 -0
  255. package/dist/host/github/token.js +74 -0
  256. package/dist/host/launch/generate.js +11 -5
  257. package/dist/host/launch/index.d.ts +1 -1
  258. package/dist/host/launch/index.js +1 -1
  259. package/dist/host/launch/session-file.js +1 -2
  260. package/dist/host/launch/wsb-file.d.ts +11 -7
  261. package/dist/host/launch/wsb-file.js +30 -12
  262. package/dist/host/life/watchdog.d.ts +9 -0
  263. package/dist/host/life/watchdog.js +17 -1
  264. package/dist/host/sandbox/tasklist.d.ts +6 -0
  265. package/dist/host/sandbox/tasklist.js +37 -0
  266. package/dist/host/secrets/launch-secrets.d.ts +2 -1
  267. package/dist/host/secrets/launch-secrets.js +4 -2
  268. package/dist/host/vpn/status.js +13 -3
  269. package/dist/host/web/backend.d.ts +1 -3
  270. package/dist/host/web/index.d.ts +2 -1
  271. package/dist/host/web/index.js +2 -1
  272. package/dist/host/web/live.d.ts +23 -0
  273. package/dist/host/web/live.js +107 -0
  274. package/dist/host/web/rpc.d.ts +6 -1
  275. package/dist/host/web/rpc.js +23 -31
  276. package/dist/host/web/runner.d.ts +4 -0
  277. package/dist/host/web/runner.js +12 -1
  278. package/dist/host/web/server.d.ts +30 -2
  279. package/dist/host/web/server.js +87 -29
  280. package/dist/host/web/state.d.ts +1 -0
  281. package/dist/host/web/state.js +2 -0
  282. package/dist/runtime/agent.js +30 -6
  283. package/dist/runtime/daemon.js +323 -176
  284. package/dist/runtime/db.js +30 -6
  285. package/dist/runtime/main.js +375 -76
  286. package/dist/runtime/netproxy.js +30 -6
  287. package/dist/runtime/view.js +495 -146
  288. package/dist/selftest/app.test.js +620 -12
  289. package/dist/selftest/channel.test.js +18 -1
  290. package/dist/selftest/cli.test.js +1 -1
  291. package/dist/selftest/config.test.js +1 -1
  292. package/dist/selftest/contained.test.js +1 -1
  293. package/dist/selftest/data.test.js +1 -2
  294. package/dist/selftest/engine.test.js +3 -1
  295. package/dist/selftest/github.test.d.ts +2 -0
  296. package/dist/selftest/github.test.js +243 -0
  297. package/dist/selftest/hcs.test.js +105 -8
  298. package/dist/selftest/health.test.js +71 -0
  299. package/dist/selftest/heavy-load.test.js +132 -72
  300. package/dist/selftest/index.js +4 -0
  301. package/dist/selftest/layout.test.js +8 -0
  302. package/dist/selftest/live-terminal.test.js +74 -0
  303. package/dist/selftest/menu-screens.js +171 -56
  304. package/dist/selftest/menu.test.js +54 -37
  305. package/dist/selftest/migrate.test.js +14 -12
  306. package/dist/selftest/powershell-variables.test.d.ts +2 -0
  307. package/dist/selftest/powershell-variables.test.js +61 -0
  308. package/dist/selftest/support/engine-golden.js +1 -0
  309. package/dist/selftest/support/fixtures.d.ts +4 -0
  310. package/dist/selftest/support/web-preview.d.ts +1 -0
  311. package/dist/selftest/support/web-preview.js +856 -0
  312. package/dist/selftest/update-service.test.js +15 -8
  313. package/dist/selftest/vpn.test.js +86 -2
  314. package/dist/selftest/web.test.js +58 -11
  315. package/dist/shared/channel/run-dir.d.ts +2 -0
  316. package/dist/shared/channel/run-dir.js +3 -0
  317. package/dist/shared/channel/schema.d.ts +7 -0
  318. package/dist/shared/channel/schema.js +9 -1
  319. package/dist/shared/channel/secrets.d.ts +1 -0
  320. package/dist/shared/channel/secrets.js +3 -0
  321. package/dist/shared/github/index.d.ts +1 -0
  322. package/dist/shared/github/index.js +2 -0
  323. package/dist/shared/github/token.d.ts +29 -0
  324. package/dist/shared/github/token.js +89 -0
  325. package/dist/shared/memory/policy.d.ts +33 -12
  326. package/dist/shared/memory/policy.js +50 -19
  327. package/dist/shared/pty/ctrl-c.d.ts +18 -0
  328. package/dist/shared/pty/ctrl-c.js +36 -0
  329. package/dist/shared/pty/index.d.ts +1 -0
  330. package/dist/shared/pty/index.js +1 -0
  331. package/dist/shared/pty/terminal.d.ts +2 -0
  332. package/dist/shared/pty/terminal.js +21 -2
  333. package/dist/shared/vpn/kill-switch.d.ts +13 -2
  334. package/dist/shared/vpn/kill-switch.js +88 -8
  335. package/dist/shared/web/protocol.d.ts +0 -4
  336. package/dist/sync/watcher.js +1 -1
  337. package/dist/web/index.html +2 -2
  338. package/dist/web/{main-T5DMCHYV.css → main-6JF3ASB5.css} +1 -1
  339. package/dist/web/main-Z645W45E.js +219 -0
  340. package/native/csharp/HcsHost/AssemblyInfo.cs +3 -3
  341. package/native/csharp/HostTools/AssemblyInfo.cs +3 -3
  342. package/package.json +10 -3
  343. package/sandbox/csharp/MemGov.cs +240 -38
  344. package/sandbox/csharp/PtyHost.cs +104 -15
  345. package/sandbox/csharp/SandboxTools/CpuCap.cs +50 -1
  346. package/sandbox/csharp/SandboxTools/Program.cs +1 -1
  347. package/sandbox/scripts/repair-wmi-services.ps1 +26 -0
  348. package/schemas/config.schema.json +19 -5
  349. package/schemas/sandbox-config.schema.json +7 -3
  350. package/dist/cli/commands/accounts.d.ts +0 -2
  351. package/dist/cli/commands/accounts.js +0 -96
  352. package/dist/cli/commands/data/clean.d.ts +0 -3
  353. package/dist/cli/commands/data/clean.js +0 -60
  354. package/dist/cli/commands/data/index.d.ts +0 -4
  355. package/dist/cli/commands/data/index.js +0 -62
  356. package/dist/cli/commands/maintenance/clean.d.ts +0 -2
  357. package/dist/cli/commands/maintenance/clean.js +0 -24
  358. package/dist/cli/commands/maintenance/config-share.d.ts +0 -12
  359. package/dist/cli/commands/maintenance/config-share.js +0 -80
  360. package/dist/cli/commands/projects/projects.d.ts +0 -2
  361. package/dist/cli/commands/projects/projects.js +0 -162
  362. package/dist/cli/commands/storage/contained.d.ts +0 -3
  363. package/dist/cli/commands/storage/contained.js +0 -179
  364. package/dist/cli/commands/storage/storage.d.ts +0 -8
  365. package/dist/cli/commands/storage/storage.js +0 -179
  366. package/dist/cli/menu/add-agent.d.ts +0 -47
  367. package/dist/cli/menu/add-agent.js +0 -168
  368. package/dist/cli/menu/apps.d.ts +0 -69
  369. package/dist/cli/menu/apps.js +0 -214
  370. package/dist/cli/menu/data-folder.d.ts +0 -18
  371. package/dist/cli/menu/data-folder.js +0 -99
  372. package/dist/cli/menu/share-settings.d.ts +0 -21
  373. package/dist/cli/menu/share-settings.js +0 -112
  374. package/dist/cli/menu/vpn-login.d.ts +0 -16
  375. package/dist/cli/menu/vpn-login.js +0 -74
  376. package/dist/web/main-NRRS5O6Z.js +0 -157
  377. /package/dist/cli/{commands/data/collect.d.ts → services/data-collect.d.ts} +0 -0
  378. /package/dist/cli/{commands/data/remove.d.ts → services/data-remove.d.ts} +0 -0
@@ -0,0 +1,107 @@
1
+ import { NO_QUESTIONS } from '../../app/kernel/index.js';
2
+ /** A query unchanged this many reads in a row is read less often... */
3
+ const CALM_READS = 4;
4
+ /** ...down to this many times its own interval. */
5
+ const SLOWEST = 4;
6
+ export class LiveHub {
7
+ host;
8
+ polls = new Map();
9
+ constructor(host) {
10
+ this.host = host;
11
+ }
12
+ /** How many polls run (for tests and the idle check). */
13
+ get size() {
14
+ return this.polls.size;
15
+ }
16
+ /**
17
+ * Starts listening; the listener gets the current answer (at once when one is known) and every change. Returns the
18
+ * function that stops listening.
19
+ */
20
+ watch(query, input, listener) {
21
+ const key = query + '\u0000' + JSON.stringify(input ?? null);
22
+ let poll = this.polls.get(key);
23
+ if (!poll) {
24
+ poll = {
25
+ query,
26
+ input,
27
+ everyMs: this.host.liveMs(query) ?? 0,
28
+ listeners: new Set(),
29
+ timer: undefined,
30
+ busy: false,
31
+ last: undefined,
32
+ lastOutput: undefined,
33
+ calm: 0,
34
+ again: false,
35
+ };
36
+ this.polls.set(key, poll);
37
+ void this.read(poll);
38
+ }
39
+ else if (poll.last !== undefined)
40
+ listener({ output: poll.lastOutput });
41
+ else if (!poll.busy)
42
+ void this.read(poll); // the last read failed: try again for the newcomer
43
+ poll.listeners.add(listener);
44
+ const p = poll;
45
+ return () => {
46
+ p.listeners.delete(listener);
47
+ if (p.listeners.size)
48
+ return;
49
+ clearTimeout(p.timer);
50
+ this.polls.delete(key);
51
+ };
52
+ }
53
+ /** Something changed (an operation ran): every query reads again now, at its own pace. */
54
+ poke() {
55
+ for (const poll of this.polls.values()) {
56
+ poll.calm = 0;
57
+ void this.read(poll);
58
+ }
59
+ }
60
+ schedule(poll) {
61
+ clearTimeout(poll.timer);
62
+ if (!poll.everyMs || !this.isLive(poll))
63
+ return;
64
+ const factor = poll.calm >= CALM_READS ? Math.min(SLOWEST, 1 + Math.floor(poll.calm / CALM_READS)) : 1;
65
+ poll.timer = setTimeout(() => void this.read(poll), poll.everyMs * factor);
66
+ poll.timer.unref?.();
67
+ }
68
+ isLive(poll) {
69
+ return [...this.polls.values()].includes(poll);
70
+ }
71
+ async read(poll) {
72
+ if (poll.busy) {
73
+ poll.again = true;
74
+ return;
75
+ }
76
+ poll.busy = true;
77
+ clearTimeout(poll.timer);
78
+ try {
79
+ const output = await this.host.run(poll.query, poll.input, NO_QUESTIONS);
80
+ const text = JSON.stringify(output);
81
+ if (text === poll.last)
82
+ poll.calm++;
83
+ else {
84
+ poll.calm = 0;
85
+ poll.last = text;
86
+ poll.lastOutput = output;
87
+ for (const l of [...poll.listeners])
88
+ l({ output });
89
+ }
90
+ }
91
+ catch (error) {
92
+ poll.calm = 0;
93
+ poll.last = undefined;
94
+ for (const l of [...poll.listeners])
95
+ l({ error });
96
+ }
97
+ finally {
98
+ poll.busy = false;
99
+ }
100
+ if (poll.again) {
101
+ poll.again = false;
102
+ void this.read(poll);
103
+ return;
104
+ }
105
+ this.schedule(poll);
106
+ }
107
+ }
@@ -1,12 +1,17 @@
1
1
  import type { WebSocket } from 'ws';
2
2
  import { type RpcHost } from '../../app/kernel/index.js';
3
+ import { LiveHub } from './live.js';
3
4
  export declare class RpcSession {
4
5
  private readonly ws;
5
6
  private readonly host;
7
+ /** Shared by every socket of the server (one poll per query); a socket of its own by default. */
8
+ private readonly live;
6
9
  private readonly calls;
7
10
  private readonly subscriptions;
8
11
  private closed;
9
- constructor(ws: WebSocket, host: RpcHost);
12
+ constructor(ws: WebSocket, host: RpcHost,
13
+ /** Shared by every socket of the server (one poll per query); a socket of its own by default. */
14
+ live?: LiveHub);
10
15
  start(): void;
11
16
  private send;
12
17
  /** A browser that sends something that is not one of our messages is closed. */
@@ -1,4 +1,5 @@
1
- import { ANSWER_SCHEMAS, ClientMessageSchema, MAX_OPEN_PER_SOCKET, NO_QUESTIONS, ioFromAsker, toWireError, } from '../../app/kernel/index.js';
1
+ import { ANSWER_SCHEMAS, ClientMessageSchema, MAX_OPEN_PER_SOCKET, ioFromAsker, toWireError, } from '../../app/kernel/index.js';
2
+ import { LiveHub } from './live.js';
2
3
  /** What a question gets when the browser goes away before answering: no. */
3
4
  function declined(q) {
4
5
  return q.kind === 'confirm' ? false : undefined;
@@ -6,12 +7,16 @@ function declined(q) {
6
7
  export class RpcSession {
7
8
  ws;
8
9
  host;
10
+ live;
9
11
  calls = new Map();
10
12
  subscriptions = new Map();
11
13
  closed = false;
12
- constructor(ws, host) {
14
+ constructor(ws, host,
15
+ /** Shared by every socket of the server (one poll per query); a socket of its own by default. */
16
+ live = new LiveHub(host)) {
13
17
  this.ws = ws;
14
18
  this.host = host;
19
+ this.live = live;
15
20
  }
16
21
  start() {
17
22
  this.ws.on('message', (data, binary) => this.receive(data, binary));
@@ -86,7 +91,12 @@ export class RpcSession {
86
91
  this.host
87
92
  .run(op, input, io)
88
93
  .then((output) => this.send({ t: 'result', id, output }), (e) => this.send({ t: 'error', id, error: toWireError(e) }))
89
- .finally(() => this.calls.delete(id));
94
+ .finally(() => {
95
+ this.calls.delete(id);
96
+ // An operation may have changed what the live queries show: they read again now.
97
+ if (this.host.kind(op) === 'operation')
98
+ this.live.poke();
99
+ });
90
100
  }
91
101
  answer(id, value) {
92
102
  const waiting = this.calls.get(id);
@@ -103,38 +113,20 @@ export class RpcSession {
103
113
  this.send({ t: 'error', id, error: { kind: 'invalid', message: 'There is no ' + query + ' here.' } });
104
114
  return;
105
115
  }
106
- const sub = { timer: undefined, last: undefined, busy: false };
107
- this.subscriptions.set(id, sub);
108
- const read = async () => {
109
- if (sub.busy)
116
+ const stop = this.live.watch(query, input, (event) => {
117
+ if (this.subscriptions.get(id) !== stop)
110
118
  return;
111
- sub.busy = true;
112
- try {
113
- const output = await this.host.run(query, input, NO_QUESTIONS);
114
- const text = JSON.stringify(output);
115
- if (text !== sub.last && this.subscriptions.get(id) === sub) {
116
- sub.last = text;
117
- this.send({ t: 'data', id, output });
118
- }
119
- }
120
- catch (e) {
121
- sub.last = undefined;
122
- this.send({ t: 'error', id, error: toWireError(e) });
123
- }
124
- finally {
125
- sub.busy = false;
126
- }
127
- };
128
- void read();
129
- const every = this.host.liveMs(query);
130
- if (every)
131
- sub.timer = setInterval(() => void read(), every);
119
+ if ('output' in event)
120
+ this.send({ t: 'data', id, output: event.output });
121
+ else
122
+ this.send({ t: 'error', id, error: toWireError(event.error) });
123
+ });
124
+ this.subscriptions.set(id, stop);
132
125
  }
133
126
  unsubscribe(id) {
134
- const sub = this.subscriptions.get(id);
135
- if (sub?.timer)
136
- clearInterval(sub.timer);
127
+ const stop = this.subscriptions.get(id);
137
128
  this.subscriptions.delete(id);
129
+ stop?.();
138
130
  }
139
131
  /** The browser went away: subscriptions stop, and questions still open are answered no. */
140
132
  close() {
@@ -6,6 +6,10 @@ export interface WebRunOptions {
6
6
  readonly backend: WebBackend;
7
7
  readonly rpc?: RpcHost;
8
8
  readonly staticRoot: string;
9
+ /** This program's version, kept in the state file. */
10
+ readonly version: string;
11
+ /** End after this long unused while no sandbox runs (WebServerOptions.idleExitMs). */
12
+ readonly idleExitMs?: number;
9
13
  }
10
14
  export declare class WebRunner {
11
15
  private readonly options;
@@ -1,4 +1,5 @@
1
1
  import { fail } from '../../core/util/index.js';
2
+ import { keepTasklistFresh } from '../sandbox/index.js';
2
3
  import { newToken } from './auth.js';
3
4
  import { WebServer } from './server.js';
4
5
  import { WebStore } from './state.js';
@@ -20,6 +21,7 @@ export class WebRunner {
20
21
  backend: this.options.backend,
21
22
  rpc: this.options.rpc,
22
23
  staticRoot: this.options.staticRoot,
24
+ idleExitMs: this.options.idleExitMs,
23
25
  onStop: stop,
24
26
  });
25
27
  try {
@@ -30,13 +32,22 @@ export class WebRunner {
30
32
  fail('Port ' + this.options.port + ' is taken by another program.', 'Pick another with the web.port setting, then run sandbox web start again.');
31
33
  throw e;
32
34
  }
33
- this.store.write({ pid: process.pid, port: server.port, token, startedAt: new Date().toISOString() });
35
+ this.store.write({
36
+ pid: process.pid,
37
+ port: server.port,
38
+ token,
39
+ startedAt: new Date().toISOString(),
40
+ version: this.options.version,
41
+ });
34
42
  process.once('SIGINT', stop);
35
43
  process.once('SIGTERM', stop);
44
+ // Every browser's status reads share one process list instead of each running tasklist.
45
+ const stopTasklist = keepTasklistFresh();
36
46
  try {
37
47
  await stopped;
38
48
  }
39
49
  finally {
50
+ stopTasklist();
40
51
  this.store.clear();
41
52
  await server.stop();
42
53
  }
@@ -4,6 +4,8 @@ import type { Endpoint } from '../../shared/pty/index.js';
4
4
  import type { WebBackend } from './backend.js';
5
5
  /** No inline script: the page and its script are files from this server. Inline styles are what xterm.js needs. */
6
6
  export declare const CONTENT_SECURITY_POLICY: string;
7
+ /** The server ends after this long with no request, no socket and no sandbox running (it starts again with one). */
8
+ export declare const IDLE_EXIT_MS: number;
7
9
  export declare const RPC_PATH = "/api/rpc";
8
10
  export interface WebServerOptions {
9
11
  /** 0 picks a free port (tests). */
@@ -18,6 +20,16 @@ export interface WebServerOptions {
18
20
  readonly dial?: (endpoint: Endpoint) => Promise<net.Socket>;
19
21
  /** The shared core's operations and queries the browser may run (none: /api/rpc is not served). */
20
22
  readonly rpc?: RpcHost;
23
+ /** End by itself (onStop) after this long unused while no sandbox runs; 0 or missing: never (tests). */
24
+ readonly idleExitMs?: number;
25
+ /** Heartbeat period (tests make it short). */
26
+ readonly heartbeatMs?: number;
27
+ }
28
+ /** A browser's socket: which kind, whether it answered the last ping, and when it was opened. */
29
+ interface Tracked {
30
+ readonly kind: 'terminal' | 'rpc';
31
+ readonly opened: number;
32
+ alive: boolean;
21
33
  }
22
34
  export declare class WebServer {
23
35
  private readonly options;
@@ -25,12 +37,27 @@ export declare class WebServer {
25
37
  private readonly sockets;
26
38
  private readonly auth;
27
39
  private readonly files;
28
- private terminals;
29
- private rpcSockets;
40
+ private readonly tracked;
41
+ /** One poll per live query for all browsers together. */
42
+ private readonly live;
30
43
  private actualPort;
44
+ private lastUsed;
45
+ private heartbeat;
46
+ private idleCheck;
31
47
  constructor(options: WebServerOptions);
32
48
  get port(): number;
33
49
  start(): Promise<void>;
50
+ /** How many browser sockets are open, by kind. */
51
+ openSockets(kind: Tracked['kind']): number;
52
+ /**
53
+ * Drops sockets that did not answer the last ping (a tab that went away without closing: sleep, a killed browser),
54
+ * so they never hold a slot or a session connection.
55
+ */
56
+ private beat;
57
+ /** Nothing has used the server for a while and no sandbox runs: it ends (the next launch starts it again). */
58
+ private checkIdle;
59
+ /** Counts a new socket; at the limit, the oldest of its kind makes room (a new tab always works). */
60
+ private track;
34
61
  stop(): Promise<void>;
35
62
  private headers;
36
63
  private json;
@@ -45,3 +72,4 @@ export declare class WebServer {
45
72
  /** The RPC socket (after the same Origin and cookie checks as a terminal). */
46
73
  private upgradeRpc;
47
74
  }
75
+ export {};
@@ -5,9 +5,10 @@ import fs from 'node:fs';
5
5
  import http from 'node:http';
6
6
  import { WebSocketServer } from 'ws';
7
7
  import { UserError, safeText } from '../../core/util/index.js';
8
- import { MAX_CLIENT_MESSAGE_BYTES, MAX_COLS, MAX_ROWS, MIN_COLS, MIN_ROWS, } from '../../shared/web/index.js';
8
+ import { MAX_CLIENT_MESSAGE_BYTES, MAX_COLS, MAX_ROWS, MIN_COLS, MIN_ROWS } from '../../shared/web/index.js';
9
9
  import { cookieHeader, csrfOk, hostAllowed, originAllowed, WebAuth } from './auth.js';
10
10
  import { TerminalBridge } from './bridge.js';
11
+ import { LiveHub } from './live.js';
11
12
  import { RpcSession } from './rpc.js';
12
13
  import { StaticFiles } from './static-files.js';
13
14
  /** No inline script: the page and its script are files from this server. Inline styles are what xterm.js needs. */
@@ -16,6 +17,10 @@ export const CONTENT_SECURITY_POLICY = "default-src 'self'; script-src 'self'; s
16
17
  const MAX_BODY_BYTES = 16 * 1024;
17
18
  const MAX_TERMINALS = 16;
18
19
  const MAX_RPC_SOCKETS = 8;
20
+ /** Every socket is pinged this often; one that has not answered the last ping by the next is dropped. */
21
+ const HEARTBEAT_MS = 20_000;
22
+ /** The server ends after this long with no request, no socket and no sandbox running (it starts again with one). */
23
+ export const IDLE_EXIT_MS = 30 * 60_000;
19
24
  const SESSION_PATH = /^\/api\/sessions\/([A-Za-z0-9_-]{1,64})\/terminal$/;
20
25
  export const RPC_PATH = '/api/rpc';
21
26
  class HttpFailure extends Error {
@@ -35,15 +40,23 @@ export class WebServer {
35
40
  });
36
41
  auth;
37
42
  files;
38
- terminals = 0;
39
- rpcSockets = 0;
43
+ tracked = new Map();
44
+ /** One poll per live query for all browsers together. */
45
+ live;
40
46
  actualPort = 0;
47
+ lastUsed = Date.now();
48
+ heartbeat;
49
+ idleCheck;
41
50
  constructor(options) {
42
51
  this.options = options;
43
52
  this.auth = new WebAuth(options.token);
44
53
  this.files = new StaticFiles(options.staticRoot);
54
+ this.live = options.rpc ? new LiveHub(options.rpc) : undefined;
45
55
  this.server = http.createServer((req, res) => void this.handle(req, res));
46
- this.server.on('upgrade', (req, socket, head) => this.upgrade(req, socket, head));
56
+ this.server.on('upgrade', (req, socket, head) => {
57
+ this.lastUsed = Date.now();
58
+ this.upgrade(req, socket, head);
59
+ });
47
60
  this.server.headersTimeout = 15000;
48
61
  this.server.requestTimeout = 0; // creating a session waits for the agent to start
49
62
  }
@@ -56,11 +69,75 @@ export class WebServer {
56
69
  this.server.listen(this.options.port, '127.0.0.1', () => {
57
70
  this.server.off('error', reject);
58
71
  this.actualPort = this.server.address().port;
72
+ this.heartbeat = setInterval(() => this.beat(), this.options.heartbeatMs ?? HEARTBEAT_MS);
73
+ this.heartbeat.unref();
74
+ if (this.options.idleExitMs) {
75
+ this.idleCheck = setInterval(() => void this.checkIdle(), Math.min(60_000, this.options.idleExitMs));
76
+ this.idleCheck.unref();
77
+ }
59
78
  resolve();
60
79
  });
61
80
  });
62
81
  }
82
+ /** How many browser sockets are open, by kind. */
83
+ openSockets(kind) {
84
+ return [...this.tracked.values()].filter((t) => t.kind === kind).length;
85
+ }
86
+ /**
87
+ * Drops sockets that did not answer the last ping (a tab that went away without closing: sleep, a killed browser),
88
+ * so they never hold a slot or a session connection.
89
+ */
90
+ beat() {
91
+ for (const [ws, t] of this.tracked) {
92
+ if (!t.alive) {
93
+ ws.terminate();
94
+ continue;
95
+ }
96
+ t.alive = false;
97
+ try {
98
+ ws.ping();
99
+ }
100
+ catch {
101
+ ws.terminate(); // the socket broke between the check and the ping
102
+ }
103
+ }
104
+ }
105
+ /** Nothing has used the server for a while and no sandbox runs: it ends (the next launch starts it again). */
106
+ async checkIdle() {
107
+ const limit = this.options.idleExitMs ?? 0;
108
+ if (!limit || this.tracked.size > 0 || Date.now() - this.lastUsed < limit)
109
+ return;
110
+ try {
111
+ if ((await this.options.backend.info()).sandbox === 'running')
112
+ return;
113
+ }
114
+ catch {
115
+ return; // cannot tell: keep running
116
+ }
117
+ this.options.onStop?.();
118
+ }
119
+ /** Counts a new socket; at the limit, the oldest of its kind makes room (a new tab always works). */
120
+ track(ws, kind, limit) {
121
+ const same = [...this.tracked.entries()]
122
+ .filter(([, t]) => t.kind === kind)
123
+ .sort((a, b) => a[1].opened - b[1].opened);
124
+ for (const [old] of same.slice(0, Math.max(0, same.length - limit + 1)))
125
+ old.close(4000, 'replaced by a newer page');
126
+ this.tracked.set(ws, { kind, opened: Date.now(), alive: true });
127
+ ws.on('pong', () => {
128
+ const t = this.tracked.get(ws);
129
+ if (t)
130
+ t.alive = true;
131
+ });
132
+ ws.on('message', () => (this.lastUsed = Date.now()));
133
+ ws.on('close', () => {
134
+ this.tracked.delete(ws);
135
+ this.lastUsed = Date.now();
136
+ });
137
+ }
63
138
  stop() {
139
+ clearInterval(this.heartbeat);
140
+ clearInterval(this.idleCheck);
64
141
  for (const client of this.sockets.clients)
65
142
  client.close(1001, 'server stopping');
66
143
  this.server.closeAllConnections();
@@ -82,6 +159,7 @@ export class WebServer {
82
159
  res.end(JSON.stringify(body));
83
160
  }
84
161
  async handle(req, res) {
162
+ this.lastUsed = Date.now();
85
163
  try {
86
164
  if (!hostAllowed(req.headers.host, this.actualPort))
87
165
  throw new HttpFailure(421, 'Wrong host name.');
@@ -141,13 +219,6 @@ export class WebServer {
141
219
  return this.json(res, 200, await backend.info());
142
220
  if (pathname === '/api/sessions' && method === 'GET')
143
221
  return this.json(res, 200, await backend.sessions());
144
- if (pathname === '/api/sessions' && method === 'POST') {
145
- this.requireSameSite(req);
146
- const request = newSessionRequest(await this.body(req));
147
- if (!request)
148
- throw new HttpFailure(400, 'Choose an agent and a project.');
149
- return this.json(res, 201, await backend.create(request));
150
- }
151
222
  if (pathname === '/api/stop' && method === 'POST') {
152
223
  this.requireSameSite(req);
153
224
  this.json(res, 200, { ok: true });
@@ -198,30 +269,24 @@ export class WebServer {
198
269
  if (!this.auth.signedIn(req.headers.cookie))
199
270
  return refuse('401 Unauthorized');
200
271
  if (rpc)
201
- return this.upgradeRpc(req, socket, head, rpc, refuse);
272
+ return this.upgradeRpc(req, socket, head, rpc);
202
273
  if (!match)
203
274
  return refuse('404 Not Found');
204
- if (this.terminals >= MAX_TERMINALS)
205
- return refuse('503 Service Unavailable');
206
275
  const endpoint = this.options.backend.endpoint(match[1]);
207
276
  if (!endpoint)
208
277
  return refuse('404 Not Found');
209
278
  const cols = bounded(url.searchParams.get('cols'), MIN_COLS, MAX_COLS, 120);
210
279
  const rows = bounded(url.searchParams.get('rows'), MIN_ROWS, MAX_ROWS, 30);
211
280
  this.sockets.handleUpgrade(req, socket, head, (ws) => {
212
- this.terminals++;
213
- ws.on('close', () => this.terminals--);
281
+ this.track(ws, 'terminal', MAX_TERMINALS);
214
282
  void new TerminalBridge(ws, endpoint, cols, rows, this.options.dial).start();
215
283
  });
216
284
  }
217
285
  /** The RPC socket (after the same Origin and cookie checks as a terminal). */
218
- upgradeRpc(req, socket, head, rpc, refuse) {
219
- if (this.rpcSockets >= MAX_RPC_SOCKETS)
220
- return refuse('503 Service Unavailable');
286
+ upgradeRpc(req, socket, head, rpc) {
221
287
  this.sockets.handleUpgrade(req, socket, head, (ws) => {
222
- this.rpcSockets++;
223
- ws.on('close', () => this.rpcSockets--);
224
- new RpcSession(ws, rpc).start();
288
+ this.track(ws, 'rpc', MAX_RPC_SOCKETS);
289
+ new RpcSession(ws, rpc, this.live).start();
225
290
  });
226
291
  }
227
292
  }
@@ -229,10 +294,3 @@ function bounded(text, min, max, fallback) {
229
294
  const n = Number(text);
230
295
  return text !== null && Number.isInteger(n) && n >= min && n <= max ? n : fallback;
231
296
  }
232
- function newSessionRequest(body) {
233
- if (typeof body !== 'object' || body === null)
234
- return null;
235
- const { harness, project } = body;
236
- const ok = (v) => typeof v === 'string' && /^[A-Za-z0-9_.-]{1,64}$/.test(v);
237
- return ok(harness) && ok(project) ? { harness, project } : null;
238
- }
@@ -4,6 +4,7 @@ export declare const WebStateSchema: z.ZodObject<{
4
4
  port: z.ZodNumber;
5
5
  token: z.ZodString;
6
6
  startedAt: z.ZodString;
7
+ version: z.ZodOptional<z.ZodString>;
7
8
  }, z.core.$strict>;
8
9
  export type WebState = z.output<typeof WebStateSchema>;
9
10
  export declare class WebStore {
@@ -10,6 +10,8 @@ export const WebStateSchema = z.strictObject({
10
10
  port: z.number().int().min(1).max(65535),
11
11
  token: z.string().regex(/^[0-9a-f]{64}$/),
12
12
  startedAt: z.string().max(64),
13
+ /** The version that started it (missing: an older one). Another version is replaced, never reused. */
14
+ version: z.string().max(64).optional(),
13
15
  });
14
16
  export class WebStore {
15
17
  storageRoot;