windows-development-sandbox 1.7.0 → 1.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (413) hide show
  1. package/config.example.json +10 -0
  2. package/dist/app/context.d.ts +8 -0
  3. package/dist/app/context.js +12 -0
  4. package/dist/app/features/defs.d.ts +322 -0
  5. package/dist/app/features/defs.js +28 -0
  6. package/dist/app/features/sandbox/index.d.ts +2 -0
  7. package/dist/app/features/sandbox/index.js +3 -0
  8. package/dist/app/features/sandbox/sandbox.def.d.ts +193 -0
  9. package/dist/app/features/sandbox/sandbox.def.js +245 -0
  10. package/dist/app/features/sandbox/sandbox.run.d.ts +72 -0
  11. package/dist/app/features/sandbox/sandbox.run.js +98 -0
  12. package/dist/app/features/sessions/index.d.ts +2 -0
  13. package/dist/app/features/sessions/index.js +3 -0
  14. package/dist/app/features/sessions/sessions.def.d.ts +141 -0
  15. package/dist/app/features/sessions/sessions.def.js +176 -0
  16. package/dist/app/features/sessions/sessions.run.d.ts +39 -0
  17. package/dist/app/features/sessions/sessions.run.js +58 -0
  18. package/dist/{cli/menu/settings/categories.d.ts → app/features/settings/categories.def.d.ts} +2 -0
  19. package/dist/{cli/menu/settings/categories.js → app/features/settings/categories.def.js} +24 -5
  20. package/dist/app/features/settings/index.d.ts +5 -0
  21. package/dist/app/features/settings/index.js +6 -0
  22. package/dist/{cli/menu/settings/config-file.d.ts → app/features/settings/settings-file.d.ts} +1 -1
  23. package/dist/{cli/menu/settings/config-file.js → app/features/settings/settings-file.js} +1 -1
  24. package/dist/app/features/settings/settings-rows.d.ts +35 -0
  25. package/dist/app/features/settings/settings-rows.js +94 -0
  26. package/dist/app/features/settings/settings.def.d.ts +157 -0
  27. package/dist/app/features/settings/settings.def.js +179 -0
  28. package/dist/app/features/settings/settings.run.d.ts +67 -0
  29. package/dist/app/features/settings/settings.run.js +84 -0
  30. package/dist/app/kernel/define.d.ts +143 -0
  31. package/dist/app/kernel/define.js +42 -0
  32. package/dist/app/kernel/errors.d.ts +34 -0
  33. package/dist/app/kernel/errors.js +45 -0
  34. package/dist/app/kernel/fields.d.ts +70 -0
  35. package/dist/app/kernel/fields.js +243 -0
  36. package/dist/app/kernel/index.d.ts +8 -0
  37. package/dist/app/kernel/index.js +10 -0
  38. package/dist/app/kernel/io.d.ts +193 -0
  39. package/dist/app/kernel/io.js +126 -0
  40. package/dist/app/kernel/rpc.d.ts +156 -0
  41. package/dist/app/kernel/rpc.js +47 -0
  42. package/dist/app/kernel/text.d.ts +6 -0
  43. package/dist/app/kernel/text.js +57 -0
  44. package/dist/app/kernel/view.d.ts +146 -0
  45. package/dist/app/kernel/view.js +28 -0
  46. package/dist/app/registry.d.ts +10 -0
  47. package/dist/app/registry.js +50 -0
  48. package/dist/cli/args.d.ts +5 -0
  49. package/dist/cli/args.js +6 -0
  50. package/dist/cli/commands/engine.d.ts +2 -0
  51. package/dist/cli/commands/engine.js +60 -0
  52. package/dist/cli/commands/index.d.ts +1 -1
  53. package/dist/cli/commands/index.js +1 -1
  54. package/dist/cli/commands/launch/launch.js +47 -7
  55. package/dist/cli/commands/launch/prepare.js +3 -3
  56. package/dist/cli/commands/launch/running.js +2 -1
  57. package/dist/cli/commands/launch/summary.d.ts +2 -0
  58. package/dist/cli/commands/maintenance/config-share.d.ts +12 -0
  59. package/dist/cli/commands/maintenance/config-share.js +80 -0
  60. package/dist/cli/commands/maintenance/config.js +8 -82
  61. package/dist/cli/commands/maintenance/logs.js +14 -0
  62. package/dist/cli/commands/registry.js +3 -7
  63. package/dist/cli/commands/repair/checks/contained.d.ts +2 -0
  64. package/dist/cli/commands/repair/checks/contained.js +139 -0
  65. package/dist/cli/commands/repair/checks/hcs.d.ts +7 -0
  66. package/dist/cli/commands/repair/checks/hcs.js +28 -0
  67. package/dist/cli/commands/repair/checks/index.js +4 -0
  68. package/dist/cli/commands/repair/checks/pc.js +32 -1
  69. package/dist/cli/commands/repair/checks/sandbox.js +16 -1
  70. package/dist/cli/commands/repair/checks/vpn.js +34 -9
  71. package/dist/cli/commands/repair/env.js +2 -1
  72. package/dist/cli/commands/sessions/index.d.ts +0 -4
  73. package/dist/cli/commands/sessions/index.js +3 -7
  74. package/dist/cli/commands/shared-use.d.ts +5 -2
  75. package/dist/cli/commands/shared-use.js +11 -8
  76. package/dist/cli/commands/storage/contained.d.ts +3 -0
  77. package/dist/cli/commands/storage/contained.js +179 -0
  78. package/dist/cli/commands/storage/offer.js +7 -1
  79. package/dist/cli/commands/storage/storage.js +10 -2
  80. package/dist/cli/commands/user-config.js +1 -1
  81. package/dist/cli/commands/vpn.js +19 -3
  82. package/dist/cli/commands/web.js +2 -0
  83. package/dist/cli/index.js +2 -1
  84. package/dist/cli/manifest.d.ts +72 -104
  85. package/dist/cli/manifest.js +64 -39
  86. package/dist/cli/menu/add-agent.d.ts +1 -1
  87. package/dist/cli/menu/add-agent.js +1 -1
  88. package/dist/cli/menu/apps.d.ts +7 -1
  89. package/dist/cli/menu/apps.js +36 -0
  90. package/dist/cli/menu/command-fields.d.ts +1 -1
  91. package/dist/cli/menu/command-form.js +2 -2
  92. package/dist/cli/menu/io-menu.d.ts +29 -0
  93. package/dist/cli/menu/io-menu.js +126 -0
  94. package/dist/cli/menu/live.d.ts +27 -0
  95. package/dist/cli/menu/live.js +69 -0
  96. package/dist/cli/menu/memory.d.ts +19 -0
  97. package/dist/cli/menu/memory.js +53 -0
  98. package/dist/cli/menu/menu.d.ts +10 -1
  99. package/dist/cli/menu/menu.js +27 -9
  100. package/dist/cli/menu/pages/agents-page.d.ts +3 -1
  101. package/dist/cli/menu/pages/agents-page.js +12 -5
  102. package/dist/cli/menu/pages/apps-page.d.ts +3 -1
  103. package/dist/cli/menu/pages/apps-page.js +19 -2
  104. package/dist/cli/menu/pages/list-page.d.ts +27 -0
  105. package/dist/cli/menu/pages/list-page.js +109 -7
  106. package/dist/cli/menu/pages/maintenance-page.d.ts +0 -5
  107. package/dist/cli/menu/pages/maintenance-page.js +17 -19
  108. package/dist/cli/menu/pages/sandbox-page.d.ts +4 -6
  109. package/dist/cli/menu/pages/sandbox-page.js +26 -118
  110. package/dist/cli/menu/pages/scopes.d.ts +1 -1
  111. package/dist/cli/menu/pages/sessions-page.d.ts +5 -11
  112. package/dist/cli/menu/pages/sessions-page.js +19 -88
  113. package/dist/cli/menu/pages/settings-page.d.ts +2 -10
  114. package/dist/cli/menu/pages/settings-page.js +64 -94
  115. package/dist/cli/menu/render-view.d.ts +28 -0
  116. package/dist/cli/menu/render-view.js +112 -0
  117. package/dist/cli/menu/settings/{path-kind.d.ts → ask-path.d.ts} +1 -7
  118. package/dist/cli/menu/settings/ask-path.js +14 -0
  119. package/dist/cli/menu/settings/collections/collection-editor.d.ts +1 -1
  120. package/dist/cli/menu/settings/collections/string-list-editor.d.ts +1 -2
  121. package/dist/cli/menu/settings/collections/string-list-editor.js +1 -1
  122. package/dist/cli/menu/settings/draft.d.ts +1 -1
  123. package/dist/cli/menu/settings/format.d.ts +1 -3
  124. package/dist/cli/menu/settings/format.js +6 -36
  125. package/dist/cli/menu/settings/index.d.ts +1 -3
  126. package/dist/cli/menu/settings/index.js +3 -3
  127. package/dist/cli/menu/settings/object-editor.d.ts +1 -1
  128. package/dist/cli/menu/settings/share.d.ts +36 -0
  129. package/dist/cli/menu/settings/share.js +131 -0
  130. package/dist/cli/menu/settings/value-editor.d.ts +5 -1
  131. package/dist/cli/menu/settings/value-editor.js +57 -54
  132. package/dist/cli/menu/several.js +1 -1
  133. package/dist/cli/menu/share-settings.d.ts +21 -0
  134. package/dist/cli/menu/share-settings.js +112 -0
  135. package/dist/cli/menu/shell.d.ts +21 -5
  136. package/dist/cli/menu/shell.js +31 -6
  137. package/dist/cli/menu/views.d.ts +7 -0
  138. package/dist/cli/menu/views.js +11 -0
  139. package/dist/cli/menu/vpn-login.js +8 -0
  140. package/dist/cli/operations/handler.d.ts +6 -0
  141. package/dist/cli/operations/handler.js +61 -0
  142. package/dist/cli/operations/index.d.ts +4 -0
  143. package/dist/cli/operations/index.js +5 -0
  144. package/dist/cli/operations/input.d.ts +6 -0
  145. package/dist/cli/operations/input.js +37 -0
  146. package/dist/cli/operations/io-cli.d.ts +7 -0
  147. package/dist/cli/operations/io-cli.js +62 -0
  148. package/dist/cli/operations/spec.d.ts +18 -0
  149. package/dist/cli/operations/spec.js +108 -0
  150. package/dist/cli/output/colors.d.ts +2 -2
  151. package/dist/cli/output/colors.js +2 -11
  152. package/dist/cli/output/index.d.ts +1 -0
  153. package/dist/cli/output/index.js +1 -0
  154. package/dist/cli/output/print.d.ts +3 -1
  155. package/dist/cli/output/print.js +7 -3
  156. package/dist/cli/output/progress.d.ts +15 -0
  157. package/dist/cli/output/progress.js +44 -0
  158. package/dist/cli/output/render-view.d.ts +4 -0
  159. package/dist/cli/output/render-view.js +63 -0
  160. package/dist/cli/services/agent-commands-service.d.ts +2 -0
  161. package/dist/cli/services/agent-commands-service.js +11 -2
  162. package/dist/cli/services/logs-service.js +4 -3
  163. package/dist/cli/tui/app.d.ts +6 -1
  164. package/dist/cli/tui/app.js +26 -4
  165. package/dist/cli/tui/list/list-screen.d.ts +14 -0
  166. package/dist/cli/tui/list/list-screen.js +64 -36
  167. package/dist/cli/tui/list/list-types.d.ts +2 -0
  168. package/dist/cli/tui/terminal.d.ts +7 -0
  169. package/dist/cli/tui/terminal.js +30 -10
  170. package/dist/core/config/defaults.js +8 -0
  171. package/dist/core/schema/config.d.ts +13 -0
  172. package/dist/core/schema/config.js +16 -1
  173. package/dist/core/schema/harness.d.ts +1 -0
  174. package/dist/core/schema/harness.js +5 -0
  175. package/dist/core/schema/sandbox.d.ts +6 -0
  176. package/dist/core/schema/sandbox.js +20 -3
  177. package/dist/core/schema/vpn.d.ts +2 -1
  178. package/dist/core/schema/vpn.js +3 -3
  179. package/dist/core/types.d.ts +23 -5
  180. package/dist/core/util/fs.d.ts +2 -0
  181. package/dist/core/util/fs.js +9 -0
  182. package/dist/core/util/index.d.ts +1 -1
  183. package/dist/core/util/index.js +1 -1
  184. package/dist/guest/agents/agent-files.d.ts +5 -0
  185. package/dist/guest/agents/agent-files.js +4 -0
  186. package/dist/guest/agents/local-state.d.ts +2 -0
  187. package/dist/guest/agents/local-state.js +21 -1
  188. package/dist/guest/boot/boot.js +2 -1
  189. package/dist/guest/daemon/daemon.js +4 -0
  190. package/dist/guest/daemon/sessions.d.ts +5 -0
  191. package/dist/guest/daemon/sessions.js +17 -3
  192. package/dist/guest/entries/daemon.js +3 -0
  193. package/dist/guest/governor/decide.d.ts +61 -0
  194. package/dist/guest/governor/decide.js +182 -0
  195. package/dist/guest/governor/governor.d.ts +42 -0
  196. package/dist/guest/governor/governor.js +214 -0
  197. package/dist/guest/governor/index.d.ts +2 -0
  198. package/dist/guest/governor/index.js +2 -0
  199. package/dist/guest/runtime/csc.d.ts +1 -0
  200. package/dist/guest/runtime/csc.js +1 -0
  201. package/dist/guest/sessions/viewer.js +3 -1
  202. package/dist/guest/toolchains/native/rust-extras.js +4 -2
  203. package/dist/guest/vpn/vpn.js +69 -21
  204. package/dist/guest/vpn/wireguard.d.ts +32 -0
  205. package/dist/guest/vpn/wireguard.js +132 -0
  206. package/dist/host/agent-commands/commands.d.ts +4 -0
  207. package/dist/host/agent-commands/commands.js +31 -7
  208. package/dist/host/audit/log.d.ts +1 -1
  209. package/dist/host/channel/attach.d.ts +3 -1
  210. package/dist/host/channel/attach.js +5 -2
  211. package/dist/host/channel/channel.d.ts +2 -0
  212. package/dist/host/channel/channel.js +4 -0
  213. package/dist/host/engines/engine.d.ts +12 -0
  214. package/dist/host/engines/engine.js +1 -0
  215. package/dist/host/engines/hcs/check.d.ts +25 -0
  216. package/dist/host/engines/hcs/check.js +95 -0
  217. package/dist/host/engines/hcs/documents.d.ts +44 -0
  218. package/dist/host/engines/hcs/documents.js +155 -0
  219. package/dist/host/engines/hcs/engine.d.ts +39 -0
  220. package/dist/host/engines/hcs/engine.js +171 -0
  221. package/dist/host/engines/hcs/helper.d.ts +43 -0
  222. package/dist/host/engines/hcs/helper.js +173 -0
  223. package/dist/host/engines/hcs/images.d.ts +37 -0
  224. package/dist/host/engines/hcs/images.js +152 -0
  225. package/dist/host/engines/index.d.ts +7 -0
  226. package/dist/host/engines/index.js +5 -0
  227. package/dist/host/engines/registry.d.ts +16 -0
  228. package/dist/host/engines/registry.js +30 -0
  229. package/dist/host/engines/types.d.ts +58 -0
  230. package/dist/host/engines/types.js +1 -0
  231. package/dist/host/engines/windows-sandbox/engine.d.ts +16 -0
  232. package/dist/host/engines/windows-sandbox/engine.js +63 -0
  233. package/dist/host/engines/windows-sandbox/wsb-text.d.ts +2 -0
  234. package/dist/host/engines/windows-sandbox/wsb-text.js +29 -0
  235. package/dist/host/launch/generate.d.ts +5 -1
  236. package/dist/host/launch/generate.js +50 -8
  237. package/dist/host/launch/index.d.ts +3 -1
  238. package/dist/host/launch/index.js +3 -1
  239. package/dist/host/launch/launch-plan.d.ts +13 -0
  240. package/dist/host/launch/launch-plan.js +26 -0
  241. package/dist/host/launch/mappings.js +2 -1
  242. package/dist/host/launch/risk-notes.d.ts +10 -0
  243. package/dist/host/launch/risk-notes.js +18 -0
  244. package/dist/host/launch/session-file.d.ts +2 -0
  245. package/dist/host/launch/session-file.js +7 -1
  246. package/dist/host/launch/wsb-file.d.ts +28 -4
  247. package/dist/host/launch/wsb-file.js +61 -42
  248. package/dist/host/life/crash-report.d.ts +27 -1
  249. package/dist/host/life/crash-report.js +173 -7
  250. package/dist/host/life/host-memory.d.ts +40 -0
  251. package/dist/host/life/host-memory.js +137 -0
  252. package/dist/host/life/index.d.ts +2 -0
  253. package/dist/host/life/index.js +2 -0
  254. package/dist/host/life/stop-events.d.ts +2 -0
  255. package/dist/host/life/stop-events.js +20 -3
  256. package/dist/host/life/stop-warning.js +1 -1
  257. package/dist/host/life/watchdog.d.ts +54 -0
  258. package/dist/host/life/watchdog.js +108 -0
  259. package/dist/host/native.d.ts +3 -2
  260. package/dist/host/native.js +22 -3
  261. package/dist/host/profile/claude-login.js +3 -2
  262. package/dist/host/sandbox/contained-deps.d.ts +2 -0
  263. package/dist/host/sandbox/contained-deps.js +17 -0
  264. package/dist/host/sandbox/index.d.ts +2 -0
  265. package/dist/host/sandbox/index.js +2 -0
  266. package/dist/host/sandbox/platform.d.ts +2 -0
  267. package/dist/host/sandbox/platform.js +7 -7
  268. package/dist/host/sandbox/tasklist.d.ts +13 -0
  269. package/dist/host/sandbox/tasklist.js +47 -0
  270. package/dist/host/secrets/launch-secrets.js +2 -1
  271. package/dist/host/sharing/current.js +2 -6
  272. package/dist/host/storage/contained/check.d.ts +9 -0
  273. package/dist/host/storage/contained/check.js +37 -0
  274. package/dist/host/storage/contained/contained-storage.d.ts +66 -0
  275. package/dist/host/storage/contained/contained-storage.js +168 -0
  276. package/dist/host/storage/contained/disk-port.d.ts +43 -0
  277. package/dist/host/storage/contained/disk-port.js +122 -0
  278. package/dist/host/storage/contained/index.d.ts +9 -0
  279. package/dist/host/storage/contained/index.js +8 -0
  280. package/dist/host/storage/contained/layout.d.ts +28 -0
  281. package/dist/host/storage/contained/layout.js +45 -0
  282. package/dist/host/storage/contained/resolve.d.ts +4 -0
  283. package/dist/host/storage/contained/resolve.js +30 -0
  284. package/dist/host/storage/contained/setting.d.ts +1 -0
  285. package/dist/host/storage/contained/setting.js +5 -0
  286. package/dist/host/storage/contained/state.d.ts +89 -0
  287. package/dist/host/storage/contained/state.js +166 -0
  288. package/dist/host/storage/contained/transfer.d.ts +24 -0
  289. package/dist/host/storage/contained/transfer.js +121 -0
  290. package/dist/host/storage/index.d.ts +2 -0
  291. package/dist/host/storage/index.js +2 -0
  292. package/dist/host/storage/providers/contained.d.ts +15 -0
  293. package/dist/host/storage/providers/contained.js +39 -0
  294. package/dist/host/storage/providers/folder.d.ts +12 -0
  295. package/dist/host/storage/providers/folder.js +20 -0
  296. package/dist/host/storage/providers/index.d.ts +4 -0
  297. package/dist/host/storage/providers/index.js +3 -0
  298. package/dist/host/storage/providers/registry.d.ts +9 -0
  299. package/dist/host/storage/providers/registry.js +14 -0
  300. package/dist/host/storage/providers/types.d.ts +33 -0
  301. package/dist/host/storage/providers/types.js +1 -0
  302. package/dist/host/tools/conpty.js +3 -2
  303. package/dist/host/tools/windows-terminal.js +8 -7
  304. package/dist/host/vpn/index.d.ts +1 -1
  305. package/dist/host/vpn/index.js +1 -1
  306. package/dist/host/vpn/prepare.d.ts +2 -0
  307. package/dist/host/vpn/prepare.js +63 -2
  308. package/dist/host/web/browser.d.ts +2 -0
  309. package/dist/host/web/browser.js +14 -1
  310. package/dist/host/web/index.d.ts +3 -2
  311. package/dist/host/web/index.js +3 -2
  312. package/dist/host/web/rpc.d.ts +24 -0
  313. package/dist/host/web/rpc.js +149 -0
  314. package/dist/host/web/runner.d.ts +2 -0
  315. package/dist/host/web/runner.js +1 -2
  316. package/dist/host/web/server.d.ts +7 -0
  317. package/dist/host/web/server.js +23 -3
  318. package/dist/runtime/agent.js +69 -8
  319. package/dist/runtime/daemon.js +1058 -624
  320. package/dist/runtime/db.js +69 -8
  321. package/dist/runtime/main.js +624 -182
  322. package/dist/runtime/netproxy.js +69 -8
  323. package/dist/runtime/view.js +87 -13
  324. package/dist/selftest/agent-paths.test.js +11 -1
  325. package/dist/selftest/app.test.d.ts +2 -0
  326. package/dist/selftest/app.test.js +324 -0
  327. package/dist/selftest/channel.test.js +13 -0
  328. package/dist/selftest/cli.test.js +2 -1
  329. package/dist/selftest/config.test.js +59 -0
  330. package/dist/selftest/contained.test.d.ts +2 -0
  331. package/dist/selftest/contained.test.js +544 -0
  332. package/dist/selftest/credentials.test.js +1 -1
  333. package/dist/selftest/engine.test.d.ts +2 -0
  334. package/dist/selftest/engine.test.js +127 -0
  335. package/dist/selftest/hcs.test.d.ts +2 -0
  336. package/dist/selftest/hcs.test.js +482 -0
  337. package/dist/selftest/heavy-load.test.d.ts +2 -0
  338. package/dist/selftest/heavy-load.test.js +329 -0
  339. package/dist/selftest/index.js +12 -0
  340. package/dist/selftest/layout.test.js +12 -0
  341. package/dist/selftest/menu-perf.test.d.ts +2 -0
  342. package/dist/selftest/menu-perf.test.js +248 -0
  343. package/dist/selftest/menu-screens.js +4 -2
  344. package/dist/selftest/menu.test.js +36 -8
  345. package/dist/selftest/support/engine-cases.d.ts +8 -0
  346. package/dist/selftest/support/engine-cases.js +65 -0
  347. package/dist/selftest/support/engine-golden.d.ts +3 -0
  348. package/dist/selftest/support/engine-golden.js +66 -0
  349. package/dist/selftest/support/fixtures.d.ts +7 -1
  350. package/dist/selftest/support/hcs-probes.d.ts +17 -0
  351. package/dist/selftest/support/hcs-probes.js +41 -0
  352. package/dist/selftest/vpn.test.js +136 -3
  353. package/dist/selftest/web.test.js +101 -3
  354. package/dist/shared/channel/run-dir.d.ts +4 -1
  355. package/dist/shared/channel/run-dir.js +7 -1
  356. package/dist/shared/channel/schema.d.ts +35 -1
  357. package/dist/shared/channel/schema.js +27 -0
  358. package/dist/shared/channel/secrets.d.ts +1 -0
  359. package/dist/shared/channel/secrets.js +6 -0
  360. package/dist/shared/memory/index.d.ts +1 -0
  361. package/dist/shared/memory/index.js +1 -0
  362. package/dist/shared/memory/policy.d.ts +33 -0
  363. package/dist/shared/memory/policy.js +55 -0
  364. package/dist/shared/pty/output-filter.d.ts +14 -1
  365. package/dist/shared/pty/output-filter.js +22 -4
  366. package/dist/shared/pty/terminal.d.ts +4 -1
  367. package/dist/shared/pty/terminal.js +4 -2
  368. package/dist/shared/system.d.ts +1 -0
  369. package/dist/shared/system.js +1 -0
  370. package/dist/shared/vpn/download.d.ts +16 -0
  371. package/dist/shared/vpn/download.js +16 -0
  372. package/dist/shared/vpn/wireguard.d.ts +51 -0
  373. package/dist/shared/vpn/wireguard.js +227 -0
  374. package/dist/sync/sync-cycle.js +5 -5
  375. package/dist/sync/watcher.d.ts +13 -0
  376. package/dist/sync/watcher.js +92 -3
  377. package/dist/web/index.html +4 -3
  378. package/dist/web/main-NRRS5O6Z.js +157 -0
  379. package/dist/web/main-T5DMCHYV.css +1 -0
  380. package/native/csharp/HcsHost/AssemblyInfo.cs +14 -0
  381. package/native/csharp/HcsHost/Guard.cs +100 -0
  382. package/native/csharp/HcsHost/Hcs.cs +256 -0
  383. package/native/csharp/HcsHost/Json.cs +78 -0
  384. package/native/csharp/HcsHost/Program.cs +185 -0
  385. package/native/csharp/HcsHost/VirtualDisk.cs +95 -0
  386. package/native/csharp/HostTools/AssemblyInfo.cs +14 -0
  387. package/native/csharp/HostTools/MemoryStatus.cs +107 -0
  388. package/native/csharp/HostTools/Program.cs +58 -17
  389. package/native/scripts/check-contained.ps1 +28 -0
  390. package/native/scripts/contained-common.ps1 +241 -0
  391. package/native/scripts/dismount-contained.ps1 +66 -0
  392. package/native/scripts/memory-status.ps1 +34 -0
  393. package/native/scripts/mount-contained.ps1 +139 -0
  394. package/native/scripts/run-elevated.ps1 +17 -4
  395. package/native/scripts/stop-events.ps1 +23 -8
  396. package/package.json +9 -2
  397. package/sandbox/csharp/MemGov.cs +398 -0
  398. package/sandbox/csharp/PtyHost.cs +111 -5
  399. package/schemas/config.schema.json +60 -6
  400. package/schemas/sandbox-config.schema.json +3 -3
  401. package/dist/cli/commands/sessions/control.d.ts +0 -4
  402. package/dist/cli/commands/sessions/control.js +0 -66
  403. package/dist/cli/commands/sessions/list.d.ts +0 -2
  404. package/dist/cli/commands/sessions/list.js +0 -48
  405. package/dist/cli/commands/sessions/status.d.ts +0 -4
  406. package/dist/cli/commands/sessions/status.js +0 -114
  407. package/dist/cli/commands/sessions/window.d.ts +0 -3
  408. package/dist/cli/commands/sessions/window.js +0 -18
  409. package/dist/cli/menu/settings/path-kind.js +0 -44
  410. package/dist/web/main-CXESL347.js +0 -45
  411. package/dist/web/main-VLR7DVQ4.css +0 -1
  412. /package/dist/{cli/menu/settings → app/kernel}/schema-node.d.ts +0 -0
  413. /package/dist/{cli/menu/settings → app/kernel}/schema-node.js +0 -0
