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
@@ -0,0 +1,126 @@
1
+ // Running the shared core's actions inside the menu: an operation's questions become the menu's dialogs (yes/no over
2
+ // the screen, a text field, the path picker, a list), its fields the settings editors, its progress the page's notice,
3
+ // and what it said so far is shown above its next question. The result is one notice line: what it did, or what went
4
+ // wrong and how to fix it. A link leaves the menu for what needs the terminal (attach, a start).
5
+ import { operationById } from '../../app/features/defs.js';
6
+ import { runDefinition } from '../../app/registry.js';
7
+ import { Cancelled, ioFromAsker, SchemaNode, toWireError, } from '../../app/kernel/index.js';
8
+ import { errMsg } from '../../core/util/index.js';
9
+ import { Theme } from '../tui/index.js';
10
+ import { askPath } from './settings/ask-path.js';
11
+ import { ValueEditor } from './settings/value-editor.js';
12
+ async function ask(app, where, q, said) {
13
+ const lines = [...said, ...(q.lines ?? []), ...('problem' in q && q.problem ? [Theme.warn(q.problem)] : [])];
14
+ const path = [...where];
15
+ switch (q.kind) {
16
+ case 'confirm': {
17
+ if (!q.typed)
18
+ return app.ask(q.title, lines, q.yes ?? 'Yes', q.danger ?? false);
19
+ const typed = await app.text({ path, label: 'Type "' + q.typed + '" to confirm', about: [q.title, ...lines] });
20
+ return typed?.trim().toLowerCase() === q.typed;
21
+ }
22
+ case 'choose':
23
+ return app.choose(path, [q.title, ...lines].join(' '), q.choices, q.initial);
24
+ case 'checklist': {
25
+ const picked = await app.list({
26
+ path,
27
+ intro: [q.title, ...lines],
28
+ multi: true,
29
+ items: q.choices.map((ch) => ({ ...ch, checked: q.checked?.includes(ch.value) ?? false })),
30
+ });
31
+ return picked.kind === 'multi' ? picked.values : undefined;
32
+ }
33
+ case 'path':
34
+ return askPath(app, {
35
+ path,
36
+ title: q.title,
37
+ about: lines,
38
+ kind: q.pathKind,
39
+ initial: q.initial,
40
+ literals: q.literals,
41
+ });
42
+ case 'text':
43
+ case 'secret':
44
+ return app.text({
45
+ path,
46
+ label: q.title,
47
+ about: lines,
48
+ initial: q.kind === 'text' ? q.initial : undefined,
49
+ allowEmpty: q.kind === 'text' ? q.allowEmpty : false,
50
+ masked: q.kind === 'secret',
51
+ });
52
+ }
53
+ }
54
+ /** Where a link goes in the menu: what needs the terminal is run on the normal screen. */
55
+ function follow(target) {
56
+ switch (target.kind) {
57
+ case 'terminal':
58
+ return { exit: { kind: 'run', argv: ['attach', target.session] } };
59
+ case 'start-agent':
60
+ return { exit: { kind: 'run', argv: ['launch', '--harness', target.agent] } };
61
+ case 'command':
62
+ return { exit: { kind: 'run', argv: [...target.argv] } };
63
+ case 'view':
64
+ return {};
65
+ }
66
+ }
67
+ export class MenuOperations {
68
+ app;
69
+ ctx;
70
+ constructor(app, ctx) {
71
+ this.app = app;
72
+ this.ctx = ctx;
73
+ }
74
+ /** Does what an action of a page says: follows a link, or asks for its fields and runs its operation. */
75
+ async perform(action, where, busy) {
76
+ if (action.kind === 'link')
77
+ return follow(action.target);
78
+ const def = operationById(action.op);
79
+ if (!def)
80
+ return { notice: Theme.error(Theme.glyph.cross + ' Unknown operation ' + action.op + '.') };
81
+ const answers = await this.fields(action, def, where);
82
+ if (!answers)
83
+ return {};
84
+ return { reload: true, notice: await this.run(def, { ...action.input, ...answers }, where, busy) };
85
+ }
86
+ /** The values of an action's fields, from the editors that fit them (undefined: one was backed out of). */
87
+ async fields(action, def, where) {
88
+ const answers = {};
89
+ const editor = new ValueEditor(this.app);
90
+ for (const field of action.fields ?? []) {
91
+ const node = new SchemaNode(def.input).child(field.name);
92
+ if (!node)
93
+ continue;
94
+ const edited = await editor.edit([...where, field.label], [...field.key], node, action.initial?.[field.name]);
95
+ if (!edited)
96
+ return undefined;
97
+ answers[field.name] = edited.value;
98
+ }
99
+ return answers;
100
+ }
101
+ /**
102
+ * Runs a definition with this input and returns the notice to show: its text (✓), the problem and its fix (✘), or
103
+ * undefined when a question was declined (nothing changed, nothing to say).
104
+ */
105
+ async run(def, input, where, busy = () => undefined) {
106
+ const said = [];
107
+ const asker = async (q) => (await ask(this.app, where, q, said.splice(0)));
108
+ const io = ioFromAsker(asker, {
109
+ progress: (text) => busy(Theme.muted(text + '…')),
110
+ log: (text, tone) => said.push(tone === 'warn' || tone === 'danger' ? Theme.warn(text) : text),
111
+ });
112
+ try {
113
+ const output = await runDefinition(def.id, this.ctx, input, io);
114
+ const text = def.text?.(output);
115
+ return text ? Theme.ok(Theme.glyph.ok + ' ' + text) : undefined;
116
+ }
117
+ catch (e) {
118
+ if (e instanceof Cancelled)
119
+ return undefined;
120
+ const problem = toWireError(e);
121
+ if (problem.kind === 'bug')
122
+ return Theme.error(Theme.glyph.cross + ' ' + errMsg(e));
123
+ return (Theme.error(Theme.glyph.cross + ' ' + problem.message) + (problem.fix ? Theme.muted(' ' + problem.fix) : ''));
124
+ }
125
+ }
126
+ }
@@ -0,0 +1,27 @@
1
+ export interface LiveOptions {
2
+ /** Fetches the process list (default: tasklist, run without blocking). */
3
+ readonly fetch?: () => Promise<string>;
4
+ /** The status line from the facts now (reads the cached process list and small files; never runs a program). */
5
+ readonly derive: () => string;
6
+ /** The line changed: draw again. */
7
+ readonly onChange: () => void;
8
+ readonly intervalMs?: number;
9
+ }
10
+ export declare class LiveStatus {
11
+ private readonly o;
12
+ private tasklist;
13
+ private text;
14
+ private timer;
15
+ private pending;
16
+ private running;
17
+ constructor(o: LiveOptions);
18
+ /** The status line as of the last refresh. */
19
+ line(): string;
20
+ /** Reads the process list now and derives the line (one fetch at a time; a caller during one waits for it). */
21
+ refresh(): Promise<void>;
22
+ private derive;
23
+ /** Starts keeping the line fresh and answers the host's process questions from it (until stop). */
24
+ start(): Promise<void>;
25
+ /** Stops; the host's questions run their own programs again (a command that runs after the menu needs the truth). */
26
+ stop(): void;
27
+ }
@@ -0,0 +1,69 @@
1
+ // What the menu shows about the running sandbox (the status line), kept fresh in the background. Drawing and keys only
2
+ // read the last copy: the process list is fetched without blocking (every few seconds, and after an action), and the
3
+ // screen is told when what it shows has changed. While it runs, the host's own "is the sandbox running?" questions are
4
+ // answered from the same copy, so no page waits for a child process.
5
+ import { sandboxStatus } from '../../app/features/sandbox/index.js';
6
+ import { readTasklistAsync, useTasklistCache } from '../../host/sandbox/index.js';
7
+ /** The same rule as the web's live status: the sandbox status query's interval. */
8
+ const DEFAULT_INTERVAL_MS = sandboxStatus.live?.everyMs ?? 2500;
9
+ export class LiveStatus {
10
+ o;
11
+ tasklist;
12
+ text = '';
13
+ timer;
14
+ pending;
15
+ running = false;
16
+ constructor(o) {
17
+ this.o = o;
18
+ }
19
+ /** The status line as of the last refresh. */
20
+ line() {
21
+ return this.text;
22
+ }
23
+ /** Reads the process list now and derives the line (one fetch at a time; a caller during one waits for it). */
24
+ refresh() {
25
+ this.pending ??= (async () => {
26
+ try {
27
+ const list = await (this.o.fetch ?? readTasklistAsync)();
28
+ // Stopped meanwhile: a command may be running, and nothing here may touch the host's facts any more.
29
+ if (!this.running)
30
+ return;
31
+ this.tasklist = list;
32
+ this.derive();
33
+ }
34
+ catch {
35
+ // The line keeps what it showed; the next refresh tries again.
36
+ }
37
+ finally {
38
+ this.pending = undefined;
39
+ }
40
+ })();
41
+ return this.pending;
42
+ }
43
+ derive() {
44
+ const next = this.o.derive();
45
+ if (next === this.text)
46
+ return;
47
+ this.text = next;
48
+ if (this.running)
49
+ this.o.onChange();
50
+ }
51
+ /** Starts keeping the line fresh and answers the host's process questions from it (until stop). */
52
+ async start() {
53
+ if (this.running)
54
+ return;
55
+ this.running = true;
56
+ useTasklistCache(() => this.tasklist);
57
+ this.timer = setInterval(() => void this.refresh(), this.o.intervalMs ?? DEFAULT_INTERVAL_MS);
58
+ this.timer.unref();
59
+ await this.refresh();
60
+ }
61
+ /** Stops; the host's questions run their own programs again (a command that runs after the menu needs the truth). */
62
+ stop() {
63
+ this.running = false;
64
+ clearInterval(this.timer);
65
+ this.timer = undefined;
66
+ useTasklistCache(null);
67
+ this.tasklist = undefined;
68
+ }
69
+ }
@@ -0,0 +1,19 @@
1
+ import { z } from 'zod';
2
+ import type { MenuView } from './shell.js';
3
+ declare const MenuStateSchema: z.ZodObject<{
4
+ section: z.ZodString;
5
+ screens: z.ZodRecord<z.ZodString, z.ZodObject<{
6
+ category: z.ZodOptional<z.ZodString>;
7
+ search: z.ZodOptional<z.ZodString>;
8
+ }, z.core.$strict>>;
9
+ }, z.core.$strict>;
10
+ type MenuState = z.output<typeof MenuStateSchema>;
11
+ /** The view as it is stored: the section by name, so a menu with other sections still finds its way. */
12
+ export declare function viewToState(view: MenuView, labels: readonly string[]): MenuState;
13
+ /** A stored state as a view; a section that no longer exists falls back to the first. */
14
+ export declare function stateToView(state: MenuState, labels: readonly string[]): MenuView;
15
+ /** The view the last run left (undefined when there is none or the file is not valid). */
16
+ export declare function readMenuView(labels: readonly string[], dir?: string): MenuView | undefined;
17
+ /** Remembers the view for the next run. Never fails the menu. */
18
+ export declare function saveMenuView(view: MenuView, labels: readonly string[], dir?: string): void;
19
+ export {};
@@ -0,0 +1,53 @@
1
+ // Where the menu was left, kept in ~/.agent-sandbox/menu-state.json: the open section and, for each section, its
2
+ // category and search. A convenience only: a missing, invalid or unwritable file means the menu opens on Agents.
3
+ import path from 'node:path';
4
+ import { z } from 'zod';
5
+ import { userDir } from '../../core/config/index.js';
6
+ import { JsonFile } from '../../shared/io/index.js';
7
+ const ScreenSchema = z.strictObject({
8
+ category: z.string().max(100).optional(),
9
+ search: z.string().max(200).optional(),
10
+ });
11
+ const MenuStateSchema = z.strictObject({
12
+ section: z.string().max(40),
13
+ screens: z.record(z.string().max(40), ScreenSchema),
14
+ });
15
+ function stateFile(dir) {
16
+ return new JsonFile(MenuStateSchema, path.join(dir, 'menu-state.json'));
17
+ }
18
+ /** The view as it is stored: the section by name, so a menu with other sections still finds its way. */
19
+ export function viewToState(view, labels) {
20
+ const screens = {};
21
+ for (const [label, screen] of Object.entries(view.screens))
22
+ if (labels.includes(label) && (screen.category || screen.search))
23
+ screens[label] = screen;
24
+ return { section: labels[view.section] ?? labels[0] ?? '', screens };
25
+ }
26
+ /** A stored state as a view; a section that no longer exists falls back to the first. */
27
+ export function stateToView(state, labels) {
28
+ const section = labels.indexOf(state.section);
29
+ return {
30
+ section: section < 0 ? 0 : section,
31
+ screens: Object.fromEntries(Object.entries(state.screens).filter(([label]) => labels.includes(label))),
32
+ };
33
+ }
34
+ /** The view the last run left (undefined when there is none or the file is not valid). */
35
+ export function readMenuView(labels, dir = userDir()) {
36
+ try {
37
+ const state = stateFile(dir).read();
38
+ return state ? stateToView(state, labels) : undefined;
39
+ }
40
+ catch {
41
+ // The state is a convenience: an unreadable file counts as none.
42
+ return undefined;
43
+ }
44
+ }
45
+ /** Remembers the view for the next run. Never fails the menu. */
46
+ export function saveMenuView(view, labels, dir = userDir()) {
47
+ try {
48
+ stateFile(dir).write(viewToState(view, labels));
49
+ }
50
+ catch {
51
+ // Not remembering is fine.
52
+ }
53
+ }
@@ -1,9 +1,18 @@
1
1
  import type { Context } from '../commands/context.js';
