@timurproko/a1 0.2.7-dev.1245 → 0.2.7-dev.1252

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 (342) hide show
  1. package/README.md +11 -36
  2. package/bin/cli.js +2 -17
  3. package/bin/ui.js +7 -48
  4. package/dist/app/session-shell/exit-notice.d.ts +2 -2
  5. package/dist/app/session-shell/exit-notice.js +10 -10
  6. package/dist/app/session-shell/prompt-submission.d.ts +2 -4
  7. package/dist/app/session-shell/prompt-submission.js +3 -23
  8. package/dist/app/session-shell/response-copy-transport.js +1 -2
  9. package/dist/app/session-shell/session-lifecycle.d.ts +1 -2
  10. package/dist/app/session-shell/session-lifecycle.js +2 -4
  11. package/dist/app/session-shell/session-shell-root.d.ts +0 -31
  12. package/dist/app/session-shell/session-shell-root.js +3 -4
  13. package/dist/app/session-shell/session-shell.js +6 -47
  14. package/dist/app/session-shell/session-viewport-controller.js +3 -6
  15. package/dist/app/session-shell/shell-host.js +2 -18
  16. package/dist/app/session-shell/transcript-document.d.ts +1 -2
  17. package/dist/app/session-shell/transcript-document.js +3 -28
  18. package/dist/app/session-shell/workflow-output.js +1 -1
  19. package/dist/app/session-shell/workflow-router.d.ts +1 -3
  20. package/dist/app/session-shell/workflow-router.js +1 -4
  21. package/dist/cli/dispatch.d.ts +1 -11
  22. package/dist/cli/dispatch.js +5 -92
  23. package/dist/cli/session-context.js +5 -7
  24. package/dist/cli/uninstall.d.ts +1 -5
  25. package/dist/cli/uninstall.js +6 -35
  26. package/dist/cli/version-stats.js +4 -7
  27. package/dist/composition/owned-settings.d.ts +0 -1
  28. package/dist/composition/owned-settings.js +0 -1
  29. package/dist/composition/owned-ui.d.ts +2 -24
  30. package/dist/composition/owned-ui.js +11 -87
  31. package/dist/composition/release-update-observation.d.ts +4 -4
  32. package/dist/composition/release-update-observation.js +17 -47
  33. package/dist/composition/session-backend.d.ts +0 -1
  34. package/dist/composition/session-runtime-registry.d.ts +3 -3
  35. package/dist/composition/settings-route-host.d.ts +0 -7
  36. package/dist/composition/settings-route-host.js +4 -10
  37. package/dist/composition/startup-route.d.ts +0 -1
  38. package/dist/composition/startup-route.js +0 -1
  39. package/dist/contracts/owned-ui/index.d.ts +1 -1
  40. package/dist/contracts/owned-ui/model.d.ts +0 -3
  41. package/dist/contracts/owned-ui/session-workflows.d.ts +1 -4
  42. package/dist/features/launch/development-launch.js +0 -1
  43. package/dist/features/launch/index.d.ts +1 -1
  44. package/dist/features/launch/intent.d.ts +1 -3
  45. package/dist/features/launch/intent.js +2 -4
  46. package/dist/features/launch/session-context.d.ts +2 -2
  47. package/dist/features/owned-ui/reference-screen-app.d.ts +0 -2
  48. package/dist/features/owned-ui/reference-screen-app.js +2 -9
  49. package/dist/features/owned-ui/settings-app.d.ts +0 -6
  50. package/dist/features/owned-ui/settings-app.js +5 -36
  51. package/dist/foundation/launch-context/index.d.ts +0 -12
  52. package/dist/foundation/launch-context/index.js +0 -26
  53. package/dist/foundation/lifecycle/claim-gate.d.ts +8 -0
  54. package/dist/foundation/lifecycle/claim-gate.js +37 -0
  55. package/dist/foundation/lifecycle/index.d.ts +5 -9
  56. package/dist/foundation/lifecycle/index.js +3 -4
  57. package/dist/foundation/lifecycle/model.d.ts +2 -59
  58. package/dist/foundation/lifecycle/model.js +0 -35
  59. package/dist/foundation/lifecycle/paths.d.ts +0 -24
  60. package/dist/foundation/lifecycle/paths.js +0 -40
  61. package/dist/foundation/lifecycle/process-liveness.d.ts +8 -0
  62. package/dist/foundation/lifecycle/process-liveness.js +18 -0
  63. package/dist/foundation/lifecycle/session-claim-lock.d.ts +1 -1
  64. package/dist/foundation/lifecycle/session-claim-lock.js +2 -2
  65. package/dist/foundation/lifecycle/session-repository-context.d.ts +6 -6
  66. package/dist/foundation/lifecycle/session-repository-context.js +2 -3
  67. package/dist/foundation/release/automatic-update.d.ts +1 -3
  68. package/dist/foundation/release/automatic-update.js +9 -30
  69. package/dist/foundation/release/bootstrap.d.ts +2 -25
  70. package/dist/foundation/release/bootstrap.js +33 -344
  71. package/dist/foundation/release/cohort-state.d.ts +21 -46
  72. package/dist/foundation/release/cohort-state.js +30 -62
  73. package/dist/foundation/release/dependency-certification.d.ts +1 -1
  74. package/dist/foundation/release/index.d.ts +10 -17
  75. package/dist/foundation/release/index.js +7 -11
  76. package/dist/foundation/release/latest-release.d.ts +1 -2
  77. package/dist/foundation/release/latest-release.js +1 -2
  78. package/dist/foundation/release/process-liveness.d.ts +2 -0
  79. package/dist/foundation/release/process-liveness.js +12 -0
  80. package/dist/foundation/release/release-gc.d.ts +12 -9
  81. package/dist/foundation/release/release-gc.js +34 -98
  82. package/dist/foundation/release/release-store.d.ts +2 -2
  83. package/dist/foundation/release/release-store.js +2 -2
  84. package/dist/foundation/release/restart-certification.js +1 -1
  85. package/dist/foundation/release/update-activation.d.ts +2 -9
  86. package/dist/foundation/release/update-activation.js +28 -43
  87. package/dist/foundation/release/update-execution-lease.js +1 -1
  88. package/dist/foundation/release/update-launch.d.ts +0 -2
  89. package/dist/foundation/release/update-launch.js +0 -7
  90. package/dist/foundation/release/update-recovery.js +1 -1
  91. package/dist/foundation/release/update-transaction.d.ts +1 -1
  92. package/dist/foundation/release/update-transaction.js +5 -2
  93. package/dist/foundation/release/update.d.ts +2 -10
  94. package/dist/foundation/release/update.js +23 -200
  95. package/dist/foundation/session-writer-lock/index.d.ts +14 -6
  96. package/dist/foundation/session-writer-lock/index.js +48 -103
  97. package/dist/foundation/startup/startup-runtime.d.ts +1 -1
  98. package/dist/foundation/terminal-cleanup/exit-notice.d.ts +6 -0
  99. package/dist/foundation/terminal-cleanup/exit-notice.js +36 -0
  100. package/dist/foundation/terminal-cleanup/index.d.ts +2 -1
  101. package/dist/foundation/terminal-cleanup/index.js +2 -1
  102. package/dist/foundation/terminal-cleanup/terminal-reset.d.ts +0 -5
  103. package/dist/foundation/terminal-cleanup/terminal-reset.js +0 -5
  104. package/dist/integrations/pi/components/index.d.ts +1 -1
  105. package/dist/integrations/pi/components/index.js +1 -1
  106. package/dist/integrations/pi/components/shell-editor-autocomplete.js +1 -4
  107. package/dist/integrations/pi/components/shell-footer-status.d.ts +1 -1
  108. package/dist/integrations/pi/components/shell-footer-status.js +2 -2
  109. package/dist/integrations/pi/components/shell-hotkey-sections.js +1 -1
  110. package/dist/integrations/pi/components/shell-presenters-info.d.ts +2 -5
  111. package/dist/integrations/pi/components/shell-presenters-info.js +4 -18
  112. package/dist/integrations/pi/components/shell-presenters-transcript.d.ts +1 -1
  113. package/dist/integrations/pi/components/shell-presenters-transcript.js +1 -1
  114. package/dist/integrations/pi/components/shell-shared-facade.d.ts +0 -2
  115. package/dist/integrations/pi/components/submitted-prompt-adapter.d.ts +2 -5
  116. package/dist/integrations/pi/components/submitted-prompt-adapter.js +2 -16
  117. package/dist/integrations/pi/components/upstream/components/session-footer.d.ts +1 -2
  118. package/dist/integrations/pi/components/upstream/components/session-footer.js +6 -8
  119. package/dist/integrations/pi/engine/adapter.d.ts +0 -1
  120. package/dist/integrations/pi/engine/adapter.js +1 -16
  121. package/dist/integrations/pi/engine/compaction-progress.js +13 -5
  122. package/dist/integrations/pi/engine/diagnostics-state.d.ts +0 -1
  123. package/dist/integrations/pi/engine/diagnostics-state.js +0 -14
  124. package/dist/integrations/pi/engine/prompt-suggestions.d.ts +0 -1
  125. package/dist/integrations/pi/engine/prompt-suggestions.js +0 -40
  126. package/dist/integrations/pi/engine/session-runtime.js +0 -2
  127. package/dist/integrations/pi/engine/workflow-runner.js +1 -1
  128. package/dist/integrations/pi/tui-runtime/adapter.d.ts +2 -2
  129. package/dist/integrations/pi/tui-runtime/adapter.js +2 -2
  130. package/dist/product-identity.d.ts +4 -6
  131. package/dist/product-identity.js +6 -11
  132. package/dist/product-identity.json +1 -40
  133. package/dist/runtime-payload-inventory.json +0 -3
  134. package/dist/ui/components/index.d.ts +2 -2
  135. package/dist/ui/components/index.js +1 -1
  136. package/dist/ui/components/submitted-prompt.d.ts +3 -13
  137. package/dist/ui/components/submitted-prompt.js +8 -41
  138. package/dist/ui/components/transcript-viewport.d.ts +1 -3
  139. package/dist/ui/components/transcript-viewport.js +1 -4
  140. package/dist/ui/settings/declarations.d.ts +3 -97
  141. package/dist/ui/settings/declarations.js +3 -78
  142. package/dist/ui/settings/migrations.js +18 -2
  143. package/docs/architecture/boundaries.md +22 -39
  144. package/docs/architecture/code-documentation.md +2 -2
  145. package/docs/architecture/internal-naming.md +9 -17
  146. package/docs/architecture/project-structure.md +30 -29
  147. package/docs/architecture/resource-and-data-policy.md +16 -31
  148. package/docs/architecture/toolchain.md +14 -19
  149. package/docs/architecture/ui-reference-provenance.md +2 -2
  150. package/docs/ci-release-runbook.md +6 -4
  151. package/docs/features/launch-profiles.md +11 -17
  152. package/docs/features/prompt-history.md +10 -11
  153. package/docs/local-worktree-cleanup.md +32 -1
  154. package/docs/manual-owned-ui-checkpoint.md +0 -1
  155. package/docs/openspec-archive-automation.md +11 -3
  156. package/docs/validation.md +5 -9
  157. package/docs/worktree-link-recovery.md +6 -6
  158. package/package.json +7 -19
  159. package/bin/guardian.js +0 -42
  160. package/bin/supervisor.js +0 -7
  161. package/bin/tab-launch-context.d.ts +0 -27
  162. package/bin/tab-launch-context.js +0 -94
  163. package/dist/app/session-shell/resident-tab-coordinator.d.ts +0 -38
  164. package/dist/app/session-shell/resident-tab-coordinator.js +0 -77
  165. package/dist/cli/tabs-request.d.ts +0 -7
  166. package/dist/cli/tabs-request.js +0 -1
  167. package/dist/cli/tabs.d.ts +0 -14
  168. package/dist/cli/tabs.js +0 -151
  169. package/dist/composition/resident-tab-bridge.d.ts +0 -41
  170. package/dist/composition/resident-tab-bridge.js +0 -209
  171. package/dist/composition/utility-tab.d.ts +0 -28
  172. package/dist/composition/utility-tab.js +0 -244
  173. package/dist/features/owned-ui/plus-menu-dialog.d.ts +0 -28
  174. package/dist/features/owned-ui/plus-menu-dialog.js +0 -89
  175. package/dist/features/resident-tab/auto-name.d.ts +0 -11
  176. package/dist/features/resident-tab/auto-name.js +0 -44
  177. package/dist/features/resident-tab/index.d.ts +0 -5
  178. package/dist/features/resident-tab/index.js +0 -7
  179. package/dist/features/resident-tab/shortcut-bindings-file.d.ts +0 -8
  180. package/dist/features/resident-tab/shortcut-bindings-file.js +0 -40
  181. package/dist/features/resident-tab/shortcut-declarations.d.ts +0 -13
  182. package/dist/features/resident-tab/shortcut-declarations.js +0 -26
  183. package/dist/features/resident-tab/shortcut-projection.d.ts +0 -29
  184. package/dist/features/resident-tab/shortcut-projection.js +0 -64
  185. package/dist/features/resident-tab/status-deriver.d.ts +0 -10
  186. package/dist/features/resident-tab/status-deriver.js +0 -12
  187. package/dist/foundation/launch-guardian/exit-watcher.d.ts +0 -14
  188. package/dist/foundation/launch-guardian/exit-watcher.js +0 -103
  189. package/dist/foundation/launch-guardian/index.d.ts +0 -2
  190. package/dist/foundation/launch-guardian/index.js +0 -1
  191. package/dist/foundation/launch-guardian/main.d.ts +0 -39
  192. package/dist/foundation/launch-guardian/main.js +0 -232
  193. package/dist/foundation/lifecycle/commands.d.ts +0 -40
  194. package/dist/foundation/lifecycle/commands.js +0 -1
  195. package/dist/foundation/lifecycle/launch-instance.d.ts +0 -46
  196. package/dist/foundation/lifecycle/launch-instance.js +0 -134
  197. package/dist/foundation/lifecycle/native-claim-gate.d.ts +0 -5
  198. package/dist/foundation/lifecycle/native-claim-gate.js +0 -41
  199. package/dist/foundation/lifecycle/supervisor-startup.d.ts +0 -30
  200. package/dist/foundation/lifecycle/supervisor-startup.js +0 -106
  201. package/dist/foundation/process-containment/addon-containment.d.ts +0 -20
  202. package/dist/foundation/process-containment/addon-containment.js +0 -139
  203. package/dist/foundation/process-containment/addon-process-inspector.d.ts +0 -11
  204. package/dist/foundation/process-containment/addon-process-inspector.js +0 -36
  205. package/dist/foundation/process-containment/addon.d.ts +0 -47
  206. package/dist/foundation/process-containment/addon.js +0 -57
  207. package/dist/foundation/process-containment/artifact.d.ts +0 -1
  208. package/dist/foundation/process-containment/artifact.js +0 -35
  209. package/dist/foundation/process-containment/contracts.d.ts +0 -33
  210. package/dist/foundation/process-containment/contracts.js +0 -35
  211. package/dist/foundation/process-containment/index.d.ts +0 -9
  212. package/dist/foundation/process-containment/index.js +0 -7
  213. package/dist/foundation/process-containment/linux-process-inspector.d.ts +0 -7
  214. package/dist/foundation/process-containment/linux-process-inspector.js +0 -27
  215. package/dist/foundation/process-containment/native-process-inspector.d.ts +0 -4
  216. package/dist/foundation/process-containment/native-process-inspector.js +0 -21
  217. package/dist/foundation/protocol/client.d.ts +0 -18
  218. package/dist/foundation/protocol/client.js +0 -130
  219. package/dist/foundation/protocol/index.d.ts +0 -3
  220. package/dist/foundation/protocol/index.js +0 -2
  221. package/dist/foundation/protocol/messages.d.ts +0 -90
  222. package/dist/foundation/protocol/messages.js +0 -100
  223. package/dist/foundation/release/cohort-selection.d.ts +0 -33
  224. package/dist/foundation/release/cohort-selection.js +0 -67
  225. package/dist/foundation/release/endpoint-lock.d.ts +0 -2
  226. package/dist/foundation/release/endpoint-lock.js +0 -37
  227. package/dist/foundation/release/endpoint-recovery.d.ts +0 -10
  228. package/dist/foundation/release/endpoint-recovery.js +0 -45
  229. package/dist/foundation/release/endpoints.d.ts +0 -32
  230. package/dist/foundation/release/endpoints.js +0 -152
  231. package/dist/foundation/release/prepared-update-activation.d.ts +0 -3
  232. package/dist/foundation/release/prepared-update-activation.js +0 -27
  233. package/dist/foundation/release/process-cleanup.d.ts +0 -14
  234. package/dist/foundation/release/process-cleanup.js +0 -76
  235. package/dist/foundation/release/supervisor-identity.d.ts +0 -15
  236. package/dist/foundation/release/supervisor-identity.js +0 -63
  237. package/dist/foundation/resident-tabs/boot-identity.d.ts +0 -6
  238. package/dist/foundation/resident-tabs/boot-identity.js +0 -16
  239. package/dist/foundation/resident-tabs/bounded-host-command.d.ts +0 -20
  240. package/dist/foundation/resident-tabs/bounded-host-command.js +0 -62
  241. package/dist/foundation/resident-tabs/bridge-client.d.ts +0 -73
  242. package/dist/foundation/resident-tabs/bridge-client.js +0 -327
  243. package/dist/foundation/resident-tabs/continuity.d.ts +0 -12
  244. package/dist/foundation/resident-tabs/continuity.js +0 -63
  245. package/dist/foundation/resident-tabs/doctor.d.ts +0 -28
  246. package/dist/foundation/resident-tabs/doctor.js +0 -166
  247. package/dist/foundation/resident-tabs/draft-checkpoint.d.ts +0 -27
  248. package/dist/foundation/resident-tabs/draft-checkpoint.js +0 -145
  249. package/dist/foundation/resident-tabs/ensure-terminal-host.d.ts +0 -36
  250. package/dist/foundation/resident-tabs/ensure-terminal-host.js +0 -151
  251. package/dist/foundation/resident-tabs/fault-injection.d.ts +0 -8
  252. package/dist/foundation/resident-tabs/fault-injection.js +0 -26
  253. package/dist/foundation/resident-tabs/fixture-control.d.ts +0 -7
  254. package/dist/foundation/resident-tabs/fixture-control.js +0 -35
  255. package/dist/foundation/resident-tabs/host-entry.d.ts +0 -1
  256. package/dist/foundation/resident-tabs/host-entry.js +0 -17
  257. package/dist/foundation/resident-tabs/index.d.ts +0 -15
  258. package/dist/foundation/resident-tabs/index.js +0 -10
  259. package/dist/foundation/resident-tabs/maintenance.d.ts +0 -39
  260. package/dist/foundation/resident-tabs/maintenance.js +0 -75
  261. package/dist/foundation/resident-tabs/progress-observation-schema.d.ts +0 -34
  262. package/dist/foundation/resident-tabs/progress-observation-schema.js +0 -1
  263. package/dist/foundation/resident-tabs/progress-observation.d.ts +0 -4
  264. package/dist/foundation/resident-tabs/progress-observation.js +0 -150
  265. package/dist/foundation/resident-tabs/prompt-journal.d.ts +0 -63
  266. package/dist/foundation/resident-tabs/prompt-journal.js +0 -213
  267. package/dist/foundation/resident-tabs/protocol.d.ts +0 -12
  268. package/dist/foundation/resident-tabs/protocol.js +0 -195
  269. package/dist/foundation/resident-tabs/recovery.d.ts +0 -26
  270. package/dist/foundation/resident-tabs/recovery.js +0 -205
  271. package/dist/foundation/resident-tabs/release-holds.d.ts +0 -28
  272. package/dist/foundation/resident-tabs/release-holds.js +0 -78
  273. package/dist/foundation/resident-tabs/routes.d.ts +0 -5
  274. package/dist/foundation/resident-tabs/routes.js +0 -5
  275. package/dist/foundation/resident-tabs/routing.d.ts +0 -11
  276. package/dist/foundation/resident-tabs/routing.js +0 -10
  277. package/dist/foundation/resident-tabs/runtime-control.d.ts +0 -38
  278. package/dist/foundation/resident-tabs/runtime-control.js +0 -74
  279. package/dist/foundation/resident-tabs/session-sync.d.ts +0 -14
  280. package/dist/foundation/resident-tabs/session-sync.js +0 -37
  281. package/dist/foundation/resident-tabs/settings.d.ts +0 -16
  282. package/dist/foundation/resident-tabs/settings.js +0 -60
  283. package/dist/foundation/resident-tabs/terminal-host-artifact.d.ts +0 -14
  284. package/dist/foundation/resident-tabs/terminal-host-artifact.js +0 -78
  285. package/dist/foundation/resident-tabs/zip-archive.d.ts +0 -9
  286. package/dist/foundation/resident-tabs/zip-archive.js +0 -56
  287. package/dist/foundation/storage/control-store.d.ts +0 -20
  288. package/dist/foundation/storage/control-store.js +0 -331
  289. package/dist/foundation/storage/index.d.ts +0 -1
  290. package/dist/foundation/storage/index.js +0 -1
  291. package/dist/foundation/supervision/index.d.ts +0 -3
  292. package/dist/foundation/supervision/index.js +0 -3
  293. package/dist/foundation/supervision/main.d.ts +0 -1
  294. package/dist/foundation/supervision/main.js +0 -86
  295. package/dist/foundation/supervision/paths.d.ts +0 -1
  296. package/dist/foundation/supervision/paths.js +0 -1
  297. package/dist/foundation/supervision/server.d.ts +0 -46
  298. package/dist/foundation/supervision/server.js +0 -606
  299. package/dist/integrations/pi/engine/offline-settings-port.d.ts +0 -23
  300. package/dist/integrations/pi/engine/offline-settings-port.js +0 -54
  301. package/dist/native/darwin-arm64/manifest.json +0 -23
  302. package/dist/native/darwin-arm64/process-guardian.node +0 -0
  303. package/dist/native/darwin-arm64/terminal-host-THIRD-PARTY-NOTICES.txt +0 -10500
  304. package/dist/native/darwin-arm64/terminal-host.manifest.json +0 -46
  305. package/dist/native/darwin-arm64/terminal-host.node +0 -0
  306. package/dist/native/darwin-x64/manifest.json +0 -23
  307. package/dist/native/darwin-x64/process-guardian.node +0 -0
  308. package/dist/native/darwin-x64/terminal-host-THIRD-PARTY-NOTICES.txt +0 -10737
  309. package/dist/native/darwin-x64/terminal-host.manifest.json +0 -46
  310. package/dist/native/darwin-x64/terminal-host.node +0 -0
  311. package/dist/native/linux-x64/manifest.json +0 -23
  312. package/dist/native/linux-x64/process-guardian.node +0 -0
  313. package/dist/native/linux-x64/terminal-host-THIRD-PARTY-NOTICES.txt +0 -11226
  314. package/dist/native/linux-x64/terminal-host.manifest.json +0 -46
  315. package/dist/native/linux-x64/terminal-host.node +0 -0
  316. package/dist/native/win32-x64/manifest.json +0 -23
  317. package/dist/native/win32-x64/process-guardian.node +0 -0
  318. package/dist/native/win32-x64/terminal-host-THIRD-PARTY-NOTICES.txt +0 -10757
  319. package/dist/native/win32-x64/terminal-host.manifest.json +0 -46
  320. package/dist/native/win32-x64/terminal-host.node +0 -0
  321. package/docs/architecture/evidence/resident-tabs/linux-x64.md +0 -37
  322. package/docs/architecture/evidence/resident-tabs/macos-arm64.md +0 -36
  323. package/docs/architecture/evidence/resident-tabs/macos-x64.md +0 -35
  324. package/docs/architecture/evidence/resident-tabs/windows-x64.md +0 -36
  325. package/docs/architecture/evidence/terminal-host-spike/manual-one-pane-terminal-host.md +0 -70
  326. package/docs/architecture/evidence/terminal-host-spike/manual-two-by-two-basic-terminal-host.md +0 -32
  327. package/docs/architecture/evidence/terminal-host-spike/milestone-baseline.json +0 -115
  328. package/docs/architecture/evidence/terminal-host-spike/owned-ui-foundation-prerequisite.json +0 -48
  329. package/docs/architecture/evidence/terminal-host-spike/terminal-host-provenance.json +0 -125
  330. package/docs/architecture/evidence/terminal-host-spike/terminal-host-task-5-1.json +0 -56
  331. package/docs/architecture/evidence/terminal-host-spike/terminal-host-task-5-3.json +0 -174
  332. package/docs/architecture/evidence/terminal-host-spike/terminal-spike-acceptance-record.json +0 -18
  333. package/docs/architecture/native-fixture-isolation.md +0 -23
  334. package/docs/architecture/process-guardian-provenance.md +0 -36
  335. package/docs/architecture/resident-tabs-requirements.md +0 -18
  336. package/docs/architecture/resident-tabs.md +0 -668
  337. package/docs/architecture/terminal-host-proof-gate.md +0 -34
  338. package/docs/architecture/terminal-host-provenance.md +0 -81
  339. package/docs/architecture/terminal-host-spike-evidence.md +0 -39
  340. package/docs/launch-recovery.md +0 -23
  341. package/docs/manual-launch-instance-acceptance.md +0 -43
  342. package/docs/manual-resident-tabs-acceptance.md +0 -51