@@ -8,13 +8,19 @@ const KEPT_OSC = (body) => {
8
8
  };
9
9
  /** Longest OSC kept (a title or a link); anything longer is dropped whole. */
10
10
  const MAX_KEPT_STRING = 4096;
11
+ /** Longest clipboard write kept (about 750 KB of text); anything longer is dropped whole. */
12
+ const MAX_CLIPBOARD_STRING = 1024 * 1024;
13
+ /** A clipboard write (OSC 52: selection, then base64): never a read ("?"), which would send the clipboard back. */
14
+ const CLIPBOARD_WRITE = /^0{0,2}52;[cpqs0-7]{0,8};[A-Za-z0-9+/=]*$/;
11
15
  /**
12
16
  * Removes the terminal "strings" an untrusted program could abuse from the sandbox's output before it reaches your
13
- * terminal: clipboard writes (OSC 52), terminal macros (ConEmu's OSC 9), DCS / APC / PM / SOS, and their C1 forms.
17
+ * terminal: clipboard writes (OSC 52, unless clipboardWrites) and reads, terminal macros (ConEmu's OSC 9), DCS / APC /
18
+ * PM / SOS, and their C1 forms.
14
19
  * Titles, links, progress and colours pass. A byte-level state machine, so no split across chunks, leading zero
15
20
  * (ESC]052) or huge payload slips through: a dropped string stays dropped until its terminator, however long it is.
16
21
  */