2
+ import { Terminal } from '../tui/index.js';
3
+ /** What a test may replace: the terminal, the process list fetch and how often it is fetched. */
4
+ export interface MenuOptions {
5
+ readonly terminal?: Terminal;
6
+ readonly fetchTasklist?: () => Promise<string>;
7
+ readonly refreshMs?: number;
8
+ }
2
9
  export declare class Menu {
3
10
  private readonly ctx;
4
11
  private readonly runArgv;
5
12
  private readonly app;
6
- constructor(ctx: Context, runArgv: (argv: string[]) => Promise<number>);
13
+ private readonly live;
14
+ constructor(ctx: Context, runArgv: (argv: string[]) => Promise<number>, options?: MenuOptions);
15
+ /** The status line from the facts now: only ever called by the background refresh, never by a draw or a key. */
7
16
  private status;
8
17
  /** The sections, in order. */
9
18
  private shell;
@@ -11,7 +11,7 @@ import { lookAtSandbox } from '../commands/shared-use.js';
11
11
  import { commandLine } from '../history.js';
12
12
  import { commandSpec } from '../manifest.js';
13
13
  import { AgentCommandsService, LogsService } from '../services/index.js';
14
- import { App, ExitRequested, Frame, Text, Theme } from '../tui/index.js';
14
+ import { App, ExitRequested, Frame, Terminal, Text, Theme } from '../tui/index.js';
15
15
  import { AccountsPage } from './pages/accounts-page.js';
16
16
  import { AgentsPage } from './pages/agents-page.js';
17
17
  import { AppsPage } from './pages/apps-page.js';
@@ -19,19 +19,30 @@ import { MaintenancePage } from './pages/maintenance-page.js';
19
19
  import { SandboxPage } from './pages/sandbox-page.js';
20
20
  import { SessionsPage } from './pages/sessions-page.js';
21
21
  import { SettingsPage } from './pages/settings-page.js';
22
- import { ConfigFile } from './settings/config-file.js';
22
+ import { ConfigFile } from '../../app/features/settings/index.js';
23
23
  import { Draft } from './settings/draft.js';
24
- import { Shell } from './shell.js';
25
- /** Where the menu was last left in this run: it opens there again (a new run opens on Agents). */
24
+ import { LiveStatus } from './live.js';
25
+ import { readMenuView, saveMenuView } from './memory.js';
26
+ import { SECTION_LABELS, Shell } from './shell.js';
27
+ /** Where the menu was last left: it opens there again (also in a later run, from menu-state.json). */
26
28
  let lastView;
27
29
  export class Menu {
28
30
  ctx;
29
31
  runArgv;
30
- app = new App();
31
- constructor(ctx, runArgv) {
32
+ app;
33
+ live;
34
+ constructor(ctx, runArgv, options = {}) {
32
35
  this.ctx = ctx;
33
36
  this.runArgv = runArgv;
34
- this.app.status = () => this.status();
37
+ this.app = new App(options.terminal ?? new Terminal());
38
+ this.live = new LiveStatus({
39
+ fetch: options.fetchTasklist,
40
+ derive: () => this.status(),
41
+ onChange: () => this.app.requestRedraw(),
42
+ intervalMs: options.refreshMs,
43
+ });
44
+ this.app.status = () => this.live.line();
45
+ this.app.afterAction = () => this.live.refresh();
35
46
  try {
36
47
  Frame.version =
37
48
  JSON.parse(fs.readFileSync(path.join(ctx.packageRoot, 'package.json'), 'utf8'))
@@ -41,6 +52,7 @@ export class Menu {
41
52
  /* no version shown */
42
53
  }
43
54
  }
55
+ /** The status line from the facts now: only ever called by the background refresh, never by a draw or a key. */
44
56
  status() {
45
57
  const folder = Theme.muted(Text.middle(process.cwd(), 70));
46
58
  if (!isSandboxRunning())
@@ -77,18 +89,22 @@ export class Menu {
77
89
  }
78
90
  /** Runs the menu until you leave it, or until a command that takes over the terminal (attach) has run. */
79
91
  async start() {
80
- if (!process.stdin.isTTY) {
92
+ if (!this.app.terminal.interactive) {
81
93
  console.log('The menu needs an interactive terminal. Run sandbox --help for the commands.');
82
94
  return 1;
83
95
  }
84
96
  try {
85
97
  const shell = this.shell();
86
- await shell.restore(lastView ?? { section: 0, category: undefined });
98
+ await this.live.start();
99
+ await shell.restore(lastView ?? readMenuView(SECTION_LABELS) ?? { section: 0, screens: {} });
87
100
  for (;;) {
88
101
  const exit = await this.app.show(shell);
89
102
  lastView = shell.view();
103
+ saveMenuView(lastView, SECTION_LABELS);
90
104
  if (exit.kind === 'quit')
91
105
  return 0;
106
+ // A command (or a log) runs on its own facts, not on the menu's copy.
107
+ this.live.stop();
92
108
  if (exit.kind === 'view')
93
109
  await this.view(exit.file);
94
110
  else {
@@ -96,6 +112,7 @@ export class Menu {
96
112
  if (code !== undefined)
97
113
  return code;
98
114
  }
115
+ await this.live.start();
99
116
  await shell.reload();
100
117
  }
101
118
  }
@@ -106,6 +123,7 @@ export class Menu {
106
123
  throw e;
107
124
  }
108
125
  finally {
126
+ this.live.stop();
109
127
  this.app.close();
110
128
  }
111
129
  }
@@ -1,6 +1,6 @@
1
1
  import type { Context } from '../../commands/context.js';
2
2
  import { type App, type Category, type ListAction, type ListOptions, type ListResult } from '../../tui/index.js';
3
- import type { ConfigFile } from '../settings/config-file.js';
3
+ import type { ConfigFile } from '../../../app/features/settings/index.js';
4
4
  import type { Draft } from '../settings/draft.js';
5
5
  import { ListPage, type Outcome } from './list-page.js';
6
6
  export type WindowChoice = 'config' | 'hide' | 'show';
@@ -46,6 +46,8 @@ export declare class AgentsPage extends ListPage<string> {
46
46
  private window;
47
47
  private folder;
48
48
  private model;
49
+ /** What load() looked up (programs on PATH, the history file): not asked again for every list that is built. */
50
+ private facts;
49
51
  constructor(app: App, ctx: Context,
50
52
  /** Your own config file and its unsaved changes: where "Add an agent" writes to. */
51
53
  file: ConfigFile, draft: Draft);
@@ -11,7 +11,7 @@ import { commandSpec } from '../../manifest.js';
11
11
  import { Text, Theme, } from '../../tui/index.js';
12
12
  import { AddAgent, makeDefaultAgent, removeAgent } from '../add-agent.js';
13
13
  import { CommandForm } from '../command-form.js';
14
- import { askPath } from '../settings/path-kind.js';
14
+ import { askPath } from '../settings/ask-path.js';
15
15
  import { SeveralAgents } from '../several.js';
16
16
  import { ListPage } from './list-page.js';
17
17
  /** The agents of `harnesses` that are not built in. Pure. */
@@ -195,6 +195,8 @@ export class AgentsPage extends ListPage {
195
195
  window = 'config';
196
196
  folder = process.cwd();
197
197
  model;
198
+ /** What load() looked up (programs on PATH, the history file): not asked again for every list that is built. */
199
+ facts;
198
200
  constructor(app, ctx,
199
201
  /** Your own config file and its unsaved changes: where "Add an agent" writes to. */
200
202
  file, draft) {
@@ -220,12 +222,14 @@ export class AgentsPage extends ListPage {
220
222
  }
221
223
  current() {
222
224
  const harnesses = this.harnesses();
223
- const commands = new AgentCommandsService(this.ctx).status();
225
+ if (!this.facts)
226
+ throw new Error('The agents tab was not loaded');
227
+ const { commands, last } = this.facts;
224
228
  return {
225
229
  folder: this.folder,
226
230
  defaultAgent: this.file.userConfig().defaultHarness,
227
231
  agents: Object.keys(harnesses).map((name) => ({ name, ready: this.installed(name, harnesses) })),
228
- last: readHistory()[0]?.argv,
232
+ last,
229
233
  attach: this.attach,
230
234
  window: this.window,
231
235
  hostAgents: hostAgentsToShow(commands.enabled, commands.found),
@@ -236,9 +240,12 @@ export class AgentsPage extends ListPage {
236
240
  initialFocus() {
237
241
  return 'agent:' + this.file.userConfig().defaultHarness;
238
242
  }
239
- load() {
243
+ async load() {
244
+ this.facts = {
245
+ commands: await new AgentCommandsService(this.ctx).statusAsync(),
246
+ last: readHistory()[0]?.argv,
247
+ };
240
248
  this.model = this.current();
241
- return Promise.resolve();
242
249
  }
243
250
  options() {
244
251
  this.model = this.current();
@@ -1,6 +1,6 @@
1
1
  import { type App, type ListOptions, type ListResult } from '../../tui/index.js';
2
2
  import { AppsModel } from '../apps.js';
3
- import type { ConfigFile } from '../settings/config-file.js';
3
+ import type { ConfigFile } from '../../../app/features/settings/index.js';
4
4
  import type { Draft } from '../settings/draft.js';
5
5
  import { type Scope } from './scopes.js';
6
6
  import { ListPage, type Outcome } from './list-page.js';
@@ -22,5 +22,7 @@ export declare class AppsPage extends ListPage<string> {
22
22
  protected options(): ListOptions<string>;
23
23
  /** Every tick is staged as it happens, so leaving or saving always sees it. */
24
24
  protected afterKey(): Step<ShellExit>;
25
+ /** Opens the GitHub request form in the browser and says where it is. */
26
+ private request;
25
27
  protected act(result: ListResult<string>): Promise<Outcome<string>>;
26
28
  }
@@ -2,7 +2,8 @@
2
2
  // scopes like Settings: apps for every project, or for this project only.
3
3
  import { errMsg } from '../../../core/util/index.js';
4
4
  import { Theme } from '../../tui/index.js';
5
- import { AppsModel } from '../apps.js';
5
+ import { openIssuePage } from '../../../host/web/index.js';
6
+ import { AppsModel, requestAppUrl } from '../apps.js';
6
7
  import { ValueEditor } from '../settings/value-editor.js';
7
8
  import { SCOPES, scopeStatus } from './scopes.js';
8
9
  import { ListPage } from './list-page.js';
@@ -17,7 +18,10 @@ export function appsOptions(model, file, draft, scope) {
17
18
  enterTicks: true,
18
19
  categories: model.categories(),
19
20
  items: model.items(draft),
20
- actions: file.isProject ? [] : [{ key: 'c', label: 'your own programs' }],
21
+ actions: [
22
+ ...(file.isProject ? [] : [{ key: 'c', label: 'your own programs' }]),
23
+ { key: 'r', label: 'request an app' },
24
+ ],
21
25
  };
22
26
  }
23
27
  export class AppsPage extends ListPage {
@@ -50,12 +54,25 @@ export class AppsPage extends ListPage {
50
54
  this.model.stageTicks(this.draft, this.ticked());
51
55
  return;
52
56
  }
57
+ /** Opens the GitHub request form in the browser and says where it is. */
58
+ request() {
59
+ const url = requestAppUrl();
60
+ try {
61
+ openIssuePage(url);
62
+ return Theme.ok(Theme.glyph.ok + ' Opened the request form in your browser: ') + Theme.muted(url);
63
+ }
64
+ catch (e) {
65
+ return Theme.error(Theme.glyph.cross + ' ' + errMsg(e)) + Theme.muted(' Open it yourself: ' + url);
66
+ }
67
+ }
53
68
  async act(result) {
54
69
  this.model.stageTicks(this.draft, this.ticked());
55
70
  if (result.kind === 'tab') {
56
71
  this.scope = SCOPES[result.index].scope;
57
72
  return {};
58
73
  }
74
+ if (result.kind === 'action' && result.key === 'r')
75
+ return { notice: this.request() };
59
76
  if (result.kind !== 'action' || result.key !== 'c')
60
77
  return {};
61
78
  const node = this.file.schema.child('apps')?.child('custom');
@@ -20,6 +20,14 @@ export declare abstract class ListPage<T> implements Page {
20
20
  private focused;
21
21
  /** The entry that was last acted on: the cursor returns to it when the section opens again. */
22
22
  private lastFocus;
23
+ /** A search to start the next list with (restored from the last run); used once. */
24
+ private pendingQuery;
25
+ /** The data has arrived at least once (until then the list is "Loading…"). */
26
+ private loaded;
27
+ /** Counts loads, so that a slow one that was overtaken does not replace newer data. */
28
+ private generation;
29
+ /** A category asked for before the data arrived: it opens when it does. */
30
+ private wantedCategory;
23
31
  constructor(app: App);
24
32
  /** Loads the data the list shows (called when the tab opens and after actions that change it). */
25
33
  protected abstract load(): Promise<void>;
@@ -29,9 +37,15 @@ export declare abstract class ListPage<T> implements Page {
29
37
  protected initialFocus(): T | undefined;
30
38
  /** Does what the list returned. */
31
39
  protected abstract act(result: ListResult<T>): Promise<Outcome<T>>;
40
+ /** The data has not arrived yet (the tab shows "Loading…"). */
41
+ get loading(): boolean;
32
42
  get capturing(): boolean;
33
43
  /** The id of the open category, if the list has any. */
34
44
  get category(): string | undefined;
45
+ /** The search typed in the list ('' when none). */
46
+ get search(): string;
47
+ /** Shows the list with this search typed (the menu opens on the search it was left on). */
48
+ restoreSearch(text: string): void;
35
49
  categories(): readonly Category[];
36
50
  selectCategory(id: string): Promise<void>;
37
51
  setFocus(list: boolean): void;
@@ -41,7 +55,20 @@ export declare abstract class ListPage<T> implements Page {
41
55
  private build;
42
56
  /** The entries ticked in a multi-select list. */
43
57
  protected ticked(): T[];
58
+ /** Loads the data again; a slow load does not hold up the keys (the tab keeps what it showed, then updates). */
44
59
  open(): Promise<void>;
60
+ /**
61
+ * Loads the data and waits for it at most QUICK_LOAD_MS. In time, the list is built here (`build`) or by the caller;
62
+ * later, the list is built when the data arrives, on the entry and search the user has moved to meanwhile.
63
+ */
64
+ private reload;
65
+ private loadData;
66
+ /** Back on the entry used last, when it is still in the category that is open. */
67
+ private rebuildOpened;
68
+ /** Data that came after the keys stopped waiting: the cursor and the search stay where the user moved them. */
69
+ private rebuildLate;
70
+ private rebuildOn;
71
+ private openWantedCategory;
45
72
  /** Builds the list again, on `focus` when it is still there. */
46
73
  protected rebuild(focus: T | undefined, notice?: string): void;
47
74
  /** Shows `text` now, before slow work starts. */