@@ -1,46 +0,0 @@
1
- import { rename, rm, writeFile } from "node:fs/promises";
2
- import { type SupervisorSnapshot } from "../lifecycle/index.js";
3
- import { type MaterializedRelease } from "../release/index.js";
4
- import { ControlStore } from "../storage/index.js";
5
- import { type ProductPaths } from "./paths.js";
6
- interface EndpointMetadataCommitOperations {
7
- readonly platform?: NodeJS.Platform;
8
- readonly write?: typeof writeFile;
9
- readonly replace?: typeof rename;
10
- readonly remove?: typeof rm;
11
- readonly wait?: (delayMs: number) => Promise<void>;
12
- }
13
- /** Atomically replaces endpoint metadata while tolerating bounded Windows reader sharing. */
14
- export declare function commitEndpointMetadata(path: string, source: string, operations?: EndpointMetadataCommitOperations): Promise<void>;
15
- /** Owns one release-cohort endpoint and the authenticated launch instances registered through it. */
16
- export declare class SupervisorServer {
17
- #private;
18
- readonly id: `${string}-${string}-${string}-${string}-${string}`;
19
- readonly bootNonce: string;
20
- readonly pidStartIdentity: string;
21
- readonly startedAt: string;
22
- readonly paths: ProductPaths;
23
- readonly store: ControlStore;
24
- readonly release: MaterializedRelease;
25
- readonly terminateProcess: (code: number) => void;
26
- readonly reconciliationDeadlineMs: number;
27
- readonly shutdownDeadlineMs: number;
28
- readonly readActiveReleaseId: (() => Promise<string | null>) | null;
29
- readonly supersededPollMs: number;
30
- constructor(store: ControlStore, paths: ProductPaths | undefined, release: MaterializedRelease, bootNonce?: string, terminateProcess?: (code: number) => void, reconciliationDeadlineMs?: number, shutdownDeadlineMs?: number,
31
- /**
32
- * Which release new sessions start on. A cohort that is no longer that release keeps
33
- * serving the instances it already has, takes no new ones, and exits when its last one
34
- * leaves. Absent, this supervisor never considers itself superseded.
35
- */
36
- readActiveReleaseId?: (() => Promise<string | null>) | null, supersededPollMs?: number);
37
- snapshot(): SupervisorSnapshot;
38
- listen(): Promise<void>;
39
- /** Whether new sessions start on some other release. */
40
- get superseded(): boolean;
41
- /** Leaves, taking this cohort's own endpoint artifacts with it. */
42
- retire(): Promise<void>;
43
- close(stopAgents?: boolean): Promise<void>;
44
- closeForReleaseReplacement(stopAgents: boolean): Promise<void>;
45
- }
46
- export {};
@@ -1,606 +0,0 @@
1
- import { randomUUID } from "node:crypto";
2
- import { chmod, lstat, mkdir, readFile, rename, rm, writeFile } from "node:fs/promises";
3
- import { createConnection, createServer } from "node:net";
4
- import { platform } from "node:os";
5
- import { dirname } from "node:path";
6
- import { assertLaunchInstanceOutcome, assertNativeProcessIdentity, assertProcessContainmentIdentity, } from "../lifecycle/index.js";
7
- import { encodeFrame, isCommandMessage, isControlHello, LineFrameDecoder, localControlHello, MAX_CONTROL_FRAME_BYTES, negotiateControlFeatures } from "../protocol/index.js";
8
- import { listRecordedEndpoints, readEndpointMetadata, supervisorProcessState, currentSupervisorStartIdentity, withEndpointLock, processIsAlive } from "../release/index.js";
9
- import { ControlStore } from "../storage/index.js";
10
- import { resolveProductPaths } from "./paths.js";
11
- import { PRODUCT_IDENTITY, PRODUCT_TEXT } from "../../product-identity.js";
12
- const WINDOWS_METADATA_REPLACE_RETRY_DELAYS_MS = [5, 10, 20, 40, 80, 160];
13
- /** Atomically replaces endpoint metadata while tolerating bounded Windows reader sharing. */
14
- export async function commitEndpointMetadata(path, source, operations = {}) {
15
- const temporary = `${path}.${process.pid}.tmp`;
16
- const write = operations.write ?? writeFile;
17
- const replace = operations.replace ?? rename;
18
- const remove = operations.remove ?? rm;
19
- const wait = operations.wait ?? (delayMs => new Promise(resolvePromise => setTimeout(resolvePromise, delayMs)));
20
- let committed = false;
21
- try {
22
- await write(temporary, source, { mode: 0o600 });
23
- for (let attempt = 0;; attempt += 1) {
24
- try {
25
- await replace(temporary, path);
26
- committed = true;
27
- return;
28
- }
29
- catch (error) {
30
- const delayMs = WINDOWS_METADATA_REPLACE_RETRY_DELAYS_MS[attempt];
31
- if ((operations.platform ?? platform()) !== "win32" || delayMs === undefined || !isWindowsSharingViolation(error))
32
- throw error;
33
- await wait(delayMs);
34
- }
35
- }
36
- }
37
- finally {
38
- if (!committed)
39
- await remove(temporary, { force: true }).catch(() => undefined);
40
- }
41
- }
42
- /** Owns one release-cohort endpoint and the authenticated launch instances registered through it. */
43
- export class SupervisorServer {
44
- id = randomUUID();
45
- bootNonce;
46
- pidStartIdentity = currentSupervisorStartIdentity();
47
- startedAt = new Date().toISOString();
48
- paths;
49
- #server = null;
50
- #ownsEndpoint = false;
51
- #instances = new Map();
52
- #instanceOwners = new Map();
53
- #clientIds = new Map();
54
- #clientInstances = new Map();
55
- #uncertainInstances = new Set();
56
- #reconciliations = new Map();
57
- #revision = 0;
58
- #clients = new Set();
59
- #results = new Map();
60
- #metadataWrites = Promise.resolve();
61
- #closing = false;
62
- #superseded = false;
63
- #supersededPoll = null;
64
- store;
65
- release;
66
- terminateProcess;
67
- reconciliationDeadlineMs;
68
- shutdownDeadlineMs;
69
- readActiveReleaseId;
70
- supersededPollMs;
71
- constructor(store, paths = resolveProductPaths(), release, bootNonce = randomUUID(), terminateProcess = code => process.exit(code), reconciliationDeadlineMs = 3_000, shutdownDeadlineMs = 1_500,
72
- /**
73
- * Which release new sessions start on. A cohort that is no longer that release keeps
74
- * serving the instances it already has, takes no new ones, and exits when its last one
75
- * leaves. Absent, this supervisor never considers itself superseded.
76
- */
77
- readActiveReleaseId = null, supersededPollMs = 1_000) {
78
- this.store = store;
79
- this.release = release;
80
- this.terminateProcess = terminateProcess;
81
- this.reconciliationDeadlineMs = reconciliationDeadlineMs;
82
- this.shutdownDeadlineMs = shutdownDeadlineMs;
83
- this.readActiveReleaseId = readActiveReleaseId;
84
- this.supersededPollMs = supersededPollMs;
85
- this.bootNonce = bootNonce;
86
- this.paths = paths;
87
- for (const instance of store.loadActiveLaunchInstances(bootNonce))
88
- this.#instances.set(instance.id, instance);
89
- }
90
- snapshot() {
91
- return {
92
- revision: this.#revision,
93
- activeInstances: [...this.#instances.values()].flatMap(instance => {
94
- if (instance.state !== "requested" && instance.state !== "active" && instance.state !== "stopping")
95
- return [];
96
- return [{ id: instance.id, profileId: instance.profileId, state: instance.state }];
97
- }),
98
- };
99
- }
100
- async listen() {
101
- await withEndpointLock(this.paths.endpointMetadataPath, async () => {
102
- if (this.#server)
103
- throw new Error("supervisor is already listening");
104
- await mkdir(this.paths.runtimeDir, { recursive: true, mode: 0o700 });
105
- await mkdir(this.paths.endpointsDir, { recursive: true, mode: 0o700 });
106
- const recorded = await readEndpointMetadata(this.paths.endpointMetadataPath);
107
- if (recorded && await supervisorProcessState(recorded) !== "gone") {
108
- throw Object.assign(new Error("another supervisor still owns this endpoint"), { code: "EADDRINUSE" });
109
- }
110
- if (platform() !== "win32") {
111
- if (this.paths.endpointDirectory)
112
- await ensureManagedEndpointDirectory(dirname(this.paths.endpoint));
113
- const existing = await lstat(this.paths.endpoint).catch(error => {
114
- if (error.code === "ENOENT")
115
- return null;
116
- throw error;
117
- });
118
- if (existing) {
119
- if (await endpointIsLive(this.paths.endpoint)) {
120
- throw Object.assign(new Error(PRODUCT_TEXT.diagnostic(`supervisor already owns ${this.paths.endpoint}`)), { code: "EADDRINUSE" });
121
- }
122
- await rm(this.paths.endpoint, { force: true });
123
- }
124
- }
125
- const server = createServer(socket => this.#attach(socket));
126
- this.#server = server;
127
- await new Promise((resolve, reject) => {
128
- server.once("error", reject);
129
- server.listen(this.paths.endpoint, () => {
130
- this.#ownsEndpoint = true;
131
- server.off("error", reject);
132
- resolve();
133
- });
134
- });
135
- // Concurrency: only the bound winner may reconcile shared control data. Other cohort boots
136
- // remain protected even when temporarily unresponsive; their instances never become ours.
137
- await withEndpointLock(this.paths.databasePath, async () => {
138
- // Concurrency: all cohorts sharing SQLite serialize boot reconciliation plus publication, so a new
139
- // cohort cannot insert instances between another cohort's inventory and stale-row update.
140
- const protectedBoots = [];
141
- for (const other of await listRecordedEndpoints(this.paths)) {
142
- if (other.paths.endpointMetadataPath !== this.paths.endpointMetadataPath
143
- && await supervisorProcessState(other.metadata) !== "gone")
144
- protectedBoots.push(other.metadata.bootNonce);
145
- }
146
- this.store.reconcilePriorBootLaunchInstances(this.bootNonce, new Date().toISOString(), protectedBoots);
147
- await this.#writeEndpointMetadata();
148
- });
149
- if (this.readActiveReleaseId) {
150
- const poll = setInterval(() => void this.#refreshCohortRole(), this.supersededPollMs);
151
- poll.unref();
152
- this.#supersededPoll = poll;
153
- }
154
- });
155
- }
156
- // Invariant: superseding a cohort never interrupts its held instances; it retires only when empty.
157
- async #refreshCohortRole() {
158
- if (this.#closing || !this.readActiveReleaseId)
159
- return;
160
- const active = await this.readActiveReleaseId().catch(() => null);
161
- if (active === null)
162
- return;
163
- this.#superseded = active !== this.release.releaseId;
164
- if (this.#superseded && this.#instances.size === 0)
165
- await this.retire();
166
- }
167
- /** Whether new sessions start on some other release. */
168
- get superseded() {
169
- return this.#superseded;
170
- }
171
- /** Leaves, taking this cohort's own endpoint artifacts with it. */
172
- async retire() {
173
- if (this.#closing)
174
- return;
175
- await this.close(false);
176
- }
177
- async close(stopAgents = false) {
178
- if (this.#closing)
179
- return;
180
- if (stopAgents && this.#ownsEndpoint && !await this.#drainInstances("update", this.shutdownDeadlineMs)) {
181
- throw new Error("active launch instances did not release ownership within the shutdown deadline");
182
- }
183
- this.#closing = true;
184
- if (this.#supersededPoll)
185
- clearInterval(this.#supersededPoll);
186
- this.#supersededPoll = null;
187
- for (const client of this.#clients)
188
- client.destroy();
189
- await withEndpointLock(this.paths.endpointMetadataPath, async () => {
190
- try {
191
- await this.#metadataWrites.catch(() => undefined);
192
- if (this.#ownsEndpoint) {
193
- // Concurrency: remove only our publication, before releasing the listening endpoint.
194
- // A contender that failed to bind must never erase the successful owner's discovery file.
195
- const metadata = await readFile(this.paths.endpointMetadataPath, "utf8").then(source => JSON.parse(source)).catch(() => null);
196
- if (metadata?.supervisorId === this.id && metadata.bootNonce === this.bootNonce) {
197
- await rm(this.paths.endpointMetadataPath, { force: true });
198
- }
199
- // Platform: Node unlinks its Unix socket when the server closes; unlinking it early would
200
- // let a replacement bind before the old server finishes releasing that path.
201
- }
202
- }
203
- finally {
204
- // Platform: a metadata sharing error must not leave a bound but undiscoverable listener.
205
- if (this.#server)
206
- await new Promise(resolve => this.#server?.close(() => resolve()));
207
- this.#server = null;
208
- this.#ownsEndpoint = false;
209
- this.store.close();
210
- }
211
- });
212
- }
213
- async closeForReleaseReplacement(stopAgents) {
214
- await this.close(stopAgents);
215
- this.terminateProcess(0);
216
- }
217
- #attach(socket) {
218
- socket.setNoDelay(true);
219
- const decoder = new LineFrameDecoder();
220
- let welcomed = false;
221
- socket.on("data", chunk => {
222
- try {
223
- for (const value of decoder.push(chunk)) {
224
- if (!welcomed) {
225
- if (isMessageType(value, "identity-probe")) {
226
- this.#send(socket, { type: "identity", supervisorId: this.id, bootNonce: this.bootNonce, pidStartIdentity: this.pidStartIdentity, releaseId: this.release.releaseId });
227
- continue;
228
- }
229
- if (isMessageType(value, "release-idle-ownership")) {
230
- const request = value;
231
- const identityMatches = request.bootNonce === this.bootNonce && typeof request.candidateReleaseId === "string";
232
- const released = identityMatches && this.#instances.size === 0;
233
- this.#send(socket, {
234
- type: "release-ownership-result",
235
- released,
236
- reason: released ? "idle cohort released ownership"
237
- : identityMatches ? "launch instances are still active" : "boot nonce or candidate release mismatch",
238
- liveInstanceIds: this.#liveInstanceIds(),
239
- });
240
- if (released)
241
- setTimeout(() => void this.closeForReleaseReplacement(false), 25);
242
- continue;
243
- }
244
- if (isMessageType(value, "release-update-ownership")) {
245
- const request = value;
246
- void this.#handleUpdateRelease(socket, request);
247
- continue;
248
- }
249
- if (!isControlHello(value)) {
250
- this.#send(socket, { type: "protocol-error", code: "invalid-control-handshake", message: `the first message must be a valid ${PRODUCT_TEXT.displayName} control feature handshake`, diagnostics: { received: value } });
251
- socket.destroy();
252
- return;
253
- }
254
- const serverHello = localControlHello();
255
- const negotiation = negotiateControlFeatures(value, serverHello);
256
- if (!negotiation.ok) {
257
- this.#send(socket, { type: "protocol-error", code: "incompatible-control-features", message: negotiation.diagnostic, diagnostics: negotiation });
258
- socket.destroy();
259
- return;
260
- }
261
- welcomed = true;
262
- this.#clients.add(socket);
263
- this.#clientIds.set(socket, value.clientId);
264
- this.#clientInstances.set(socket, new Set());
265
- this.#send(socket, { type: "server-hello", ...serverHello, releaseId: this.release.releaseId, supervisorId: this.id, bootNonce: this.bootNonce, pidStartIdentity: this.pidStartIdentity, negotiatedFeatures: negotiation.negotiatedFeatures, snapshot: this.snapshot() });
266
- }
267
- else if (isCommandMessage(value)) {
268
- void this.#execute(value.command, socket);
269
- }
270
- }
271
- }
272
- catch (error) {
273
- this.#send(socket, { type: "protocol-error", code: "framing-error", message: error instanceof Error ? error.message : String(error) });
274
- socket.destroy();
275
- }
276
- });
277
- socket.on("close", () => this.#onSocketClosed(socket));
278
- socket.on("error", () => this.#onSocketClosed(socket));
279
- }
280
- async #execute(command, socket) {
281
- const clientId = this.#clientIds.get(socket);
282
- if (!clientId) {
283
- this.#send(socket, { type: "protocol-error", code: "unauthenticated-command", message: "launch-instance commands require an authenticated client" });
284
- return;
285
- }
286
- const resultKey = `${clientId}:${command.requestId}`;
287
- const recorded = this.#results.get(resultKey);
288
- if (recorded) {
289
- this.#send(socket, { type: "command-result", result: recorded });
290
- return;
291
- }
292
- let result;
293
- try {
294
- if (command.type === "create-launch-instance")
295
- await this.#createLaunchInstance(command, socket, clientId);
296
- if (command.type === "activate-launch-instance")
297
- this.#activateLaunchInstance(command, socket, clientId);
298
- if (command.type === "begin-launch-instance-stop")
299
- this.#beginLaunchInstanceStop(command, socket, clientId);
300
- if (command.type === "complete-launch-instance")
301
- this.#completeLaunchInstance(command, socket, clientId);
302
- if (command.type === "reconcile-launch-instance") {
303
- this.#ownedInstance(command.instanceId, socket, clientId);
304
- await this.#reconcileLaunchInstance(command.instanceId);
305
- }
306
- if (command.type === "resynchronize")
307
- this.#send(socket, { type: "snapshot", snapshot: this.snapshot() });
308
- await this.#metadataWrites;
309
- result = { requestId: command.requestId, ok: true, revision: this.#revision };
310
- }
311
- catch (error) {
312
- const message = error instanceof Error ? error.message : String(error);
313
- const explicitCode = error instanceof Error && "code" in error ? error.code : undefined;
314
- const code = explicitCode === "release-superseded"
315
- ? "release-superseded"
316
- : /not found/i.test(message) ? "not-found"
317
- : /owner|ownership|authenticated/i.test(message) ? "ownership-error" : "driver-error";
318
- result = { requestId: command.requestId, ok: false, revision: this.#revision, error: { code, message } };
319
- }
320
- this.#results.set(resultKey, result);
321
- this.#send(socket, { type: "command-result", result });
322
- }
323
- async #createLaunchInstance(command, socket, clientId) {
324
- assertNativeProcessIdentity(command.guardianIdentity);
325
- const existing = this.store.loadLaunchInstance(command.instanceId);
326
- if (!existing && this.readActiveReleaseId) {
327
- const active = await this.readActiveReleaseId().catch(() => null);
328
- if (active !== null)
329
- this.#superseded = active !== this.release.releaseId;
330
- }
331
- if (this.#superseded && !existing) {
332
- throw Object.assign(new Error(PRODUCT_TEXT.diagnostic(`release ${this.release.releaseId} is no longer active for new sessions`)), {
333
- code: "release-superseded",
334
- });
335
- }
336
- if (existing) {
337
- if (existing.ownerClientId !== clientId
338
- || existing.profileId !== command.profileId
339
- || existing.shutdownPolicy !== command.shutdownPolicy
340
- || !sameProcessIdentity(existing.guardianIdentity, command.guardianIdentity)) {
341
- throw new Error("launch instance already exists with different authenticated ownership");
342
- }
343
- if (existing.state === "completed" || existing.state === "interrupted")
344
- throw new Error("launch instance already has a terminal outcome");
345
- this.#instances.set(existing.id, existing);
346
- this.#bindOwner(existing.id, socket);
347
- return;
348
- }
349
- const instance = {
350
- id: command.instanceId,
351
- ownerClientId: clientId,
352
- profileId: command.profileId,
353
- state: "requested",
354
- shutdownPolicy: command.shutdownPolicy,
355
- guardianIdentity: command.guardianIdentity,
356
- rootIdentity: null,
357
- containmentIdentity: null,
358
- createdAt: new Date().toISOString(),
359
- activatedAt: null,
360
- stoppingAt: null,
361
- completedAt: null,
362
- outcome: null,
363
- };
364
- this.store.createLaunchInstance(instance);
365
- this.#instances.set(instance.id, instance);
366
- this.#bindOwner(instance.id, socket);
367
- this.#mutated();
368
- }
369
- #activateLaunchInstance(command, socket, clientId) {
370
- assertNativeProcessIdentity(command.rootIdentity);
371
- assertProcessContainmentIdentity(command.containmentIdentity);
372
- const instance = this.#ownedInstance(command.instanceId, socket, clientId);
373
- if (instance.state === "active"
374
- && sameProcessIdentity(instance.rootIdentity, command.rootIdentity)
375
- && sameContainmentIdentity(instance.containmentIdentity, command.containmentIdentity))
376
- return;
377
- if (instance.state !== "requested")
378
- throw new Error("launch instance is not requested");
379
- const activatedAt = new Date().toISOString();
380
- if (!this.store.activateLaunchInstance(instance.id, clientId, command.rootIdentity, command.containmentIdentity, activatedAt)) {
381
- throw new Error("launch instance activation ownership mismatch");
382
- }
383
- this.#instances.set(instance.id, { ...instance, state: "active", rootIdentity: command.rootIdentity, containmentIdentity: command.containmentIdentity, activatedAt });
384
- this.#mutated();
385
- }
386
- #beginLaunchInstanceStop(command, socket, clientId) {
387
- const instance = this.#ownedInstance(command.instanceId, socket, clientId);
388
- if (instance.state === "stopping")
389
- return;
390
- if (instance.state !== "active")
391
- throw new Error("active launch instance not found");
392
- const stoppingAt = new Date().toISOString();
393
- if (!this.store.beginLaunchInstanceStop(instance.id, clientId, stoppingAt))
394
- throw new Error("launch instance stop ownership mismatch");
395
- this.#instances.set(instance.id, { ...instance, state: "stopping", stoppingAt });
396
- this.#mutated();
397
- }
398
- #completeLaunchInstance(command, socket, clientId) {
399
- assertLaunchInstanceOutcome(command.outcome);
400
- const stored = this.store.loadLaunchInstance(command.instanceId);
401
- if (!stored)
402
- throw new Error("launch instance not found");
403
- if (stored.ownerClientId !== clientId || this.#instanceOwners.get(stored.id) !== socket)
404
- throw new Error("launch instance ownership mismatch");
405
- if (stored.state === "completed" || stored.state === "interrupted") {
406
- if (stored.state === command.terminalState && JSON.stringify(stored.outcome) === JSON.stringify(command.outcome))
407
- return;
408
- throw new Error("launch instance already has a different terminal outcome");
409
- }
410
- const completedAt = new Date().toISOString();
411
- if (!this.store.completeLaunchInstance(stored.id, clientId, command.terminalState, command.outcome, completedAt)) {
412
- throw new Error("launch instance completion ownership mismatch");
413
- }
414
- this.#instances.delete(stored.id);
415
- this.#uncertainInstances.delete(stored.id);
416
- this.#unbindOwner(stored.id, socket);
417
- this.#mutated();
418
- // Invariant: the last session on a superseded cohort has left; there is nothing here to keep.
419
- if (this.#superseded && this.#instances.size === 0)
420
- void this.retire();
421
- }
422
- #ownedInstance(instanceId, socket, clientId) {
423
- const instance = this.#instances.get(instanceId) ?? this.store.loadLaunchInstance(instanceId);
424
- if (!instance || instance.state === "completed" || instance.state === "interrupted")
425
- throw new Error("active launch instance not found");
426
- if (instance.ownerClientId !== clientId || this.#instanceOwners.get(instanceId) !== socket)
427
- throw new Error("launch instance ownership mismatch");
428
- return instance;
429
- }
430
- #bindOwner(instanceId, socket) {
431
- const current = this.#instanceOwners.get(instanceId);
432
- if (current && current !== socket)
433
- throw new Error("launch instance already belongs to another authenticated connection");
434
- this.#instanceOwners.set(instanceId, socket);
435
- this.#clientInstances.get(socket)?.add(instanceId);
436
- }
437
- #unbindOwner(instanceId, socket) {
438
- this.#instanceOwners.delete(instanceId);
439
- this.#clientInstances.get(socket)?.delete(instanceId);
440
- }
441
- #onSocketClosed(socket) {
442
- if (!this.#clients.delete(socket) && !this.#clientIds.has(socket))
443
- return;
444
- const instanceIds = [...(this.#clientInstances.get(socket) ?? [])];
445
- this.#clientInstances.delete(socket);
446
- this.#clientIds.delete(socket);
447
- for (const instanceId of instanceIds) {
448
- if (this.#instanceOwners.get(instanceId) === socket)
449
- this.#instanceOwners.delete(instanceId);
450
- if (!this.#closing)
451
- void this.#reconcileLaunchInstance(instanceId);
452
- }
453
- }
454
- #reconcileLaunchInstance(instanceId) {
455
- const existing = this.#reconciliations.get(instanceId);
456
- if (existing)
457
- return existing;
458
- const reconciliation = this.#performReconciliation(instanceId).finally(() => this.#reconciliations.delete(instanceId));
459
- this.#reconciliations.set(instanceId, reconciliation);
460
- return reconciliation;
461
- }
462
- async #performReconciliation(instanceId) {
463
- const instance = this.#instances.get(instanceId);
464
- if (!instance)
465
- return;
466
- const deadline = Date.now() + this.reconciliationDeadlineMs;
467
- while (instance.rootIdentity && processIsAlive(instance.rootIdentity.pid) && Date.now() < deadline) {
468
- await new Promise(resolvePromise => setTimeout(resolvePromise, 25));
469
- }
470
- if (instance.state === "requested" || (instance.rootIdentity && !processIsAlive(instance.rootIdentity.pid))) {
471
- const outcome = { kind: "interrupted", reason: "owner-disconnect", message: "launch instance owner disconnected before reporting a terminal outcome" };
472
- if (this.store.completeLaunchInstance(instance.id, instance.ownerClientId, "interrupted", outcome, new Date().toISOString())) {
473
- this.#instances.delete(instance.id);
474
- this.#uncertainInstances.delete(instance.id);
475
- this.#mutated();
476
- }
477
- return;
478
- }
479
- this.#uncertainInstances.add(instance.id);
480
- await this.#writeEndpointMetadata();
481
- }
482
- async #handleUpdateRelease(socket, request) {
483
- const identityMatches = request.bootNonce === this.bootNonce && typeof request.targetVersion === "string";
484
- if (!identityMatches) {
485
- this.#send(socket, {
486
- type: "release-update-result",
487
- accepted: false,
488
- reason: "boot nonce or target version mismatch",
489
- liveInstanceIds: this.#liveInstanceIds(),
490
- });
491
- return;
492
- }
493
- const released = await this.#drainInstances("update", this.shutdownDeadlineMs);
494
- this.#send(socket, {
495
- type: "release-update-result",
496
- accepted: released,
497
- reason: released
498
- ? `verified ${PRODUCT_TEXT.displayName} owner drained all launch instances for immediate update shutdown`
499
- : "one or more launch instances did not release verified ownership within the update deadline",
500
- liveInstanceIds: this.#liveInstanceIds(),
501
- });
502
- if (released)
503
- setTimeout(() => void this.closeForReleaseReplacement(false), 25);
504
- }
505
- async #drainInstances(reason, timeoutMs) {
506
- if (this.#instances.size === 0)
507
- return true;
508
- this.#requestStopAll(reason);
509
- const deadline = Date.now() + timeoutMs;
510
- while (Date.now() < deadline) {
511
- if (this.#instances.size === 0) {
512
- // Concurrency: allow the terminal command result to flush before closing owner sockets.
513
- await new Promise(resolvePromise => setTimeout(resolvePromise, 25));
514
- return this.#instances.size === 0;
515
- }
516
- await new Promise(resolvePromise => setTimeout(resolvePromise, 25));
517
- }
518
- return this.#instances.size === 0;
519
- }
520
- #requestStopAll(reason) {
521
- for (const [instanceId, socket] of this.#instanceOwners) {
522
- this.#send(socket, { type: "stop-launch-instance", requestId: randomUUID(), instanceId, reason });
523
- }
524
- }
525
- #mutated() {
526
- this.#revision += 1;
527
- void this.#writeEndpointMetadata();
528
- }
529
- #liveInstanceIds() {
530
- return [...this.#instances.keys()];
531
- }
532
- #writeEndpointMetadata() {
533
- if (this.#closing)
534
- return Promise.resolve();
535
- const liveInstanceIds = this.#liveInstanceIds();
536
- const metadata = {
537
- schema: PRODUCT_IDENTITY.protocol.supervisorSchema,
538
- ...localControlHello(this.release.releaseId),
539
- supervisorId: this.id,
540
- pid: process.pid,
541
- pidStartIdentity: this.pidStartIdentity,
542
- nativeStartIdentity: this.pidStartIdentity,
543
- bootNonce: this.bootNonce,
544
- endpoint: this.paths.endpoint,
545
- databasePath: this.paths.databasePath,
546
- startedAt: this.startedAt,
547
- releaseId: this.release.releaseId,
548
- releaseRoot: this.release.releaseRoot,
549
- contentDigest: this.release.contentDigest,
550
- ownership: {
551
- state: this.#uncertainInstances.size > 0 ? "blocked" : liveInstanceIds.length > 0 ? "busy" : "idle",
552
- liveInstanceIds,
553
- nonResumableInstanceIds: liveInstanceIds,
554
- uncertainInstanceIds: [...this.#uncertainInstances],
555
- },
556
- };
557
- const commit = () => commitEndpointMetadata(this.paths.endpointMetadataPath, JSON.stringify(metadata, null, 2));
558
- // Platform: one exhausted filesystem replacement must fail its command, but it must not
559
- // poison every later ownership revision after the sharing condition has cleared.
560
- this.#metadataWrites = this.#metadataWrites.then(commit, commit);
561
- return this.#metadataWrites;
562
- }
563
- #send(socket, message) {
564
- if (socket.destroyed)
565
- return;
566
- const frame = encodeFrame(message);
567
- const bytes = Buffer.byteLength(frame, "utf8");
568
- if (bytes > MAX_CONTROL_FRAME_BYTES || socket.writableLength + bytes > MAX_CONTROL_FRAME_BYTES * 2) {
569
- socket.destroy(new Error("slow client exceeded the bounded control/output queue"));
570
- return;
571
- }
572
- socket.write(frame);
573
- }
574
- }
575
- function sameProcessIdentity(left, right) {
576
- return left !== null && left.pid === right.pid && left.startIdentity === right.startIdentity;
577
- }
578
- function sameContainmentIdentity(left, right) {
579
- return left !== null && left.provider === right.provider && left.token === right.token;
580
- }
581
- function isMessageType(value, type) {
582
- return typeof value === "object" && value !== null && "type" in value && value.type === type;
583
- }
584
- function isWindowsSharingViolation(error) {
585
- return error instanceof Error && "code" in error
586
- && (error.code === "EPERM" || error.code === "EACCES" || error.code === "EBUSY");
587
- }
588
- async function ensureManagedEndpointDirectory(path) {
589
- await mkdir(path, { recursive: true, mode: 0o700 });
590
- const metadata = await lstat(path);
591
- if (!metadata.isDirectory() || metadata.isSymbolicLink())
592
- throw new Error(`managed endpoint path is not a directory: ${path}`);
593
- const currentUser = process.getuid?.();
594
- if (currentUser !== undefined && metadata.uid !== currentUser)
595
- throw new Error(`managed endpoint directory has another owner: ${path}`);
596
- await chmod(path, 0o700);
597
- }
598
- async function endpointIsLive(endpoint) {
599
- return await new Promise(resolve => {
600
- const socket = createConnection(endpoint);
601
- const done = (live) => { socket.destroy(); resolve(live); };
602
- socket.once("connect", () => done(true));
603
- socket.once("error", () => done(false));
604
- setTimeout(() => done(false), 250).unref();
605
- });
606
- }
@@ -1,23 +0,0 @@
1
- import { resolvePiProjectTrustPreflight } from "./project-trust-preflight.js";
2
- import { PiSettingsBridge, type PiSettingKey } from "./settings-bridge.js";
3
- import { ModelRuntime, SettingsManager } from "../startup-public.js";
4
- export interface OfflinePiSettingsPortOptions {
5
- readonly cwd: string;
6
- readonly agentDir: string;
7
- readonly resolveTrust?: typeof resolvePiProjectTrustPreflight;
8
- readonly createSettingsManager?: (cwd: string, agentDir: string, projectTrusted: boolean) => SettingsManager;
9
- readonly createModelRuntime?: (options: Parameters<typeof ModelRuntime.create>[0]) => Promise<Pick<ModelRuntime, "getAvailableSnapshot">>;
10
- }
11
- export interface OfflinePiSettingsPort {
12
- readonly port: PiSettingsBridge;
13
- readonly manager: SettingsManager;
14
- /** Resolves after the local auth/model catalog has populated the per-model choices. */
15
- readonly modelsReady: Promise<void>;
16
- }
17
- /**
18
- * Pi settings for the resident Settings utility tab. It reads the same persisted manager as an
19
- * Agent, but every bound live effect is persistence-only because each Agent process owns its effect.
20
- */
21
- export declare function createOfflinePiSettingsPort(options: OfflinePiSettingsPortOptions): Promise<OfflinePiSettingsPort>;
22
- /** Keys the slim Settings port intentionally binds and exposes in bare mode. */
23
- export declare function offlinePiSettingKeys(): readonly PiSettingKey[];