17
22
  export class OutputFilter {
23
+ options;
18
24
  /**
19
25
  * text: normal output; esc: after ESC; c2: after a 0xC2 lead byte; string: inside an OSC/DCS/...; stringEsc /
20
26
  * stringC2: a possible terminator inside one.
@@ -22,8 +28,20 @@ export class OutputFilter {
22
28
  state = 'text';
23
29
  /** The string being read: an OSC worth keeping is collected until its terminator; null = being dropped. */
24
30
  body = null;
31
+ maxString;
32
+ constructor(options = {}) {
33
+ this.options = options;
34
+ this.maxString = options.clipboardWrites ? MAX_CLIPBOARD_STRING : MAX_KEPT_STRING;
35
+ }
36
+ /** An OSC that may pass: a short title, link, progress or mark, or (when allowed) a clipboard write. */
37
+ kept(body) {
38
+ if (this.options.clipboardWrites && CLIPBOARD_WRITE.test(body))
39
+ return true;
40
+ return body.length <= MAX_KEPT_STRING && KEPT_OSC(body);
41
+ }
25
42
  push(chunk) {
26
- const out = Buffer.allocUnsafe(chunk.length + MAX_KEPT_STRING + 8);
43
+ // At most: the string carried over from the last chunk, this chunk, and an OSC prefix and terminator.
44
+ const out = Buffer.allocUnsafe(chunk.length + (this.body?.length ?? 0) + 8);
27
45
  let n = 0;
28
46
  const put = (...bytes) => {
29
47
  for (const b of bytes)
@@ -45,7 +63,7 @@ export class OutputFilter {
45
63
  };
46
64
  const endString = (terminator) => {
47
65
  const body = this.body && Buffer.from(this.body).toString('latin1');
48
- if (body !== null && KEPT_OSC(body)) {
66
+ if (body !== null && this.kept(body)) {
49
67
  put(0x1b, 0x5d);
50
68
  out.write(body, n, 'latin1');
51
69
  n += body.length;
@@ -55,7 +73,7 @@ export class OutputFilter {
55
73
  this.state = 'text';
56
74
  };
57
75
  const collect = (b) => {
58
- if (this.body && this.body.push(b) > MAX_KEPT_STRING)
76
+ if (this.body && this.body.push(b) > this.maxString)
59
77
  this.body = null;
60
78
  };
61
79
  for (const b of chunk) {
@@ -22,6 +22,9 @@ export declare function fixWindowsKeys(chunk: Buffer): Buffer;
22
22
  * Bridges this process's terminal to the remote program until it exits, the connection drops, or the user presses
23
23
  * the detach key. Restores the terminal before resolving.
24
24
  */
25
- export declare function runTerminalClient(socket: net.Socket, token: string, options?: {
25
+ export declare function runTerminalClient(socket: net.Socket, token: string,
26
+ /** clipboardWrites: the agent's "copy" (OSC 52) reaches the clipboard; see OutputFilterOptions. */
27
+ options?: {
26
28
  detachKey?: number;
29
+ clipboardWrites?: boolean;
27
30
  }): Promise<ClientResult>;
@@ -20,7 +20,9 @@ export function fixWindowsKeys(chunk) {
20
20
  * Bridges this process's terminal to the remote program until it exits, the connection drops, or the user presses
21
21
  * the detach key. Restores the terminal before resolving.
22
22
  */
23
- export function runTerminalClient(socket, token, options = {}) {
23
+ export function runTerminalClient(socket, token,
24
+ /** clipboardWrites: the agent's "copy" (OSC 52) reaches the clipboard; see OutputFilterOptions. */
25
+ options = {}) {
24
26
  const stdin = process.stdin;
25
27
  const stdout = process.stdout;
26
28
  // getWindowSize asks the console every time; columns/rows only change when Node noticed a resize.
@@ -33,7 +35,7 @@ export function runTerminalClient(socket, token, options = {}) {
33
35
  return new Promise((resolve) => {
34
36
  let done = false;
35
37
  const reader = new FrameReader();
36
- const filter = new OutputFilter();
38
+ const filter = new OutputFilter({ clipboardWrites: options.clipboardWrites });
37
39
  let sent = size();
38
40
  let resizeTimer;
39
41
  // Set once raw mode is on (below); finish() may run before that.
@@ -15,6 +15,7 @@ export declare const SYSTEM: {
15
15
  readonly shutdown: () => string;
16
16
  readonly dism: () => string;
17
17
  readonly msiexec: () => string;
18
+ readonly sc: () => string;
18
19
  /** The .NET Framework C# compiler that ships with Windows (C# 5). */
19
20
  readonly csc: () => string;
20
21
  };
@@ -21,6 +21,7 @@ export const SYSTEM = {
21
21
  shutdown: () => system32('shutdown.exe'),
22
22
  dism: () => system32('Dism.exe'),
23
23
  msiexec: () => system32('msiexec.exe'),
24
+ sc: () => system32('sc.exe'),
24
25
  /** The .NET Framework C# compiler that ships with Windows (C# 5). */
25
26
  csc: () => path.join(systemRoot(), 'Microsoft.NET', 'Framework64', 'v4.0.30319', 'csc.exe'),
26
27
  };
@@ -11,3 +11,19 @@ export declare const OPENVPN_MSI: {
11
11
  };
12
12
  readonly base: "https://swupdate.openvpn.org/community/releases/";
13
13
  };
14
+ /**
15
+ * WireGuard for Windows (brings wireguard.exe and wg.exe). Hashes computed from the files on download.wireguard.com
16
+ * (https); both files carry a valid WireGuard LLC signature.
17
+ */
18
+ export declare const WIREGUARD_MSI: {
19
+ readonly version: "1.1.1";
20
+ readonly x64: {
21
+ readonly file: "wireguard-amd64-1.1.1.msi";
22
+ readonly sha256: "7bfed60ad61b785c914b38b61555a975488e1d3ec472dbfb2fcdf498fca75242";
23
+ };
24
+ readonly arm64: {
25
+ readonly file: "wireguard-arm64-1.1.1.msi";
26
+ readonly sha256: "8336335c738d4aff040fa9d11b752da65a80b6f41d3262d6ba5e82f97ebd1bd4";
27
+ };
28
+ readonly base: "https://download.wireguard.com/windows-client/";
29
+ };
@@ -12,3 +12,19 @@ export const OPENVPN_MSI = {
12
12
  },
13
13
  base: 'https://swupdate.openvpn.org/community/releases/',
14
14
  };
15
+ /**
16
+ * WireGuard for Windows (brings wireguard.exe and wg.exe). Hashes computed from the files on download.wireguard.com
17
+ * (https); both files carry a valid WireGuard LLC signature.
18
+ */
19
+ export const WIREGUARD_MSI = {
20
+ version: '1.1.1',
21
+ x64: {
22
+ file: 'wireguard-amd64-1.1.1.msi',
23
+ sha256: '7bfed60ad61b785c914b38b61555a975488e1d3ec472dbfb2fcdf498fca75242',
24
+ },
25
+ arm64: {
26
+ file: 'wireguard-arm64-1.1.1.msi',
27
+ sha256: '8336335c738d4aff040fa9d11b752da65a80b6f41d3262d6ba5e82f97ebd1bd4',
28
+ },
29
+ base: 'https://download.wireguard.com/windows-client/',
30
+ };
@@ -0,0 +1,51 @@
1
+ export declare const MAX_WIREGUARD_BYTES: number;
2
+ /** Keys that run a program, change the routes or write the config back. Refused with a clear reason. */
3
+ export declare const WIREGUARD_PROGRAM_KEYS: readonly ["preup", "postup", "predown", "postdown", "table", "saveconfig"];
4
+ export interface WireguardPeer {
5
+ readonly publicKey: string;
6
+ readonly presharedKey?: string;
7
+ readonly allowedIps: readonly string[];
8
+ readonly endpointHost: string;
9
+ readonly endpointPort: number;
10
+ readonly keepalive?: number;
11
+ }
12
+ /** A checked config. Holds secrets (privateKey, presharedKey): never log it or write it to session.json. */
13
+ export interface WireguardConfig {
14
+ readonly privateKey: string;
15
+ readonly addresses: readonly string[];
16
+ readonly dns: readonly string[];
17
+ readonly mtu?: number;
18
+ readonly listenPort?: number;
19
+ readonly peers: readonly WireguardPeer[];
20
+ }
21
+ export type WireguardReport = {
22
+ readonly ok: true;
23
+ readonly config: WireguardConfig;
24
+ } | {
25
+ readonly ok: false;
26
+ readonly problems: readonly string[];
27
+ };
28
+ /** Where the tunnel connects (the kill switch allows these, over udp). */
29
+ export interface WireguardEndpoint {
30
+ readonly host: string;
31
+ readonly port: number;
32
+ }
33
+ /**
34
+ * Checks a config. `ok: false` lists the problems (no values in them); `ok: true` gives the checked settings. It needs
35
+ * one [Interface] with a PrivateKey and an IPv4 Address, and at least one [Peer] with PublicKey, AllowedIPs and an
36
+ * Endpoint that is a name or an IPv4 address (the kill switch rules are IPv4).
37
+ */
38
+ export declare function inspectWireguard(text: string): WireguardReport;
39
+ /** The checked config; throws, saying why (never with a key), when it is not safe. `what` names the file. */
40
+ export declare function assertSafeWireguard(text: string, what: string): WireguardConfig;
41
+ /** The config as text, written from the checked values. `endpointIps` puts an address in a peer's place. */
42
+ export declare function renderWireguard(config: WireguardConfig, endpointIps?: Readonly<Record<number, string>>): string;
43
+ export declare function wireguardEndpoints(config: WireguardConfig): WireguardEndpoint[];
44
+ /** The tunnel's own IPv4 address (what the kill switch lets through once the tunnel is up). */
45
+ export declare function wireguardTunnelAddress(config: WireguardConfig): string;
46
+ /** True when some peer sends all IPv4 traffic through the tunnel (AllowedIPs 0.0.0.0/0). */
47
+ export declare function routesEverything(config: WireguardConfig): boolean;
48
+ /** Whether `sc query` output says the tunnel service is running. */
49
+ export declare function serviceRunning(scOutput: string): boolean;
50
+ /** Whether `wg show <tunnel> latest-handshakes` says a peer has completed a handshake (a time that is not 0). */
51
+ export declare function handshakeDone(wgOutput: string): boolean;
@@ -0,0 +1,227 @@
1
+ // A WireGuard config (.conf) is untrusted data: it comes from a file you were given, and WireGuard for Windows runs the
2
+ // programs of PreUp/PostUp/PreDown/PostDown with administrator rights. Only the settings on a short list are accepted;
3
+ // anything else is refused by name, and the config that is handed on is written again from the checked values, never
4
+ // copied. Problem messages name keys and lines, never values (the private and preshared keys are secrets). Also answers
5
+ // where the tunnel connects and which address it has, which the kill switch needs. Pure: no files, no network.
6
+ import net from 'node:net';
7
+ export const MAX_WIREGUARD_BYTES = 16 * 1024;
8
+ const MAX_LINES = 300;
9
+ const MAX_PEERS = 16;
10
+ /** Keys that run a program, change the routes or write the config back. Refused with a clear reason. */
11
+ export const WIREGUARD_PROGRAM_KEYS = ['preup', 'postup', 'predown', 'postdown', 'table', 'saveconfig'];
12
+ const INTERFACE_KEYS = new Set(['privatekey', 'address', 'dns', 'mtu', 'listenport']);
13
+ const PEER_KEYS = new Set(['publickey', 'presharedkey', 'allowedips', 'endpoint', 'persistentkeepalive']);
14
+ /** Keys that may be given more than once (their values are joined). */
15
+ const LIST_KEYS = new Set(['address', 'dns', 'allowedips']);
16
+ const KEY = /^[A-Za-z0-9+/]{43}=$/;
17
+ const HOST = /^(?:\d{1,3}(?:\.\d{1,3}){3}|[A-Za-z0-9](?:[A-Za-z0-9.-]{0,251}[A-Za-z0-9])?)$/;
18
+ function validCidr(text) {
19
+ const [addr, prefix, ...rest] = text.split('/');
20
+ if (rest.length > 0 || !addr)
21
+ return false;
22
+ const version = net.isIP(addr);
23
+ if (version === 0)
24
+ return false;
25
+ if (prefix === undefined)
26
+ return true;
27
+ if (!/^\d{1,3}$/.test(prefix))
28
+ return false;
29
+ return Number(prefix) <= (version === 4 ? 32 : 128);
30
+ }
31
+ const inRange = (text, low, high) => {
32
+ if (!/^\d{1,5}$/.test(text))
33
+ return null;
34
+ const n = Number(text);
35
+ return n >= low && n <= high ? n : null;
36
+ };
37
+ const splitList = (text) => text
38
+ .split(',')
39
+ .map((s) => s.trim())
40
+ .filter(Boolean);
41
+ /**
42
+ * Checks a config. `ok: false` lists the problems (no values in them); `ok: true` gives the checked settings. It needs
43
+ * one [Interface] with a PrivateKey and an IPv4 Address, and at least one [Peer] with PublicKey, AllowedIPs and an
44
+ * Endpoint that is a name or an IPv4 address (the kill switch rules are IPv4).
45
+ */
46
+ export function inspectWireguard(text) {
47
+ const problems = [];
48
+ const refuse = (message) => {
49
+ if (problems.length < 20 && !problems.includes(message))
50
+ problems.push(message);
51
+ };
52
+ if (Buffer.byteLength(text, 'utf8') > MAX_WIREGUARD_BYTES)
53
+ return { ok: false, problems: ['the file is too big'] };
54
+ if (text.includes('\0'))
55
+ return { ok: false, problems: ['the file is not text'] };
56
+ const lines = (text.charCodeAt(0) === 0xfeff ? text.slice(1) : text).split(/\r?\n/);
57
+ if (lines.length > MAX_LINES)
58
+ return { ok: false, problems: ['the file has too many lines'] };
59
+ const sections = [];
60
+ let current = null;
61
+ lines.forEach((raw, i) => {
62
+ const at = 'line ' + (i + 1) + ': ';
63
+ const line = raw.replace(/#.*$/, '').trim();
64
+ if (!line)
65
+ return;
66
+ const header = /^\[([A-Za-z]{1,20})\]$/.exec(line);
67
+ if (header) {
68
+ const name = header[1].toLowerCase();
69
+ if (name !== 'interface' && name !== 'peer') {
70
+ current = null;
71
+ return refuse(at + '[' + name + '] is not allowed');
72
+ }
73
+ current = { kind: name, values: new Map() };
74
+ sections.push(current);
75
+ return;
76
+ }
77
+ const pair = /^([A-Za-z]{1,40})\s*=\s*(.*)$/.exec(line);
78
+ if (!pair)
79
+ return refuse(at + 'not a "Key = value" line');
80
+ const key = pair[1].toLowerCase();
81
+ if (WIREGUARD_PROGRAM_KEYS.includes(key))
82
+ return refuse(at + pair[1] + ' runs a program or changes how the tunnel is routed or saved');
83
+ if (!current)
84
+ return refuse(at + pair[1] + ' is outside a section');
85
+ if (!(current.kind === 'interface' ? INTERFACE_KEYS : PEER_KEYS).has(key))
86
+ return refuse(at + pair[1] + ' is not on the list of settings that are safe to accept');
87
+ const seen = current.values.get(key);
88
+ if (seen && !LIST_KEYS.has(key))
89
+ return refuse(at + pair[1] + ' is given twice');
90
+ current.values.set(key, [...(seen ?? []), pair[2].trim()]);
91
+ });
92
+ const one = (s, key) => s.values.get(key)?.[0];
93
+ const joined = (s, key) => (s.values.get(key) ?? []).flatMap(splitList);
94
+ const interfaces = sections.filter((s) => s.kind === 'interface');
95
+ const peers = sections.filter((s) => s.kind === 'peer');
96
+ if (interfaces.length !== 1)
97
+ refuse('it needs exactly one [Interface] section');
98
+ if (peers.length === 0)
99
+ refuse('it has no [Peer] section');
100
+ if (peers.length > MAX_PEERS)
101
+ refuse('it has too many [Peer] sections');
102
+ if (problems.length)
103
+ return { ok: false, problems };
104
+ const face = interfaces[0];
105
+ const privateKey = one(face, 'privatekey');
106
+ if (!privateKey)
107
+ refuse('[Interface] has no PrivateKey');
108
+ else if (!KEY.test(privateKey))
109
+ refuse('PrivateKey is not a WireGuard key (44 characters of base64)');
110
+ const addresses = joined(face, 'address');
111
+ if (addresses.length === 0 || !addresses.every(validCidr))
112
+ refuse('[Interface] needs valid Address values');
113
+ else if (!addresses.some((a) => net.isIPv4(a.split('/')[0])))
114
+ refuse('Address needs an IPv4 address (the kill switch works on IPv4)');
115
+ const dns = joined(face, 'dns');
116
+ if (!dns.every((d) => net.isIP(d) !== 0))
117
+ refuse('DNS may only list IP addresses');
118
+ const mtuText = one(face, 'mtu');
119
+ const mtu = mtuText === undefined ? undefined : inRange(mtuText, 576, 1500);
120
+ if (mtuText !== undefined && mtu === null)
121
+ refuse('MTU must be a number from 576 to 1500');
122
+ const portText = one(face, 'listenport');
123
+ const listenPort = portText === undefined ? undefined : inRange(portText, 1, 65535);
124
+ if (portText !== undefined && listenPort === null)
125
+ refuse('ListenPort must be a number from 1 to 65535');
126
+ const checked = [];
127
+ peers.forEach((p, n) => {
128
+ const who = '[Peer] ' + (n + 1) + ': ';
129
+ const publicKey = one(p, 'publickey');
130
+ if (!publicKey || !KEY.test(publicKey))
131
+ return refuse(who + 'PublicKey is missing or not a WireGuard key');
132
+ const preshared = one(p, 'presharedkey');
133
+ if (preshared !== undefined && !KEY.test(preshared))
134
+ return refuse(who + 'PresharedKey is not a WireGuard key');
135
+ const allowed = joined(p, 'allowedips');
136
+ if (allowed.length === 0 || !allowed.every(validCidr))
137
+ return refuse(who + 'AllowedIPs is missing or invalid');
138
+ const endpoint = one(p, 'endpoint');
139
+ const m = endpoint ? /^([^:[\]]+):(\d{1,5})$/.exec(endpoint) : null;
140
+ if (!m || !HOST.test(m[1]) || inRange(m[2], 1, 65535) === null)
141
+ return refuse(who + 'Endpoint must be host:port with a name or an IPv4 address');
142
+ if (/^\d+\.\d+\.\d+\.\d+$/.test(m[1]) && !net.isIPv4(m[1]))
143
+ return refuse(who + 'Endpoint is not a valid address');
144
+ const keepText = one(p, 'persistentkeepalive');
145
+ let keepalive;
146
+ if (keepText !== undefined && keepText.toLowerCase() !== 'off') {
147
+ const k = inRange(keepText, 0, 65535);
148
+ if (k === null)
149
+ return refuse(who + 'PersistentKeepalive must be a number or off');
150
+ keepalive = k;
151
+ }
152
+ checked.push({
153
+ publicKey,
154
+ ...(preshared ? { presharedKey: preshared } : {}),
155
+ allowedIps: allowed,
156
+ endpointHost: m[1],
157
+ endpointPort: Number(m[2]),
158
+ ...(keepalive !== undefined ? { keepalive } : {}),
159
+ });
160
+ });
161
+ if (problems.length || !privateKey)
162
+ return { ok: false, problems };
163
+ return {
164
+ ok: true,
165
+ config: {
166
+ privateKey,
167
+ addresses,
168
+ dns,
169
+ ...(typeof mtu === 'number' ? { mtu } : {}),
170
+ ...(typeof listenPort === 'number' ? { listenPort } : {}),
171
+ peers: checked,
172
+ },
173
+ };
174
+ }
175
+ /** The checked config; throws, saying why (never with a key), when it is not safe. `what` names the file. */
176
+ export function assertSafeWireguard(text, what) {
177
+ const report = inspectWireguard(text);
178
+ if (report.ok)
179
+ return report.config;
180
+ throw new Error(what +
181
+ ' was refused because it could run programs or is not a plain WireGuard config:\n - ' +
182
+ report.problems.slice(0, 5).join('\n - '));
183
+ }
184
+ /** The config as text, written from the checked values. `endpointIps` puts an address in a peer's place. */
185
+ export function renderWireguard(config, endpointIps = {}) {
186
+ const out = ['[Interface]', 'PrivateKey = ' + config.privateKey, 'Address = ' + config.addresses.join(', ')];
187
+ if (config.dns.length)
188
+ out.push('DNS = ' + config.dns.join(', '));
189
+ if (config.mtu !== undefined)
190
+ out.push('MTU = ' + config.mtu);
191
+ if (config.listenPort !== undefined)
192
+ out.push('ListenPort = ' + config.listenPort);
193
+ config.peers.forEach((p, i) => {
194
+ out.push('', '[Peer]', 'PublicKey = ' + p.publicKey);
195
+ if (p.presharedKey)
196
+ out.push('PresharedKey = ' + p.presharedKey);
197
+ out.push('AllowedIPs = ' + p.allowedIps.join(', '), 'Endpoint = ' + (endpointIps[i] ?? p.endpointHost) + ':' + p.endpointPort);
198
+ if (p.keepalive !== undefined)
199
+ out.push('PersistentKeepalive = ' + p.keepalive);
200
+ });
201
+ return out.join('\r\n') + '\r\n';
202
+ }
203
+ export function wireguardEndpoints(config) {
204
+ return config.peers.map((p) => ({ host: p.endpointHost, port: p.endpointPort }));
205
+ }
206
+ /** The tunnel's own IPv4 address (what the kill switch lets through once the tunnel is up). */
207
+ export function wireguardTunnelAddress(config) {
208
+ const found = config.addresses.map((a) => a.split('/')[0]).find((a) => net.isIPv4(a));
209
+ if (!found)
210
+ throw new Error('the config has no IPv4 address');
211
+ return found;
212
+ }
213
+ /** True when some peer sends all IPv4 traffic through the tunnel (AllowedIPs 0.0.0.0/0). */
214
+ export function routesEverything(config) {
215
+ return config.peers.some((p) => p.allowedIps.some((a) => a === '0.0.0.0/0'));
216
+ }
217
+ /** Whether `sc query` output says the tunnel service is running. */
218
+ export function serviceRunning(scOutput) {
219
+ return /STATE\s*:\s*\d+\s+RUNNING/i.test(scOutput);
220
+ }
221
+ /** Whether `wg show <tunnel> latest-handshakes` says a peer has completed a handshake (a time that is not 0). */
222
+ export function handshakeDone(wgOutput) {
223
+ return wgOutput.split(/\r?\n/).some((line) => {
224
+ const t = /\s(\d+)\s*$/.exec(line);
225
+ return t !== null && Number(t[1]) > 0;
226
+ });
227
+ }
@@ -1,15 +1,15 @@
1
1
  import path from 'node:path';
2
2
  import { assertNoLinks } from '../core/util/index.js';
3
3
  import { hostClaudeDir, PluginShare } from '../host/profile/index.js';
4
- import { storageOf } from '../host/storage/index.js';
4
+ import { hostDataFolder, hostDataRoot, storageOf } from '../host/storage/index.js';
5
5
  import { hostClaudeMemDir, syncClaudeMem } from './claude-mem.js';
6
6
  import { readSyncState, writeSyncState } from './sync-state.js';
7
7
  import { syncHistoryIn, syncHistoryOut } from './history.js';
8
- const guestClaude = (dataPath) => path.join(dataPath, 'state', 'claude');
9
- const guestMem = (dataPath) => path.join(dataPath, 'state', 'claude-mem');
8
+ const guestClaude = (dataPath) => path.join(hostDataFolder(dataPath, 'state'), 'claude');
9
+ const guestMem = (dataPath) => path.join(hostDataFolder(dataPath, 'state'), 'claude-mem');
10
10
  /** Sync paths for the sandbox side, after checking nothing on the way there is a link planted by the sandbox. */
11
11
  const toPaths = (state, dataPath) => {
12
- assertNoLinks(dataPath, guestClaude(dataPath));
12
+ assertNoLinks(hostDataRoot(dataPath), guestClaude(dataPath));
13
13
  return {
14
14
  hostClaudeDir: state.hostClaudeDir,
15
15
  guestClaudeDir: guestClaude(dataPath),
@@ -38,7 +38,7 @@ export function syncProjectIn(dataPath, project) {
38
38
  : 0;
39
39
  }
40
40
  const memSync = (dataPath, state, direction) => {
41
- assertNoLinks(dataPath, guestMem(dataPath));
41
+ assertNoLinks(hostDataRoot(dataPath), guestMem(dataPath));
42
42
  return syncClaudeMem(hostClaudeMemDir(), guestMem(dataPath), state, direction);
43
43
  };
44
44
  /** Before launch: finish syncing the previous run out, then bring this PC's history (and claude-mem) in. */
@@ -22,12 +22,25 @@ export declare class Watcher {
22
22
  private awake;
23
23
  private views;
24
24
  private stops;
25
+ private watchdogTimer;
26
+ private watchdog;
27
+ private watchdogBusy;
28
+ private lastCpu;
29
+ private lastHostLog;
25
30
  constructor(dataPath: string, resume?: WatcherResume);
26
31
  /** Runs until the sandbox has closed and the final sync is done (or the end deadline passed). */
27
32
  run(): Promise<void>;
33
+ /** Contained storage of this data set (only when a mounted image is on record), else null. */
34
+ private contained;
35
+ /** The mounted image stays mounted until the sandbox is gone: this watcher holds it. */
36
+ private holdContained;
37
+ /** The sandbox is gone and the last sync done: let go of the image, which dismounts it when nothing else holds it. */
38
+ private releaseContained;
28
39
  private note;
29
40
  /** Everything that runs alongside the sandbox, in this order. */
30
41
  private start;
42
+ /** One watchdog sample: this PC's memory and CPU; asks the sandbox to slow down while the PC is low. Never throws. */
43
+ private watchHost;
31
44
  /** The repo guard, then history and (at the end) claude-mem back to this PC. */
32
45
  private sync;
33
46
  /** The running sessions and their conversations, for the next launch (resume.sessions). */
@@ -7,24 +7,29 @@ import { writeFileAtomic } from '../shared/io/index.js';
7
7
  import { refuseInSelfTest } from '../host/test-guard.js';
8
8
  import { errMsg, sleep } from '../core/util/index.js';
9
9
  import { RepoGuardStore } from '../host/guard/index.js';
10
- import { isSandboxRunning, spawnDetached } from '../host/sandbox/index.js';
10
+ import { isSandboxRunning, realContainedDeps, spawnDetached } from '../host/sandbox/index.js';
11
11
  import { readActiveViews, WorkspaceView } from '../host/workspace-view/index.js';
12
12
  import { readForwards, sandboxAddress, startPortForwards } from '../host/network/index.js';
13
13
  import { AuditCollector, readAuditSettings } from '../host/audit/collect.js';
14
14
  import { readMarkedProjects, saveDataSize, watchAndMark } from '../host/guard/index.js';
15
15
  import { sizeWithin } from '../core/disk.js';
16
- import { autoRestartAllowed, causeSince, KeepAwake, LifeRecord, saveCrashReport, SessionLedger, } from '../host/life/index.js';
16
+ import { autoRestartAllowed, causeSince, appendHostMemory, cpuPercentBetween, KeepAwake, LifeRecord, readHostMemoryAsync, readLoadSettings, REQUEST_SECONDS, WATCHDOG_START, watchdogStep, saveCrashReport, SessionLedger, } from '../host/life/index.js';
17
17
  import { SandboxChannel } from '../host/channel/index.js';
18
18
  import { ProjectRegistry } from '../host/projects/registry.js';
19
- import { storageOf } from '../host/storage/index.js';
19
+ import { ContainedStorage, setNameOf, storageOf } from '../host/storage/index.js';
20
20
  import { clearSecrets } from '../host/secrets/index.js';
21
21
  import { endSharing, sharingDeps } from '../host/sharing/index.js';
22
22
  import { PACKAGE_ROOT } from '../core/package-info.js';
23
23
  import { syncBack } from './sync-cycle.js';
24
+ import os from 'node:os';
24
25
  const WATCH_INTERVAL_MS = 60 * 1000;
25
26
  /** The first sync waits this long: the sandbox is still booting. */
26
27
  const FIRST_SYNC_MS = 30000;
27
28
  const AUDIT_FLUSH_MS = 10000;
29
+ /** How often this PC's memory and CPU are sampled (the sample runs asynchronously: it never holds anything up). */
30
+ const WATCHDOG_MS = 10000;
31
+ /** The host memory log gets a line this often, and at every sample while the PC is low. */
32
+ const HOST_LOG_EVERY_MS = 60000;
28
33
  /** After the sandbox closed: a moment for its last writes to land before the final sync. */
29
34
  const SETTLE_MS = 3000;
30
35
  /** The longest the watcher may take after the sandbox closed (final syncs) before it stops anyway. */
@@ -79,6 +84,11 @@ export class Watcher {
79
84
  awake = null;
80
85
  views = [];
81
86
  stops = [];
87
+ watchdogTimer = null;
88
+ watchdog = WATCHDOG_START;
89
+ watchdogBusy = false;
90
+ lastCpu = os.cpus();
91
+ lastHostLog = 0;
82
92
  constructor(dataPath, resume = { restartAfterCrash: false }) {
83
93
  this.dataPath = dataPath;
84
94
  this.resume = resume;
@@ -95,6 +105,7 @@ export class Watcher {
95
105
  fs.mkdirSync(path.dirname(watcherLog(this.dataPath)), { recursive: true });
96
106
  writeFileAtomic(watcherPidFile(this.dataPath), String(process.pid));
97
107
  this.start();
108
+ this.holdContained();
98
109
  try {
99
110
  await sleep(FIRST_SYNC_MS);
100
111
  while (isSandboxRunning()) {
@@ -116,10 +127,44 @@ export class Watcher {
116
127
  }
117
128
  finally {
118
129
  this.stop();
130
+ await this.releaseContained();
119
131
  }
120
132
  // After stop(): its pid file is gone, so the sandbox started again gets a watcher of its own.
121
133
  this.restartAfterCrash();
122
134
  }
135
+ /** Contained storage of this data set (only when a mounted image is on record), else null. */
136
+ contained() {
137
+ try {
138
+ const storage = new ContainedStorage(realContainedDeps(this.storageRoot, 64), setNameOf(this.dataPath));
139
+ return storage.isRecorded() ? storage : null;
140
+ }
141
+ catch {
142
+ // Not a name contained storage can use: there is none.
143
+ return null;
144
+ }
145
+ }
146
+ /** The mounted image stays mounted until the sandbox is gone: this watcher holds it. */
147
+ holdContained() {
148
+ try {
149
+ this.contained()?.hold({ id: 'watcher', pid: process.pid });
150
+ }
151
+ catch (e) {
152
+ this.note('contained storage: could not record the watcher: ' + errMsg(e));
153
+ }
154
+ }
155
+ /** The sandbox is gone and the last sync done: let go of the image, which dismounts it when nothing else holds it. */
156
+ async releaseContained() {
157
+ try {
158
+ const result = await this.contained()?.release('watcher');
159
+ if (result)
160
+ this.note(result.dismounted
161
+ ? 'contained storage dismounted'
162
+ : 'contained storage stays mounted: ' + (result.note ?? ''));
163
+ }
164
+ catch (e) {
165
+ this.note('contained storage: ' + errMsg(e));
166
+ }
167
+ }
123
168
  note(message) {
124
169
  try {
125
170
  fs.appendFileSync(watcherLog(this.dataPath), new Date().toISOString() + ' ' + message + '\n');
@@ -162,9 +207,51 @@ export class Watcher {
162
207
  }
163
208
  });
164
209
  this.stops = [...viewStops, stopForwards, stopMarking];
210
+ // Memory and CPU of this PC: when low, the sandbox is asked to slow its builds down (host/life/watchdog.ts).
211
+ this.watchdogTimer = setInterval(() => void this.watchHost(), WATCHDOG_MS);
212
+ this.watchdogTimer.unref();
165
213
  // sandbox.keepAwake, and the life record that tells the next launch how this run ended (host/life).
166
214
  this.awake = this.life.read()?.keepAwake ? KeepAwake.start(process.pid, storageRoot) : null;
167
215
  }
216
+ /** One watchdog sample: this PC's memory and CPU; asks the sandbox to slow down while the PC is low. Never throws. */
217
+ async watchHost() {
218
+ if (this.watchdogBusy)
219
+ return;
220
+ this.watchdogBusy = true;
221
+ try {
222
+ const settings = readLoadSettings(this.storageRoot, this.dataPath);
223
+ const memory = await readHostMemoryAsync(this.storageRoot);
224
+ const cpus = os.cpus();
225
+ const cpuPercent = cpuPercentBetween(this.lastCpu, cpus);
226
+ this.lastCpu = cpus;
227
+ const nowMs = Date.now();
228
+ const step = watchdogStep(this.watchdog, {
229
+ memory,
230
+ cpuPercent,
231
+ intervalSeconds: WATCHDOG_MS / 1000,
232
+ nowMs,
233
+ reserveHostMb: settings.reserveHostMb,
234
+ profile: settings.profile,
235
+ });
236
+ this.watchdog = step.state;
237
+ if (memory && (step.state.low || nowMs - this.lastHostLog >= HOST_LOG_EVERY_MS)) {
238
+ this.lastHostLog = nowMs;
239
+ appendHostMemory(path.join(this.storageRoot, 'logs', 'host-memory-' + path.basename(this.dataPath) + '.log'), memory, cpuPercent);
240
+ }
241
+ if (step.changed)
242
+ this.note(step.state.low
243
+ ? 'this PC is low (' + step.reason + '): asking the sandbox to slow its builds down'
244
+ : 'this PC has recovered: no longer asking the sandbox to slow down');
245
+ if (step.send)
246
+ new SandboxChannel(this.dataPath).pressure(step.reason, REQUEST_SECONDS);
247
+ }
248
+ catch (e) {
249
+ this.note('host watchdog: ' + errMsg(e));
250
+ }
251
+ finally {
252
+ this.watchdogBusy = false;
253
+ }
254
+ }
168
255
  /** The repo guard, then history and (at the end) claude-mem back to this PC. */
169
256
  sync(final) {
170
257
  try {
@@ -235,6 +322,8 @@ export class Watcher {
235
322
  /** Stops what start() started, syncs the filtered workspaces a last time and records the data folder's size. */
236
323
  stop() {
237
324
  this.awake?.stop();
325
+ if (this.watchdogTimer)
326
+ clearInterval(this.watchdogTimer);
238
327
  this.stops.forEach((stop) => stop());
239
328
  if (this.auditTimer)
240
329
  clearInterval(this.auditTimer);