@north-light/crouter 0.3.311 → 0.3.313

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 (520) hide show
  1. package/dist/api/client.d.ts +27 -30
  2. package/dist/api/client.js +80 -67
  3. package/dist/api/dto/broker-ops.d.ts +10 -20
  4. package/dist/api/dto/canvas.d.ts +1 -10
  5. package/dist/api/dto/health.d.ts +113 -0
  6. package/dist/api/dto/human-requests.d.ts +14 -3
  7. package/dist/api/dto/human.d.ts +2 -48
  8. package/dist/api/dto/human.js +3 -2
  9. package/dist/api/dto/inbox.d.ts +14 -0
  10. package/dist/api/dto/mail.d.ts +43 -0
  11. package/dist/api/dto/nodes.d.ts +11 -8
  12. package/dist/api/dto/recovery.d.ts +96 -0
  13. package/dist/api/dto/reviews.d.ts +2 -2
  14. package/dist/api/routes.d.ts +9 -6
  15. package/dist/api/routes.js +11 -11
  16. package/dist/build-root.d.ts +4 -0
  17. package/dist/build-root.js +11 -1
  18. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/__tests__/integration/provider-rotation.test.ts +35 -47
  19. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/provider-rotation.d.ts +1 -1
  20. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/provider-rotation.js +12 -5
  21. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/provider-rotation.ts +12 -5
  22. package/dist/cli.js +10 -1
  23. package/dist/clients/__tests__/helpers/human-daemon.d.ts +11 -0
  24. package/dist/clients/__tests__/helpers/human-daemon.js +60 -0
  25. package/dist/clients/attach/chrome/canvas-panels.js +1 -1
  26. package/dist/clients/attach/chrome/inbox-strip.d.ts +3 -3
  27. package/dist/clients/attach/chrome/inbox-strip.js +17 -9
  28. package/dist/clients/attach/chrome/ticket-panel.d.ts +2 -2
  29. package/dist/clients/attach/chrome/ticket-panel.js +77 -46
  30. package/dist/clients/attach/overlays/graph.js +6 -9
  31. package/dist/clients/attach/render/chat-view.d.ts +5 -4
  32. package/dist/clients/attach/render/chat-view.js +22 -13
  33. package/dist/clients/attach/render/page-block.d.ts +11 -7
  34. package/dist/clients/attach/render/page-block.js +64 -55
  35. package/dist/clients/attach/viewer.js +746 -735
  36. package/dist/clients/inbox/__tests__/integration/inbox-controller.test.js +56 -56
  37. package/dist/clients/inbox/__tests__/integration/stale-row-activation.test.js +45 -32
  38. package/dist/clients/inbox/controller.d.ts +42 -14
  39. package/dist/clients/inbox/controller.js +240 -124
  40. package/dist/clients/inbox/dto-adapt.d.ts +28 -0
  41. package/dist/clients/inbox/dto-adapt.js +93 -0
  42. package/dist/clients/inbox/page-adapter.d.ts +5 -4
  43. package/dist/clients/inbox/page-adapter.js +12 -12
  44. package/dist/clients/inbox/resolve.d.ts +10 -5
  45. package/dist/clients/inbox/resolve.js +13 -12
  46. package/dist/clients/inbox/review/review-client.d.ts +0 -1
  47. package/dist/clients/inbox/review/review-client.js +0 -4
  48. package/dist/clients/inbox/review-adapter.d.ts +2 -1
  49. package/dist/clients/inbox/review-adapter.js +3 -5
  50. package/dist/clients/inbox/surface.d.ts +2 -2
  51. package/dist/clients/inbox/surface.js +19 -8
  52. package/dist/clients/inbox/tui.d.ts +3 -3
  53. package/dist/commands/__tests__/human.test.js +31 -15
  54. package/dist/commands/__tests__/integration/dashboard-snapshot.test.js +36 -11
  55. package/dist/commands/{sys/__tests__ → __tests__/integration}/migrate.test.js +142 -36
  56. package/dist/commands/__tests__/revive-now-gate.test.js +2 -0
  57. package/dist/commands/api-client.d.ts +5 -4
  58. package/dist/commands/api-client.js +13 -7
  59. package/dist/commands/canvas-history/shared.js +1 -1
  60. package/dist/commands/canvas-history.js +1 -1
  61. package/dist/commands/canvas-roster.js +1 -1
  62. package/dist/commands/canvas.js +1 -2
  63. package/dist/commands/human/feedback.js +20 -17
  64. package/dist/commands/human/prompts.js +45 -36
  65. package/dist/commands/human/queue.js +73 -123
  66. package/dist/commands/human/review.js +6 -16
  67. package/dist/commands/memory/__tests__/command-selector-and-mutation-guards.test.js +2 -0
  68. package/dist/commands/memory/__tests__/memory-list-and-lint-ignore.test.js +2 -0
  69. package/dist/commands/memory/__tests__/repository-root-lint.test.js +2 -0
  70. package/dist/commands/node-inspect-artifacts.js +1 -1
  71. package/dist/commands/node-lifecycle-revive.d.ts +2 -2
  72. package/dist/commands/node-lifecycle-revive.js +9 -19
  73. package/dist/commands/pkg/plugin-manage.js +0 -1
  74. package/dist/commands/profile/show.js +4 -12
  75. package/dist/commands/surface-tmux-spread.js +1 -5
  76. package/dist/commands/sys/__tests__/config-model-ladders.test.js +23 -30
  77. package/dist/commands/sys/__tests__/sync-project-guidance.test.js +37 -11
  78. package/dist/commands/sys/branch.d.ts +2 -0
  79. package/dist/commands/sys/branch.js +19 -0
  80. package/dist/commands/sys/config.js +106 -33
  81. package/dist/commands/sys/daemon.d.ts +2 -0
  82. package/dist/commands/sys/daemon.js +130 -7
  83. package/dist/commands/sys/doctor.js +5 -15
  84. package/dist/commands/sys/migrate.js +21 -92
  85. package/dist/commands/sys/setup-settings-runner.js +4 -2
  86. package/dist/commands/sys.js +2 -15
  87. package/dist/core/__tests__/boot-fault-attempt.test.js +23 -1
  88. package/dist/core/__tests__/boot.test.js +32 -25
  89. package/dist/core/__tests__/broker-launch-candidates.test.js +49 -0
  90. package/dist/core/__tests__/canvas-inbox-watcher-hold.test.js +149 -176
  91. package/dist/core/__tests__/canvas-inbox-watcher-naming.test.js +59 -10
  92. package/dist/core/__tests__/canvas-inbox-watcher.test.js +76 -20
  93. package/dist/core/__tests__/canvas.test.js +18 -47
  94. package/dist/core/__tests__/child-death-wake.test.js +29 -7
  95. package/dist/core/__tests__/cli-help-contract.test.js +2 -0
  96. package/dist/core/__tests__/close.test.js +29 -10
  97. package/dist/core/__tests__/collectible.test.js +2 -1
  98. package/dist/core/__tests__/config-change-delta.test.js +2 -0
  99. package/dist/core/__tests__/context-intro.test.js +17 -5
  100. package/dist/core/__tests__/cron-armed-context-failure.test.js +17 -12
  101. package/dist/core/__tests__/cron-held-settlement.test.js +21 -3
  102. package/dist/core/__tests__/cron-node-sink-parked-root.test.js +29 -10
  103. package/dist/core/__tests__/cron-self-cancel-delivery.test.js +14 -2
  104. package/dist/core/__tests__/cron-timeout-descendants.test.js +13 -1
  105. package/dist/core/__tests__/daemon-boot.test.js +51 -14
  106. package/dist/core/__tests__/fixtures/fake-engine.d.ts +1 -1
  107. package/dist/core/__tests__/fixtures/fake-engine.js +8 -3
  108. package/dist/core/__tests__/focuses.test.js +10 -5
  109. package/dist/core/__tests__/helpers/harness.js +50 -18
  110. package/dist/core/__tests__/history-inbox.test.js +12 -16
  111. package/dist/core/__tests__/human-action-delivery.test.js +48 -28
  112. package/dist/core/__tests__/human-actions.test.js +7 -3
  113. package/dist/core/__tests__/inline-memory-refs.test.js +7 -2
  114. package/dist/core/__tests__/integration/broker-fork-seam.test.js +37 -10
  115. package/dist/core/__tests__/integration/broker-sdk-wiring.test.js +42 -0
  116. package/dist/core/__tests__/integration/broker-snapshot-history.test.js +42 -29
  117. package/dist/core/__tests__/integration/broker-wedge-supervision.test.js +36 -35
  118. package/dist/core/__tests__/integration/command-plugins.test.js +15 -6
  119. package/dist/core/__tests__/integration/deferred-no-wake.test.js +113 -92
  120. package/dist/core/__tests__/integration/host-teardown-process-group.test.js +9 -5
  121. package/dist/core/__tests__/integration/lifecycle-hooks.test.js +312 -1
  122. package/dist/core/__tests__/integration/plugin-revalidate.test.js +4 -0
  123. package/dist/core/__tests__/integration/profile-pause.test.js +4 -4
  124. package/dist/core/__tests__/integration/refresh-stall-recycle.test.js +9 -12
  125. package/dist/core/__tests__/integration/revive.test.js +67 -21
  126. package/dist/core/__tests__/integration/spawn-root.test.js +62 -19
  127. package/dist/core/__tests__/integration/subscription-delivery.test.js +33 -21
  128. package/dist/core/__tests__/integration/worktree-land.test.js +5 -2
  129. package/dist/core/__tests__/integration/worktree-reap.test.js +15 -1
  130. package/dist/core/__tests__/kickoff.test.js +36 -39
  131. package/dist/core/__tests__/lifecycle-terminal-reasons.test.js +2 -1
  132. package/dist/core/__tests__/lifecycle.test.js +2 -1
  133. package/dist/core/__tests__/live-lifecycle-prompt.test.js +13 -1
  134. package/dist/core/__tests__/memory-resolver-precedence.test.js +2 -0
  135. package/dist/core/__tests__/migration.test.js +145 -31
  136. package/dist/core/__tests__/model-pin-durability.test.js +4 -1
  137. package/dist/core/__tests__/model-routes-config.test.js +13 -5
  138. package/dist/core/__tests__/node-outcome.test.js +93 -26
  139. package/dist/core/__tests__/on-read-crouter-home-fence.test.js +4 -3
  140. package/dist/core/__tests__/on-read-dedup-resume.test.js +8 -7
  141. package/dist/core/__tests__/on-read-nested-store.test.js +8 -7
  142. package/dist/core/__tests__/passive-subscription.test.js +50 -157
  143. package/dist/core/__tests__/plugin-kinds.test.js +6 -4
  144. package/dist/core/__tests__/plugin-link-symlinked-scope-root.test.js +6 -1
  145. package/dist/core/__tests__/plugin-page-components.test.js +6 -4
  146. package/dist/core/__tests__/preview-registry-sync.test.js +2 -0
  147. package/dist/core/__tests__/profile-project-memory-delivery.test.js +35 -32
  148. package/dist/core/__tests__/prune.test.js +11 -5
  149. package/dist/core/__tests__/push-final-guard.test.js +14 -7
  150. package/dist/core/__tests__/relaunch-root.test.js +30 -2
  151. package/dist/core/__tests__/remote-canvas-target.test.js +7 -1
  152. package/dist/core/__tests__/repository-root-identity.test.js +4 -0
  153. package/dist/core/__tests__/review-model-floor.test.js +6 -3
  154. package/dist/core/__tests__/revive-all.test.js +13 -3
  155. package/dist/core/__tests__/revive-capacity.test.js +26 -4
  156. package/dist/core/__tests__/revive-parked-fresh.test.js +31 -8
  157. package/dist/core/__tests__/seam/broker-attach-stream.test.js +1 -1
  158. package/dist/core/__tests__/seam/broker-cap-freeze.test.js +4 -3
  159. package/dist/core/__tests__/seam/broker-crash-teardown.test.js +11 -14
  160. package/dist/core/__tests__/seam/broker-provider-retry.test.js +85 -71
  161. package/dist/core/__tests__/seam/dormancy-release.test.js +33 -19
  162. package/dist/core/__tests__/seam/held-deferred-human-prompt.test.js +7 -5
  163. package/dist/core/__tests__/seam/inbox-reference-guidance.test.js +20 -6
  164. package/dist/core/__tests__/seam/node-outcome-delivery.test.js +27 -3
  165. package/dist/core/__tests__/spawn-worktree-id-conflict.test.js +5 -4
  166. package/dist/core/__tests__/stop-guard.test.js +2 -1
  167. package/dist/core/__tests__/subscription-pool-credential-guard.test.js +4 -3
  168. package/dist/core/__tests__/warm-claim-preference-snapshot.test.js +19 -5
  169. package/dist/core/__tests__/yield-ensures-daemon.test.js +2 -1
  170. package/dist/core/bootstrap.js +2 -0
  171. package/dist/core/broker-client/__tests__/transport-socket.test.js +51 -6
  172. package/dist/core/broker-client/transport-socket.d.ts +1 -0
  173. package/dist/core/broker-client/transport-socket.js +13 -5
  174. package/dist/core/canvas/__tests__/attention.test.js +29 -35
  175. package/dist/core/canvas/__tests__/history-transcript.test.js +6 -6
  176. package/dist/core/canvas/__tests__/render-remote.test.js +25 -9
  177. package/dist/core/canvas/attention.d.ts +2 -5
  178. package/dist/core/canvas/attention.js +15 -64
  179. package/dist/core/canvas/boot.d.ts +6 -7
  180. package/dist/core/canvas/boot.js +5 -9
  181. package/dist/core/canvas/canvas.d.ts +13 -109
  182. package/dist/core/canvas/canvas.js +140 -401
  183. package/dist/core/canvas/crons.d.ts +2 -0
  184. package/dist/core/canvas/crons.js +16 -15
  185. package/dist/core/canvas/daemon-owner.js +10 -0
  186. package/dist/core/canvas/db.d.ts +2 -2
  187. package/dist/core/canvas/db.js +2 -18
  188. package/dist/core/canvas/deliveries.d.ts +104 -0
  189. package/dist/core/canvas/deliveries.js +354 -0
  190. package/dist/core/canvas/executions.d.ts +29 -0
  191. package/dist/core/canvas/executions.js +68 -0
  192. package/dist/core/canvas/history.js +35 -35
  193. package/dist/core/canvas/index.d.ts +3 -1
  194. package/dist/core/canvas/index.js +3 -1
  195. package/dist/core/canvas/migrations.js +20 -12
  196. package/dist/core/canvas/nav-model.js +1 -3
  197. package/dist/core/canvas/nav-render.d.ts +4 -4
  198. package/dist/core/canvas/nav-render.js +11 -21
  199. package/dist/core/canvas/node-sql-migration.d.ts +3 -0
  200. package/dist/core/canvas/node-sql-migration.js +261 -0
  201. package/dist/core/canvas/paths.d.ts +2 -16
  202. package/dist/core/canvas/paths.js +2 -31
  203. package/dist/core/canvas/recovery.d.ts +76 -0
  204. package/dist/core/canvas/recovery.js +588 -0
  205. package/dist/core/canvas/remote-canvas-source.js +4 -0
  206. package/dist/core/canvas/render-source.d.ts +16 -27
  207. package/dist/core/canvas/render-source.js +16 -45
  208. package/dist/core/canvas/sessions.d.ts +33 -0
  209. package/dist/core/canvas/sessions.js +78 -0
  210. package/dist/core/canvas/types.d.ts +33 -33
  211. package/dist/core/canvas/types.js +2 -5
  212. package/dist/core/canvas/waits.d.ts +18 -0
  213. package/dist/core/canvas/waits.js +70 -0
  214. package/dist/core/canvas/worktrees.d.ts +40 -0
  215. package/dist/core/canvas/worktrees.js +93 -0
  216. package/dist/core/command-hooks/artifact.d.ts +6 -0
  217. package/dist/core/command-hooks/artifact.js +22 -6
  218. package/dist/core/command-hooks/discovery.d.ts +2 -0
  219. package/dist/core/command-hooks/discovery.js +2 -0
  220. package/dist/core/command-hooks/lifecycle-catalog.d.ts +20 -2
  221. package/dist/core/command-hooks/lifecycle-catalog.js +7 -2
  222. package/dist/core/command-hooks/lifecycle-create.d.ts +4 -0
  223. package/dist/core/command-hooks/lifecycle-create.js +51 -0
  224. package/dist/core/command-hooks/schema.d.ts +2 -2
  225. package/dist/core/command-hooks/schema.js +5 -4
  226. package/dist/core/command-hooks/transport/exec-invoke.js +3 -1
  227. package/dist/core/command-hooks/transport/exec-lifecycle.d.ts +18 -8
  228. package/dist/core/command-hooks/transport/exec-lifecycle.js +89 -30
  229. package/dist/core/config.d.ts +17 -12
  230. package/dist/core/config.js +106 -155
  231. package/dist/core/errors.d.ts +1 -0
  232. package/dist/core/errors.js +4 -0
  233. package/dist/core/feed/__tests__/inbox-migrate.test.js +27 -31
  234. package/dist/core/feed/feed.d.ts +0 -6
  235. package/dist/core/feed/feed.js +68 -56
  236. package/dist/core/feed/inbox.d.ts +7 -81
  237. package/dist/core/feed/inbox.js +46 -378
  238. package/dist/core/feed/messages.d.ts +58 -0
  239. package/dist/core/feed/messages.js +217 -0
  240. package/dist/core/fs-utils.d.ts +4 -0
  241. package/dist/core/fs-utils.js +21 -1
  242. package/dist/core/human/answer.d.ts +1 -1
  243. package/dist/core/human/answer.js +1 -1
  244. package/dist/core/human/claim.d.ts +1 -1
  245. package/dist/core/human/claim.js +2 -4
  246. package/dist/core/human/component-docs.js +1 -1
  247. package/dist/core/human/feedback-companion.js +4 -3
  248. package/dist/core/human/feedback.d.ts +5 -8
  249. package/dist/core/human/feedback.js +57 -85
  250. package/dist/core/human/page-authoring.d.ts +30 -0
  251. package/dist/core/human/page-authoring.js +19 -0
  252. package/dist/core/human/requests.d.ts +136 -0
  253. package/dist/core/human/requests.js +515 -0
  254. package/dist/core/human/root.d.ts +0 -7
  255. package/dist/core/human/root.js +0 -17
  256. package/dist/core/human/scan.d.ts +2 -7
  257. package/dist/core/human/scan.js +48 -132
  258. package/dist/core/human/summary.d.ts +1 -1
  259. package/dist/core/human/summary.js +1 -1
  260. package/dist/core/human/tickets.d.ts +23 -83
  261. package/dist/core/human/tickets.js +92 -243
  262. package/dist/core/human/types.d.ts +5 -1
  263. package/dist/core/installed-plugins.js +8 -3
  264. package/dist/core/keybindings/__tests__/resolve.test.js +19 -17
  265. package/dist/core/locked-file.d.ts +6 -3
  266. package/dist/core/locked-file.js +36 -9
  267. package/dist/core/memory/extensions.js +2 -0
  268. package/dist/core/preview-registry.js +0 -1
  269. package/dist/core/profiles/__tests__/env-store.test.js +2 -0
  270. package/dist/core/profiles/default-binding.d.ts +0 -4
  271. package/dist/core/profiles/default-binding.js +12 -58
  272. package/dist/core/profiles/env-store.js +8 -3
  273. package/dist/core/profiles/manifest.d.ts +2 -37
  274. package/dist/core/profiles/manifest.js +35 -161
  275. package/dist/core/profiles/select.js +3 -0
  276. package/dist/core/review/__tests__/capture-origin.test.js +13 -2
  277. package/dist/core/review/__tests__/comments.test.js +28 -10
  278. package/dist/core/review/__tests__/remap.test.js +28 -10
  279. package/dist/core/review/__tests__/stage-identity.test.js +17 -7
  280. package/dist/core/review/birth.d.ts +1 -1
  281. package/dist/core/review/birth.js +34 -5
  282. package/dist/core/review/comments.js +1 -1
  283. package/dist/core/review/companion.d.ts +1 -1
  284. package/dist/core/review/companion.js +2 -2
  285. package/dist/core/review/document.js +1 -1
  286. package/dist/core/review/project.d.ts +5 -1
  287. package/dist/core/review/project.js +14 -36
  288. package/dist/core/review/realize.d.ts +3 -6
  289. package/dist/core/review/realize.js +51 -168
  290. package/dist/core/review/remap.js +1 -1
  291. package/dist/core/review/stage.d.ts +3 -2
  292. package/dist/core/review/stage.js +2 -3
  293. package/dist/core/review/store.d.ts +3 -21
  294. package/dist/core/review/store.js +140 -186
  295. package/dist/core/review/types.d.ts +0 -1
  296. package/dist/core/runtime/__tests__/install-id-cache-scoping.test.js +9 -5
  297. package/dist/core/runtime/__tests__/spawn-env.test.js +8 -9
  298. package/dist/core/runtime/broker/auth-reload.js +9 -10
  299. package/dist/core/runtime/broker/daemon-ops.d.ts +25 -8
  300. package/dist/core/runtime/broker/daemon-ops.js +42 -9
  301. package/dist/core/runtime/broker/extension-abort.d.ts +3 -0
  302. package/dist/core/runtime/broker/extension-abort.js +11 -0
  303. package/dist/core/runtime/broker/fault-retry.d.ts +15 -6
  304. package/dist/core/runtime/broker/fault-retry.js +142 -95
  305. package/dist/core/runtime/broker/inbox.d.ts +4 -32
  306. package/dist/core/runtime/broker/inbox.js +4 -92
  307. package/dist/core/runtime/broker/rebind.d.ts +1 -0
  308. package/dist/core/runtime/broker/rebind.js +21 -12
  309. package/dist/core/runtime/broker-persona-guidance.d.ts +1 -1
  310. package/dist/core/runtime/broker-persona-guidance.js +2 -13
  311. package/dist/core/runtime/broker.js +28 -22
  312. package/dist/core/runtime/close.d.ts +3 -9
  313. package/dist/core/runtime/close.js +16 -27
  314. package/dist/core/runtime/fault.d.ts +3 -93
  315. package/dist/core/runtime/fault.js +9 -419
  316. package/dist/core/runtime/kickoff.d.ts +3 -6
  317. package/dist/core/runtime/kickoff.js +12 -18
  318. package/dist/core/runtime/launch.js +1 -1
  319. package/dist/core/runtime/lifecycle.js +40 -7
  320. package/dist/core/runtime/nodes.d.ts +5 -1
  321. package/dist/core/runtime/nodes.js +26 -11
  322. package/dist/core/runtime/placement.js +2 -2
  323. package/dist/core/runtime/recycle.js +24 -14
  324. package/dist/core/runtime/reset.js +26 -7
  325. package/dist/core/runtime/revive.js +226 -217
  326. package/dist/core/runtime/spawn.d.ts +4 -1
  327. package/dist/core/runtime/spawn.js +43 -5
  328. package/dist/core/runtime/warm-pool.d.ts +2 -2
  329. package/dist/core/runtime/warm-pool.js +23 -3
  330. package/dist/core/scoped-state/db.d.ts +7 -0
  331. package/dist/core/scoped-state/db.js +92 -0
  332. package/dist/core/scoped-state/migrate.d.ts +11 -0
  333. package/dist/core/scoped-state/migrate.js +198 -0
  334. package/dist/core/scoped-state/paths.d.ts +9 -0
  335. package/dist/core/scoped-state/paths.js +27 -0
  336. package/dist/core/scoped-state/profiles.d.ts +12 -0
  337. package/dist/core/scoped-state/profiles.js +62 -0
  338. package/dist/core/scoped-state/providers.d.ts +16 -0
  339. package/dist/core/scoped-state/providers.js +8 -0
  340. package/dist/core/scoped-state/schema.d.ts +3 -0
  341. package/dist/core/scoped-state/schema.js +26 -0
  342. package/dist/core/scoped-state/settings.d.ts +28 -0
  343. package/dist/core/scoped-state/settings.js +82 -0
  344. package/dist/core/subscription-state.d.ts +29 -48
  345. package/dist/core/subscription-state.js +176 -712
  346. package/dist/core/worktree-close.js +4 -4
  347. package/dist/core/worktree-mutation-async.js +2 -2
  348. package/dist/core/worktree-sweep.js +11 -16
  349. package/dist/core/worktree.js +6 -12
  350. package/dist/daemon/__tests__/autostart-storm.test.js +1 -39
  351. package/dist/daemon/__tests__/helpers/source-daemon.d.ts +2 -2
  352. package/dist/daemon/__tests__/helpers/source-daemon.js +7 -6
  353. package/dist/daemon/__tests__/integration/migration-startup.test.js +59 -26
  354. package/dist/daemon/__tests__/node-deadline-lane.test.js +2 -1
  355. package/dist/daemon/__tests__/node-outcome-birth-invariants.test.js +41 -17
  356. package/dist/daemon/__tests__/node-outcome-delivery.test.js +39 -10
  357. package/dist/daemon/__tests__/reconciler-signature.test.js +2 -2
  358. package/dist/daemon/__tests__/subtree-filter-freshness.test.js +2 -2
  359. package/dist/daemon/api/__tests__/broker-settle-park.test.js +17 -12
  360. package/dist/daemon/api/__tests__/canvas-snapshot-fields.test.js +27 -5
  361. package/dist/daemon/api/__tests__/model-config.test.js +8 -8
  362. package/dist/daemon/api/__tests__/node-create-description.test.js +22 -5
  363. package/dist/daemon/api/__tests__/node-model-validation.test.js +21 -1
  364. package/dist/daemon/api/__tests__/nodes-activity-query.test.js +10 -12
  365. package/dist/daemon/api/__tests__/profile-launch-gates.test.js +6 -3
  366. package/dist/daemon/api/__tests__/profile-metadata-route.test.js +4 -1
  367. package/dist/daemon/api/__tests__/reopen-delivery.test.js +26 -8
  368. package/dist/daemon/api/__tests__/seam/api-server.test.js +24 -32
  369. package/dist/daemon/api/__tests__/seam/b10-attach-modelauth.test.js +4 -1
  370. package/dist/daemon/api/__tests__/seam/leaf-api-parity.test.js +5 -4
  371. package/dist/daemon/api/__tests__/seam/profile-delete.test.js +29 -43
  372. package/dist/daemon/api/__tests__/submit-result.test.js +3 -2
  373. package/dist/daemon/api/bridge.js +1 -1
  374. package/dist/daemon/api/handlers/bash-jobs.js +6 -7
  375. package/dist/daemon/api/handlers/broker-ops.js +47 -84
  376. package/dist/daemon/api/handlers/broker-recovery.d.ts +4 -0
  377. package/dist/daemon/api/handlers/broker-recovery.js +251 -0
  378. package/dist/daemon/api/handlers/canvas.js +2 -44
  379. package/dist/daemon/api/handlers/daemon.js +41 -0
  380. package/dist/daemon/api/handlers/feedback-comments.js +2 -2
  381. package/dist/daemon/api/handlers/health.js +10 -5
  382. package/dist/daemon/api/handlers/human-requests.js +168 -188
  383. package/dist/daemon/api/handlers/human.js +3 -148
  384. package/dist/daemon/api/handlers/inbox-page.d.ts +4 -0
  385. package/dist/daemon/api/handlers/inbox-page.js +89 -0
  386. package/dist/daemon/api/handlers/inbox.js +21 -120
  387. package/dist/daemon/api/handlers/messages.d.ts +1 -0
  388. package/dist/daemon/api/handlers/messages.js +73 -15
  389. package/dist/daemon/api/handlers/node-outcomes.js +58 -13
  390. package/dist/daemon/api/handlers/nodes.d.ts +1 -0
  391. package/dist/daemon/api/handlers/nodes.js +41 -42
  392. package/dist/daemon/api/handlers/reviews.js +4 -29
  393. package/dist/daemon/api/map.d.ts +25 -3
  394. package/dist/daemon/api/map.js +84 -14
  395. package/dist/daemon/api/server.js +68 -9
  396. package/dist/daemon/control.d.ts +18 -0
  397. package/dist/daemon/cron/disposition.d.ts +2 -0
  398. package/dist/daemon/cron/disposition.js +8 -5
  399. package/dist/daemon/cron/settle.js +37 -13
  400. package/dist/daemon/cron/sinks.d.ts +18 -1
  401. package/dist/daemon/cron/sinks.js +47 -93
  402. package/dist/daemon/crtrd-cli.js +4 -19
  403. package/dist/daemon/crtrd.d.ts +2 -0
  404. package/dist/daemon/crtrd.js +206 -81
  405. package/dist/daemon/fleet.js +5 -6
  406. package/dist/daemon/human/finish.d.ts +12 -24
  407. package/dist/daemon/human/finish.js +88 -285
  408. package/dist/daemon/human/settle.d.ts +40 -0
  409. package/dist/daemon/human/settle.js +89 -0
  410. package/dist/daemon/human/sweep.d.ts +20 -5
  411. package/dist/daemon/human/sweep.js +56 -60
  412. package/dist/daemon/manage.d.ts +21 -16
  413. package/dist/daemon/manage.js +85 -51
  414. package/dist/daemon/messaging/node-message.d.ts +1 -1
  415. package/dist/daemon/messaging/node-message.js +9 -12
  416. package/dist/daemon/park-activity.js +3 -3
  417. package/dist/daemon/reconcilers/bash-deadline.js +6 -7
  418. package/dist/daemon/reconcilers/broker-supervision.d.ts +1 -1
  419. package/dist/daemon/reconcilers/broker-supervision.js +41 -19
  420. package/dist/daemon/reconcilers/controller-death.js +6 -18
  421. package/dist/daemon/reconcilers/delivery-lane.d.ts +9 -0
  422. package/dist/daemon/reconcilers/delivery-lane.js +130 -0
  423. package/dist/daemon/reconcilers/live-obligation.js +3 -3
  424. package/dist/daemon/reconcilers/node-deadline.js +1 -1
  425. package/dist/daemon/reconcilers/node-lifecycle/freeze-lane.d.ts +1 -1
  426. package/dist/daemon/reconcilers/node-lifecycle/freeze-lane.js +10 -12
  427. package/dist/daemon/reconcilers/node-lifecycle/tick.js +5 -5
  428. package/dist/daemon/reconcilers/node-lifecycle/wants-execution.js +1 -1
  429. package/dist/daemon/reconcilers/pending-review-submit.js +3 -3
  430. package/dist/daemon/reconcilers/storage-maintenance.d.ts +2 -8
  431. package/dist/daemon/reconcilers/storage-maintenance.js +13 -43
  432. package/dist/daemon/review/companion.js +5 -11
  433. package/dist/daemon/review/deliver.d.ts +1 -5
  434. package/dist/daemon/review/deliver.js +3 -52
  435. package/dist/daemon/review/finish.d.ts +2 -27
  436. package/dist/daemon/review/finish.js +79 -263
  437. package/dist/daemon/review/sweep.d.ts +3 -10
  438. package/dist/daemon/review/sweep.js +13 -131
  439. package/dist/daemon/startup-policy.d.ts +0 -5
  440. package/dist/daemon/startup-policy.js +0 -5
  441. package/dist/hook-authoring.d.ts +28 -2
  442. package/dist/hook-authoring.js +53 -1
  443. package/dist/index.d.ts +3 -2
  444. package/dist/index.js +1 -1
  445. package/dist/migrations/__tests__/activation-concurrency.test.js +3 -2
  446. package/dist/migrations/__tests__/activation.test.js +9 -6
  447. package/dist/migrations/activation.d.ts +3 -2
  448. package/dist/migrations/activation.js +2 -4
  449. package/dist/migrations/corpus.d.ts +2 -5
  450. package/dist/migrations/corpus.js +9 -20
  451. package/dist/migrations/runner.d.ts +3 -1
  452. package/dist/migrations/runner.js +6 -2
  453. package/dist/pi-extensions/__tests__/canvas-context-intro.test.js +21 -89
  454. package/dist/pi-extensions/__tests__/canvas-goal-capture-envelope.test.js +4 -2
  455. package/dist/pi-extensions/__tests__/canvas-stophook-agentend.test.js +51 -33
  456. package/dist/pi-extensions/__tests__/canvas-stophook-context-nudge.test.js +16 -2
  457. package/dist/pi-extensions/__tests__/pre-command-gate.test.js +13 -1
  458. package/dist/pi-extensions/canvas-inbox-watcher.js +98 -244
  459. package/dist/pi-extensions/canvas-passive-context.d.ts +4 -8
  460. package/dist/pi-extensions/canvas-passive-context.js +85 -37
  461. package/dist/pi-extensions/canvas-stophook.js +47 -31
  462. package/dist/shared/env.d.ts +1 -0
  463. package/dist/shared/env.js +3 -0
  464. package/package.json +1 -1
  465. package/runtime.lock.json +5 -5
  466. package/dist/clients/attach/__tests__/page-block.test.js +0 -54
  467. package/dist/clients/inbox/review-visibility.d.ts +0 -5
  468. package/dist/clients/inbox/review-visibility.js +0 -19
  469. package/dist/commands/canvas-rebuild-index.d.ts +0 -2
  470. package/dist/commands/canvas-rebuild-index.js +0 -46
  471. package/dist/core/__tests__/human-cancel-guard.test.js +0 -110
  472. package/dist/core/__tests__/human-deliver.test.d.ts +0 -1
  473. package/dist/core/__tests__/human-deliver.test.js +0 -256
  474. package/dist/core/__tests__/human-node-not-supervised.test.d.ts +0 -1
  475. package/dist/core/__tests__/human-node-not-supervised.test.js +0 -89
  476. package/dist/core/__tests__/integration/human-deliver-e2e.test.d.ts +0 -1
  477. package/dist/core/__tests__/integration/human-deliver-e2e.test.js +0 -131
  478. package/dist/core/__tests__/phase4-review-store.test.d.ts +0 -1
  479. package/dist/core/__tests__/phase4-review-store.test.js +0 -144
  480. package/dist/core/__tests__/watchdog-abort-arms-retry.test.d.ts +0 -1
  481. package/dist/core/__tests__/watchdog-abort-arms-retry.test.js +0 -110
  482. package/dist/core/canvas/human-deliveries.d.ts +0 -39
  483. package/dist/core/canvas/human-deliveries.js +0 -75
  484. package/dist/core/canvas/node-outcome-deliveries.d.ts +0 -64
  485. package/dist/core/canvas/node-outcome-deliveries.js +0 -159
  486. package/dist/core/feed/passive.d.ts +0 -3
  487. package/dist/core/feed/passive.js +0 -53
  488. package/dist/core/human/__tests__/inbox-cancel-worker.d.ts +0 -1
  489. package/dist/core/human/__tests__/inbox-cancel-worker.js +0 -7
  490. package/dist/core/human/__tests__/inbox-claim-worker.d.ts +0 -1
  491. package/dist/core/human/__tests__/inbox-claim-worker.js +0 -17
  492. package/dist/core/human/__tests__/inbox-stale-claim-worker.d.ts +0 -1
  493. package/dist/core/human/__tests__/inbox-stale-claim-worker.js +0 -8
  494. package/dist/core/human/__tests__/integration/inbox-core.test.d.ts +0 -1
  495. package/dist/core/human/__tests__/integration/inbox-core.test.js +0 -145
  496. package/dist/core/human/__tests__/page-tickets.test.d.ts +0 -1
  497. package/dist/core/human/__tests__/page-tickets.test.js +0 -161
  498. package/dist/core/review/ticket-filter.d.ts +0 -17
  499. package/dist/core/review/ticket-filter.js +0 -21
  500. package/dist/core/runtime/broker/passive.d.ts +0 -17
  501. package/dist/core/runtime/broker/passive.js +0 -44
  502. package/dist/core/runtime/busy.d.ts +0 -46
  503. package/dist/core/runtime/busy.js +0 -150
  504. package/dist/daemon/__tests__/startup-block-marker.test.d.ts +0 -1
  505. package/dist/daemon/__tests__/startup-block-marker.test.js +0 -86
  506. package/dist/daemon/api/__tests__/review-terminal-conflict.test.d.ts +0 -1
  507. package/dist/daemon/api/__tests__/review-terminal-conflict.test.js +0 -98
  508. package/dist/daemon/human/deliver-action.d.ts +0 -3
  509. package/dist/daemon/human/deliver-action.js +0 -9
  510. package/dist/daemon/node-outcome/deliver-outcome.d.ts +0 -3
  511. package/dist/daemon/node-outcome/deliver-outcome.js +0 -9
  512. package/dist/daemon/reconcilers/human-delivery-lane.d.ts +0 -10
  513. package/dist/daemon/reconcilers/human-delivery-lane.js +0 -41
  514. package/dist/daemon/reconcilers/node-outcome-delivery-lane.d.ts +0 -10
  515. package/dist/daemon/reconcilers/node-outcome-delivery-lane.js +0 -41
  516. package/dist/daemon/startup-block-marker.d.ts +0 -25
  517. package/dist/daemon/startup-block-marker.js +0 -99
  518. /package/dist/{clients/attach/__tests__/page-block.test.d.ts → api/dto/mail.js} +0 -0
  519. /package/dist/{commands/sys/__tests__/migrate.test.d.ts → api/dto/recovery.js} +0 -0
  520. /package/dist/{core/__tests__/human-cancel-guard.test.d.ts → commands/__tests__/integration/migrate.test.d.ts} +0 -0
@@ -2,8 +2,10 @@
2
2
  import { setTimeout as sleep } from 'node:timers/promises';
3
3
  import { ApiError } from '../../../api/errors.js';
4
4
  import { getNode } from '../../../core/canvas/canvas.js';
5
- import { armOutcomeDelivery, disarmOutcomeDelivery, readOutcomeDelivery, } from '../../../core/canvas/node-outcome-deliveries.js';
5
+ import { armExternalActionDelivery, activateArmedExternalActionDelivery, readExternalActionDelivery } from '../../../core/canvas/deliveries.js';
6
+ import { withCanvasWrite } from '../../../core/canvas/db.js';
6
7
  import { OUTCOME_PAYLOAD_MAX_BYTES } from '../../../core/canvas/types.js';
8
+ import { composeNodeOutcomeDocument } from '../../../core/runtime/outcome-document.js';
7
9
  import { HUMAN_ACTION_NAME_PATTERN } from '../../../core/human/action-binding.js';
8
10
  import { resolveHumanAction } from '../../../core/human-actions.js';
9
11
  import { toNodeOutcomeDTO, toOutcomeDeliveryDTO } from '../map.js';
@@ -82,6 +84,53 @@ function parseRegistration(body) {
82
84
  }
83
85
  return body['payload'] === undefined ? { action } : { action, payload: body['payload'] };
84
86
  }
87
+ function outcomeKey(nodeId) { return `node-outcome:${nodeId}`; }
88
+ function outcomeView(row) {
89
+ const adapter = row.adapter;
90
+ if (adapter?.adapter !== 'subprocess' || !Array.isArray(adapter.argv) || !adapter.argv.every((arg) => typeof arg === 'string') || typeof adapter.cwd !== 'string')
91
+ throw new Error(`outcome delivery ${row.deliveryId} has an invalid subprocess adapter`);
92
+ return { nodeId: row.idempotencyKey?.slice('node-outcome:'.length) ?? '', state: row.state === 'handed_off' ? 'running' : row.state === 'acknowledged' ? 'accepted' : row.state, attempt: row.attempts, actionName: row.destinationId, argv: adapter.argv, cwd: adapter.cwd, configOrigin: typeof adapter.config_origin === 'string' ? adapter.config_origin : '', payload: adapter.payload ?? null, document: null, nextAttemptAt: row.nextAttemptAt ?? 0, claimedAt: row.claimedAt, claimOwner: row.claimOwner, acceptedAt: row.acknowledgedAt, permanentFailedAt: row.failedAt, lastFailure: row.lastFailure, createdAt: row.createdAt, updatedAt: row.updatedAt };
93
+ }
94
+ function nodeAtOutcome(node) {
95
+ const reason = node.outcome_reason;
96
+ let status;
97
+ let intent;
98
+ if (node.outcome_kind === 'result' || reason === 'finished' || reason === 'declined') {
99
+ status = 'done';
100
+ intent = 'done';
101
+ }
102
+ else if (reason === 'parked') {
103
+ status = 'done';
104
+ intent = 'parked';
105
+ }
106
+ else if (reason === 'closed' || reason === 'retired' || reason === 'stranded' || reason === 'deadline_exceeded') {
107
+ status = 'canceled';
108
+ intent = null;
109
+ }
110
+ else {
111
+ status = 'dead';
112
+ intent = null;
113
+ }
114
+ return { ...node, status, intent, terminal_reason: reason };
115
+ }
116
+ function armOutcomeExternalAction(node, target, payload) {
117
+ return withCanvasWrite((db) => {
118
+ const existing = readExternalActionDelivery(outcomeKey(node.node_id));
119
+ if (existing !== null && existing.state !== 'armed')
120
+ return null;
121
+ armExternalActionDelivery(db, { destinationId: target.action, adapter: { adapter: 'subprocess', argv: target.argv, cwd: target.cwd, config_origin: target.configOrigin, payload: payload ?? null }, idempotencyKey: outcomeKey(node.node_id) });
122
+ if (node.outcome_at !== null) {
123
+ if (node.outcome_kind === null || node.outcome_reason === null)
124
+ throw new Error(`outcome latch is incomplete for node ${node.node_id}`);
125
+ const document = JSON.stringify(composeNodeOutcomeDocument({ node: nodeAtOutcome(node), outcome: { kind: node.outcome_kind, reason: node.outcome_reason, at: node.outcome_at, revision: node.outcome_revision, detailJson: node.outcome_detail_json ?? null }, action: { name: target.action, payload: payload ?? null }, finalReport: node.final_report }));
126
+ activateArmedExternalActionDelivery(db, { idempotencyKey: outcomeKey(node.node_id), label: node.node_id, bodyJson: document, supersede: false });
127
+ }
128
+ const delivery = readExternalActionDelivery(outcomeKey(node.node_id));
129
+ if (delivery === null)
130
+ throw new Error(`outcome delivery was not registered for ${node.node_id}`);
131
+ return outcomeView(delivery);
132
+ });
133
+ }
85
134
  function resolveAction(action, cwd) {
86
135
  const resolution = resolveHumanAction(action, cwd);
87
136
  if (!('kind' in resolution))
@@ -97,14 +146,7 @@ function handlePutDelivery(ctx) {
97
146
  const node = requireNode(id);
98
147
  const request = parseRegistration(ctx.body);
99
148
  const target = resolveAction(request.action, node.cwd);
100
- const delivery = armOutcomeDelivery({
101
- nodeId: node.node_id,
102
- actionName: request.action,
103
- argv: target.argv,
104
- cwd: target.cwd,
105
- configOrigin: target.configOrigin,
106
- ...(request.payload === undefined ? {} : { payload: request.payload }),
107
- });
149
+ const delivery = armOutcomeExternalAction(node, { action: request.action, ...target }, request.payload);
108
150
  if (delivery === null) {
109
151
  throw new ApiError(409, 'outcome_delivery_settled', 'outcome delivery has already started or settled');
110
152
  }
@@ -113,19 +155,22 @@ function handlePutDelivery(ctx) {
113
155
  /** GET /v1/nodes/:id/outcome-delivery — inspect the one durable registration. */
114
156
  function handleGetDelivery(ctx) {
115
157
  const id = ctx.params['id'] ?? '';
116
- const delivery = readOutcomeDelivery(id);
158
+ const delivery = readExternalActionDelivery(outcomeKey(id));
117
159
  if (delivery === null)
118
160
  throw new ApiError(404, 'outcome_delivery_not_found', `no outcome delivery for node ${id}`);
119
- return { status: 200, body: toOutcomeDeliveryDTO(delivery) };
161
+ return { status: 200, body: toOutcomeDeliveryDTO(outcomeView(delivery)) };
120
162
  }
121
163
  /** DELETE /v1/nodes/:id/outcome-delivery — disarm only before delivery begins. */
122
164
  function handleDeleteDelivery(ctx) {
123
165
  const id = ctx.params['id'] ?? '';
124
- const current = readOutcomeDelivery(id);
166
+ const current = readExternalActionDelivery(outcomeKey(id));
125
167
  if (current !== null && current.state !== 'armed') {
126
168
  throw new ApiError(409, 'outcome_delivery_settled', 'outcome delivery has already started or settled');
127
169
  }
128
- disarmOutcomeDelivery(id);
170
+ if (current !== null)
171
+ withCanvasWrite((db) => {
172
+ db.prepare("DELETE FROM deliveries WHERE delivery_id = ? AND state = 'armed'").run(current.deliveryId);
173
+ });
129
174
  return { status: 204 };
130
175
  }
131
176
  export const nodeOutcomeRoutes = [
@@ -6,6 +6,7 @@ interface NodeHandlerDeps {
6
6
  export { resolveCreateKind } from '../../../core/runtime/launch-target.js';
7
7
  export declare function handleCreate(ctx: RequestContext, deps?: NodeHandlerDeps): Promise<HandlerResult>;
8
8
  export declare function handleList(ctx: RequestContext): HandlerResult;
9
+ export declare function handleFork(ctx: RequestContext): Promise<HandlerResult>;
9
10
  export declare function handlePromote(ctx: RequestContext, deps?: NodeHandlerDeps): Promise<HandlerResult>;
10
11
  export declare function handleYield(ctx: RequestContext, deps?: NodeHandlerDeps): Promise<HandlerResult>;
11
12
  export declare function handleConfig(ctx: RequestContext, deps?: NodeHandlerDeps): Promise<HandlerResult>;
@@ -13,9 +13,11 @@ import { appendSituationalContext, formatSituationalProse } from '../../../core/
13
13
  import { closeNode } from '../../../core/runtime/close.js';
14
14
  import { reviveNode } from '../../../core/runtime/revive.js';
15
15
  import { nowIso } from '../../../core/fs-utils.js';
16
- import { assertLaunchModelRegistered, forkNode, spawnChild } from '../../../core/runtime/spawn.js';
16
+ import { assertLaunchModelRegistered, forkNode, spawnChildPrepared } from '../../../core/runtime/spawn.js';
17
17
  import { childrenOf, getNode, listNodes, subscribersOf, subscriptionsOf, setMessageWait, updateNode } from '../../../core/canvas/canvas.js';
18
18
  import { subtreeIds } from '../../../core/canvas/nav-model.js';
19
+ import { withCanvasWrite } from '../../../core/canvas/db.js';
20
+ import { unbindSession } from '../../../core/canvas/sessions.js';
19
21
  import { ticketCountsForNodes } from '../../../core/canvas/attention.js';
20
22
  import { contextDir, reportsDir } from '../../../core/canvas/paths.js';
21
23
  import { nodeArtifacts } from '../../../core/canvas/history.js';
@@ -32,8 +34,6 @@ import { renameWindow } from '../../../core/runtime/placement.js';
32
34
  import { buildLaunchSpecAsync, isProviderPinnedModelToken, normalizeModel, readMergedLaunchConfigAsync } from '../../../core/runtime/launch.js';
33
35
  import { modelRequestFromConfig } from '../../../core/model-routes.js';
34
36
  import { BrokerUnreachableError } from '../../../core/runtime/broker-request.js';
35
- import { readFault } from '../../../core/runtime/fault.js';
36
- import { activeFaultForDisplay } from '../../../core/canvas/render-source.js';
37
37
  import { isBrokerLive, persistDormantModel, setModelLive } from '../../../core/runtime/model-swap.js';
38
38
  import { boundFleet } from '../../../core/runtime/fleet.js';
39
39
  import { writeOutputSchema } from '../../../core/runtime/structured-output.js';
@@ -72,16 +72,7 @@ function edgesFor(id) {
72
72
  /** Re-read a node fresh and project its detail DTO (post-action response). */
73
73
  function detailOf(id) {
74
74
  const meta = requireMeta(id);
75
- const fault = activeFaultForDisplay(meta, readFault(id));
76
- return {
77
- ...toNodeDetailDTO(meta, edgesFor(id)),
78
- fault: fault === null ? null : {
79
- link: fault.link,
80
- kind: fault.kind,
81
- retry: { disposition: fault.retry.disposition },
82
- since: fault.since,
83
- },
84
- };
75
+ return toNodeDetailDTO(meta, edgesFor(id));
85
76
  }
86
77
  /** Count context + report files under a node (the context-roster `files`). */
87
78
  function countFiles(id) {
@@ -298,32 +289,39 @@ export async function handleCreate(ctx, deps = {}) {
298
289
  const resolvedReq = { ...req, kind };
299
290
  const resolvedOutcomeDelivery = outcomeDelivery === undefined ? undefined : resolveOutcomeDelivery(outcomeDelivery, cwd);
300
291
  await assertLaunchModelRegistered(requestFromLaunch(launch), { cwd, profileId }, deps.modelRegistry);
301
- // --- Warm path: hand out an already-booted spare instead of paying a full
302
- // engine boot. A miss is not a failure — it falls through to the cold
303
- // spawn below and mints the spare that serves the NEXT create. ---
304
- const warm = warmRequestFor(resolvedReq, cwd, profileId);
305
- if (warm !== null) {
306
- const claimed = await claimWarmNode(warm, {
307
- ...(deadline === undefined ? {} : { deadlineAt: deadline }),
308
- ...(resolvedOutcomeDelivery === undefined ? {} : { outcomeDelivery: resolvedOutcomeDelivery }),
309
- });
310
- refillWarmPool(warm);
311
- if (claimed !== null) {
312
- if (schema !== null)
313
- writeOutputSchema(claimed, 'terminal', schema);
314
- return { status: 201, body: detailOf(claimed) };
292
+ let warmRefill = null;
293
+ {
294
+ // --- Warm path: hand out an already-booted spare instead of paying a full
295
+ // engine boot. A miss is not a failure — it falls through to the cold
296
+ // spawn below and mints the spare that serves the NEXT create. ---
297
+ const warm = warmRequestFor(resolvedReq, cwd, profileId);
298
+ if (warm !== null) {
299
+ const claimed = await claimWarmNode(warm, {
300
+ ...(deadline === undefined ? {} : { deadlineAt: deadline }),
301
+ ...(resolvedOutcomeDelivery === undefined ? {} : { outcomeDelivery: resolvedOutcomeDelivery }),
302
+ });
303
+ if (claimed !== null) {
304
+ refillWarmPool(warm);
305
+ if (schema !== null)
306
+ writeOutputSchema(claimed, 'terminal', schema);
307
+ return { status: 201, body: detailOf(claimed) };
308
+ }
309
+ warmRefill = warm;
315
310
  }
311
+ const recipe = buildRecipe(resolvedReq, parent, cwd);
312
+ if (deadline !== undefined)
313
+ recipe.deadlineAt = deadline;
314
+ if (resolvedOutcomeDelivery !== undefined)
315
+ recipe.outcomeDelivery = resolvedOutcomeDelivery;
316
+ recipe.readiness = req.root === true ? 'await' : 'launch';
317
+ const result = await spawnChildPrepared(recipe, undefined, () => {
318
+ if (warmRefill !== null)
319
+ refillWarmPool(warmRefill);
320
+ });
321
+ if (schema !== null)
322
+ writeOutputSchema(result.node.node_id, 'terminal', schema);
323
+ return { status: 201, body: detailOf(result.node.node_id) };
316
324
  }
317
- const recipe = buildRecipe(resolvedReq, parent, cwd);
318
- if (deadline !== undefined)
319
- recipe.deadlineAt = deadline;
320
- if (resolvedOutcomeDelivery !== undefined)
321
- recipe.outcomeDelivery = resolvedOutcomeDelivery;
322
- recipe.readiness = req.root === true ? 'await' : 'launch';
323
- const result = await spawnChild(recipe);
324
- if (schema !== null)
325
- writeOutputSchema(result.node.node_id, 'terminal', schema);
326
- return { status: 201, body: detailOf(result.node.node_id) };
327
325
  }
328
326
  // GET /v1/nodes — list (filters) ; GET /v1/nodes/{id} — detail
329
327
  function singleQueryValue(ctx, name) {
@@ -557,10 +555,8 @@ function handleArtifacts(ctx) {
557
555
  return { status: 200, body };
558
556
  }
559
557
  // Lifecycle verbs
560
- async function handleFork(ctx) {
561
- const id = ctx.params['id'];
562
- requireMeta(id);
563
- const result = await forkNode(id);
558
+ export async function handleFork(ctx) {
559
+ const result = await forkNode(ctx.params['id']);
564
560
  return { status: 201, body: detailOf(result.node.node_id) };
565
561
  }
566
562
  async function handleRevive(ctx) {
@@ -831,7 +827,10 @@ export async function handleConfig(ctx, deps = {}) {
831
827
  next: 'Create the replacement directory or send an existing absolute directory.',
832
828
  });
833
829
  }
834
- updateNode(id, { cwd: resolved, pi_session_id: null, pi_session_file: null });
830
+ withCanvasWrite(() => {
831
+ updateNode(id, { cwd: resolved });
832
+ unbindSession(id);
833
+ });
835
834
  return { status: 200, body: detailOf(id) };
836
835
  }
837
836
  let meta = requireMeta(id);
@@ -1,12 +1,9 @@
1
1
  // Canonical daemon-owned review lifecycle routes. Handlers validate the full
2
2
  // wire request, delegate lifecycle work to review services, and project only
3
3
  // committed daemon state.
4
- import { isSafeNodeId, reviewResultPath } from '../../../core/canvas/paths.js';
5
- import { responsePath } from '../../../core/human/convention.js';
6
- import { ticketDir } from '../../../core/human/root.js';
4
+ import { isSafeNodeId } from '../../../core/canvas/paths.js';
7
5
  import { createReview } from '../../../core/review/birth.js';
8
- import { readAnchorBase, getReviewByBridge, listReviews, requireVisibleReview } from '../../../core/review/store.js';
9
- import { ReviewOperationError } from '../../../core/review/types.js';
6
+ import { readAnchorBase, listReviews, requireVisibleReview } from '../../../core/review/store.js';
10
7
  import { cancelReview, submitReviewApproval } from '../../review/finish.js';
11
8
  import { toFeedbackResultDTO, toReviewDTO } from '../map.js';
12
9
  import { invalidRequest as rejectRequest, objectBody, optionalString, requireParam, requiredString, } from './validate.js';
@@ -19,20 +16,9 @@ function invalidRequest(violations) {
19
16
  function requireReviewId(ctx, violations) {
20
17
  return requireParam(ctx, 'review_id', 'must be a safe review id', isSafeNodeId, violations);
21
18
  }
22
- function requireBridgeNodeId(ctx, violations) {
23
- return requireParam(ctx, 'bridge_node_id', 'must be a safe node id', isSafeNodeId, violations);
24
- }
25
- function approvedResultPath(review) {
26
- if (review.origin_kind === 'inline')
27
- return reviewResultPath(review.review_id);
28
- if (review.bridge_node_id === null)
29
- throw new Error(`approved ticket review lacks its bridge node: ${review.review_id}`);
30
- return responsePath(ticketDir(review.bridge_node_id));
31
- }
32
19
  function toReviewResponseDTO(review) {
33
- return review.state === 'approved'
34
- ? toReviewDTO(review, { result_path: approvedResultPath(review) })
35
- : toReviewDTO(review);
20
+ const dto = toReviewDTO(review, review.state === 'approved' ? { result_path: `${review.review_dir}/result.json` } : {});
21
+ return { ...dto, review_dir: review.review_dir };
36
22
  }
37
23
  function parseCreate(ctx) {
38
24
  const violations = [];
@@ -158,16 +144,6 @@ function handleRead(ctx) {
158
144
  invalidRequest(violations);
159
145
  return { status: 200, body: toReviewResponseDTO(requireVisibleReview(reviewId)) };
160
146
  }
161
- function handleByBridge(ctx) {
162
- const violations = [];
163
- const bridgeNodeId = requireBridgeNodeId(ctx, violations);
164
- if (violations.length > 0 || bridgeNodeId === undefined)
165
- invalidRequest(violations);
166
- const review = getReviewByBridge(bridgeNodeId);
167
- if (review !== null)
168
- return { status: 200, body: toReviewResponseDTO(review) };
169
- throw new ReviewOperationError('unknown_review', 'review was not found', { received: bridgeNodeId });
170
- }
171
147
  async function handleSubmit(ctx) {
172
148
  const violations = [];
173
149
  const reviewId = requireReviewId(ctx, violations);
@@ -217,7 +193,6 @@ function handleDocument(ctx) {
217
193
  export const reviewRoutes = [
218
194
  { method: 'POST', pattern: '/v1/human/reviews', handler: handleCreate },
219
195
  { method: 'GET', pattern: '/v1/human/reviews', handler: handleList },
220
- { method: 'GET', pattern: '/v1/human/reviews/by-bridge/:bridge_node_id', handler: handleByBridge },
221
196
  { method: 'GET', pattern: '/v1/human/reviews/:review_id', handler: handleRead },
222
197
  { method: 'POST', pattern: '/v1/human/reviews/:review_id/submit', handler: handleSubmit },
223
198
  { method: 'POST', pattern: '/v1/human/reviews/:review_id/cancel', handler: handleCancel },
@@ -1,5 +1,4 @@
1
1
  import type { ManagedWorktree, NodeMeta, NodeRow, NodeRuntime, SubscriptionRef } from '../../core/canvas/types.js';
2
- import type { OutcomeDeliveryRecord } from '../../core/canvas/node-outcome-deliveries.js';
3
2
  import type { ReviewComment, ReviewCommentEvent, ReviewErrorCode, ReviewRecord, ReviewResult } from '../../core/review/types.js';
4
3
  import { type Cron, type CronRunRecord } from '../../core/canvas/crons.js';
5
4
  import type { ProfileEntry } from '../../core/profiles/manifest.js';
@@ -7,11 +6,34 @@ import type { ReviveResult } from '../../core/runtime/revive.js';
7
6
  import type { CloseNodeResult } from '../../core/runtime/close.js';
8
7
  import { type PushResult } from '../../core/feed/feed.js';
9
8
  import type { PageFeedback, PageFeedbackComment } from '../../core/human/feedback.js';
10
- import type { CloseResultDTO, CronDTO, CronRunDTO, ErrorBody, NodeEdgesDTO, NodeOutcomeDTO, NodePathsDTO, NodeStatusDTO, OutcomeDeliveryDTO, NodeSummaryDTO, NodeDetailDTO, NodeWorktreeDTO, ProfileDTO, ReviewCommentDTO, ReviewCommentEventDTO, ReviewDTO, FeedbackResultDTO, PageFeedbackCommentDTO, PageFeedbackDTO, PushReportResultDTO, ReviveResultDTO, SubscriptionDTO } from '../../api/index.js';
9
+ import type { OnDiskMigrationRunResult } from '../../migrations/runner.js';
10
+ import type { CloseResultDTO, CronDTO, CronRunDTO, ErrorBody, NodeEdgesDTO, NodeOutcomeDTO, NodePathsDTO, NodeStatusDTO, OutcomeDeliveryDTO, NodeSummaryDTO, NodeDetailDTO, NodeWorktreeDTO, ProfileDTO, ReviewCommentDTO, ReviewCommentEventDTO, ReviewDTO, FeedbackResultDTO, PageFeedbackCommentDTO, PageFeedbackDTO, PushReportResultDTO, ReviveResultDTO, MigrateStateDTO, SubscriptionDTO } from '../../api/index.js';
11
+ export declare function toMigrateStateDTO(result: OnDiskMigrationRunResult, options: {
12
+ resumed: boolean;
13
+ resumeError?: string;
14
+ }): MigrateStateDTO;
11
15
  /** Project a latched outcome, or null while this node remains unsettled. */
12
16
  export declare function toNodeOutcomeDTO(node: Pick<NodeMeta, 'node_id' | 'final_report'> & NodeRuntime): NodeOutcomeDTO | null;
17
+ /** The outcome-delivery fields exposed by the API. The delivery store supplies this projection directly. */
18
+ export interface OutcomeDeliveryDTOInput {
19
+ nodeId: string;
20
+ state: 'armed' | 'pending' | 'running' | 'accepted' | 'permanent_failed';
21
+ actionName: string;
22
+ attempt: number;
23
+ nextAttemptAt: number;
24
+ acceptedAt: number | null;
25
+ permanentFailedAt: number | null;
26
+ lastFailure: {
27
+ kind: 'exit' | 'signal' | 'timeout' | 'spawn_error';
28
+ exitCode?: number;
29
+ signal?: string;
30
+ message?: string;
31
+ } | null;
32
+ createdAt: number;
33
+ updatedAt: number;
34
+ }
13
35
  /** Project a durable delivery row without exposing its execution coordinates or stderr tail. */
14
- export declare function toOutcomeDeliveryDTO(row: OutcomeDeliveryRecord): OutcomeDeliveryDTO;
36
+ export declare function toOutcomeDeliveryDTO(row: OutcomeDeliveryDTOInput): OutcomeDeliveryDTO;
15
37
  /** Project the queryable row (what `listNodes` yields) into a summary DTO. */
16
38
  export declare function toNodeSummaryDTO(row: NodeRow): NodeSummaryDTO;
17
39
  /** The subscription/child edges a detail projection needs. The handler gathers
@@ -10,11 +10,12 @@
10
10
  // reasonable projections; this module is what makes them concrete, so any DTO
11
11
  // field that projection proves wrong is corrected here at the source.
12
12
  import { existsSync, readFileSync } from 'node:fs';
13
+ import { join } from 'node:path';
13
14
  import { outputResultPath } from '../../core/runtime/structured-output.js';
14
15
  import { ReviewOperationError as ReviewOperationErrorClass } from '../../core/review/types.js';
15
16
  import { cronSinkDisplay } from '../../core/canvas/crons.js';
16
17
  import { isProfilePaused, sanitizeProfileMetadata } from '../../core/profiles/manifest.js';
17
- import { contextDir, inboxPath, nodeDir, nodeMetaPath, reportsDir, reviewResultPath, transcriptPath, viewSocketPath, } from '../../core/canvas/paths.js';
18
+ import { contextDir, nodeDir, nodeMetaPath, reportsDir, transcriptPath, viewSocketPath, } from '../../core/canvas/paths.js';
18
19
  import { FinalizationError } from '../../core/feed/feed.js';
19
20
  import { NodeIdConflictError } from '../../core/runtime/nodes.js';
20
21
  import { readContextTokens } from '../../core/canvas/telemetry.js';
@@ -22,8 +23,82 @@ import { CrtrError } from '../../core/errors.js';
22
23
  import { WorktreeError } from '../../core/worktree.js';
23
24
  import { ApiError } from '../../api/errors.js';
24
25
  import { ExitCode } from '../../types.js';
25
- import { responsePath } from '../../core/human/convention.js';
26
- import { ticketDir } from '../../core/human/root.js';
26
+ export function toMigrateStateDTO(result, options) {
27
+ const { discovery, snapshots } = result;
28
+ return {
29
+ profiles: result.profiles,
30
+ stores: discovery.stores.length,
31
+ changed: result.changed,
32
+ applied: result.applied,
33
+ skipped: result.skipped,
34
+ blockers: result.blockers.map((blocker) => ({
35
+ kind: blocker.kind,
36
+ message: blocker.message,
37
+ ...(blocker.storeRoot === undefined ? {} : { store: blocker.storeRoot }),
38
+ ...(blocker.targetId === undefined ? {} : { target: blocker.targetId }),
39
+ ...(blocker.path === undefined ? {} : { path: blocker.path }),
40
+ ...(blocker.name === undefined ? {} : { name: blocker.name }),
41
+ ...(blocker.paths === undefined ? {} : { paths: blocker.paths }),
42
+ })),
43
+ discovery_roots: discovery.roots.map((row) => ({
44
+ reason: row.reason,
45
+ root: row.root,
46
+ boundary: row.boundary,
47
+ ...(row.detail === undefined ? {} : { detail: row.detail }),
48
+ })),
49
+ discovered_stores: discovery.stores.map((store) => ({
50
+ store: store.key,
51
+ scope: store.descriptor.scope,
52
+ namespace: store.descriptor.namespace,
53
+ mount_status: store.descriptor.mountStatus,
54
+ writable: store.writable,
55
+ reasons: [...new Set(store.discovery.map((row) => row.reason))],
56
+ targets: store.targets,
57
+ docs: snapshots.get(store.key)?.docs.length ?? 0,
58
+ })),
59
+ discovered_targets: discovery.targets.map((target) => ({
60
+ id: target.id,
61
+ kind: target.kind,
62
+ origin: target.origin,
63
+ cwd: target.target.cwd,
64
+ profile_id: target.target.profileId,
65
+ node_id: target.target.nodeId,
66
+ stores: target.storeKeys,
67
+ })),
68
+ repository_namespaces: result.repositories.map((row) => ({
69
+ repository_root: row.repositoryRoot,
70
+ repository_key: row.repositoryKey,
71
+ declaration_path: row.declarationPath,
72
+ namespace: row.namespace,
73
+ source: row.source,
74
+ action: row.action,
75
+ })),
76
+ store_prefixes: result.storePrefixes.map((row) => ({
77
+ store_root: row.storeRoot,
78
+ owner_dir: row.ownerDir,
79
+ repository_root: row.repositoryRoot,
80
+ owner_relative_path: row.ownerRelativePath,
81
+ old_prefix: row.oldPrefix,
82
+ old_rule: row.oldRule,
83
+ effective_prefix: row.effectivePrefix,
84
+ nested_declaration_action: row.nestedDeclarationAction,
85
+ front_door_action: row.frontDoorAction,
86
+ })),
87
+ renames: result.renames.map((row) => ({ store_root: row.storeRoot, path: row.path, from: row.from, to: row.to })),
88
+ references: result.references.map((row) => ({ store_root: row.storeRoot, path: row.path, kind: row.kind, from: row.from, to: row.to })),
89
+ dryRun: result.dryRun,
90
+ blocked: result.blocked,
91
+ corpus_phase: result.corpusPhase,
92
+ wrote: {
93
+ profile_manifests: result.wrote.profileManifests,
94
+ documents: result.wrote.documents,
95
+ corpus_consumers: result.wrote.corpusConsumers,
96
+ corpus_identities: result.wrote.corpusIdentities,
97
+ },
98
+ resumed: options.resumed,
99
+ ...(options.resumeError === undefined ? {} : { resume_error: options.resumeError }),
100
+ };
101
+ }
27
102
  // Node projections
28
103
  /** Read the optional machine result without letting a missing or malformed sidecar obscure the durable outcome row. */
29
104
  function readStructuredResult(nodeId) {
@@ -122,6 +197,8 @@ export function toNodeSummaryDTO(row) {
122
197
  finalized_at: row.finalized_at ?? null,
123
198
  deadline_at: row.deadline_at ?? null,
124
199
  outcome: toNodeOutcomeDTO(row),
200
+ fault: row.fault && row.fault.link !== 'broker↔crtrd' ? { ...row.fault, link: row.fault.link } : null,
201
+ streaming: row.streaming ?? false,
125
202
  };
126
203
  }
127
204
  /** Project a node's spine + subscription edges (absorbs `managers`/`paths`). */
@@ -142,7 +219,6 @@ export function toNodePathsDTO(nodeId) {
142
219
  context_dir: contextDir(nodeId),
143
220
  reports_dir: reportsDir(nodeId),
144
221
  meta_path: nodeMetaPath(nodeId),
145
- inbox_path: inboxPath(nodeId),
146
222
  transcript_path: transcriptPath(nodeId),
147
223
  view_socket: viewSocketPath(nodeId),
148
224
  };
@@ -197,6 +273,8 @@ export function toNodeDetailDTO(meta, edges) {
197
273
  finalized_at: meta.finalized_at ?? null,
198
274
  deadline_at: meta.deadline_at ?? null,
199
275
  outcome: toNodeOutcomeDTO(meta),
276
+ fault: meta.fault && meta.fault.link !== 'broker↔crtrd' ? { ...meta.fault, link: meta.fault.link } : null,
277
+ streaming: meta.streaming ?? false,
200
278
  creator: meta.creator ?? null,
201
279
  edges: toNodeEdgesDTO(meta, edges),
202
280
  paths: toNodePathsDTO(meta.node_id),
@@ -363,7 +441,7 @@ export function toReviewDTO(row, extras = {}) {
363
441
  review_id: row.review_id,
364
442
  origin_kind: row.origin_kind,
365
443
  origin_node_id: row.origin_node_id,
366
- bridge_node_id: row.bridge_node_id,
444
+ review_dir: row.review_dir,
367
445
  companion_node_id: row.companion_node_id,
368
446
  file: row.file,
369
447
  state: row.state,
@@ -601,15 +679,7 @@ function reviewErrorDetails(err) {
601
679
  function terminalConflictReviewDTO(review) {
602
680
  if (review.state !== 'approved')
603
681
  return toReviewDTO(review);
604
- const result_path = review.origin_kind === 'inline'
605
- ? reviewResultPath(review.review_id)
606
- : responsePath(ticketDir(requireBridgeNodeId(review)));
607
- return toReviewDTO(review, { result_path });
608
- }
609
- function requireBridgeNodeId(review) {
610
- if (review.bridge_node_id === null)
611
- throw new Error(`approved ticket review lacks its bridge node: ${review.review_id}`);
612
- return review.bridge_node_id;
682
+ return toReviewDTO(review, { result_path: join(review.review_dir, 'result.json') });
613
683
  }
614
684
  function statusForExitCode(exitCode) {
615
685
  switch (exitCode) {
@@ -24,21 +24,22 @@ import { emitEvent } from '../../core/events/emit.js';
24
24
  import { envCrtrdTcp, envCrtrdToken } from '../../shared/env.js';
25
25
  import { Router } from './router.js';
26
26
  import { handleAttachUpgrade } from './bridge.js';
27
+ import { boundDaemonControl } from '../control.js';
27
28
  import { attachRoutes } from './handlers/attach.js';
28
29
  import { bashJobRoutes } from './handlers/bash-jobs.js';
29
30
  import { brokerOperationRoutes } from './handlers/broker-ops.js';
31
+ import { brokerRecoveryRoutes, nodeFaultRoutes } from './handlers/broker-recovery.js';
30
32
  import { canvasRoutes } from './handlers/canvas.js';
31
33
  import { chatInventoryRoutes } from './handlers/chat-inventory.js';
32
34
  import { daemonRoutes } from './handlers/daemon.js';
33
35
  import { fileRoutes } from './handlers/files.js';
34
36
  import { focusRoutes } from './handlers/focus.js';
35
37
  import { healthRoutes } from './handlers/health.js';
36
- import { humanRoutes } from './handlers/human.js';
37
38
  import { humanRequestRoutes } from './handlers/human-requests.js';
38
39
  import { inboxRoutes } from './handlers/inbox.js';
39
40
  import { feedbackCommentRoutes } from './handlers/feedback-comments.js';
40
41
  import { memoryRoutes } from './handlers/memory.js';
41
- import { messageRoutes } from './handlers/messages.js';
42
+ import { brokerMailRoutes, messageRoutes } from './handlers/messages.js';
42
43
  import { modelAuthRoutes } from './handlers/modelauth.js';
43
44
  import { modelConfigRoutes } from './handlers/model-config.js';
44
45
  import { nodeRoutes } from './handlers/nodes.js';
@@ -62,7 +63,10 @@ function buildRouter() {
62
63
  .registerAll(nodeOutcomeRoutes)
63
64
  .registerAll(bashJobRoutes)
64
65
  .registerAll(brokerOperationRoutes)
66
+ .registerAll(brokerRecoveryRoutes)
67
+ .registerAll(nodeFaultRoutes)
65
68
  .registerAll(worktreeRoutes)
69
+ .registerAll(brokerMailRoutes)
66
70
  .registerAll(messageRoutes)
67
71
  .registerAll(reportRoutes)
68
72
  .registerAll(attachRoutes)
@@ -75,7 +79,6 @@ function buildRouter() {
75
79
  .registerAll(profileRoutes)
76
80
  .registerAll(modelAuthRoutes)
77
81
  .registerAll(modelConfigRoutes)
78
- .registerAll(humanRoutes)
79
82
  .registerAll(reviewRoutes)
80
83
  .registerAll(reviewCommentRoutes)
81
84
  .registerAll(inboxRoutes)
@@ -135,6 +138,20 @@ function writeUnauthorized(res) {
135
138
  res.writeHead(401, { 'content-type': 'application/json; charset=utf-8' });
136
139
  res.end(payload);
137
140
  }
141
+ function writeStartupBlocked(res, message) {
142
+ res.writeHead(503, { 'content-type': 'application/json; charset=utf-8' });
143
+ res.end(JSON.stringify({ error: { code: 'startup_blocked', message } }));
144
+ }
145
+ function destroyUpgradeStartupBlocked(socket, message) {
146
+ const payload = JSON.stringify({ error: { code: 'startup_blocked', message } });
147
+ const response = `HTTP/1.1 503 Service Unavailable\r\ncontent-type: application/json; charset=utf-8\r\ncontent-length: ${Buffer.byteLength(payload)}\r\nconnection: close\r\n\r\n${payload}`;
148
+ try {
149
+ socket.end(response);
150
+ }
151
+ catch {
152
+ socket.destroy();
153
+ }
154
+ }
138
155
  /** Reject an unauthorized TCP WS-upgrade attempt: this is a raw pre-upgrade
139
156
  * socket (no `ServerResponse`), so the 401 is written as a literal HTTP
140
157
  * response — mirroring `bridge.ts`'s `refuseUpgrade` (`end()`, not
@@ -180,13 +197,25 @@ function closeServer(server) {
180
197
  * servers route into identical state. */
181
198
  export function createApiServer(opts = {}) {
182
199
  const router = buildRouter();
200
+ const brokerLaunchRoutes = new Router()
201
+ .registerAll(brokerOperationRoutes)
202
+ .registerAll(brokerRecoveryRoutes)
203
+ .registerAll(brokerMailRoutes);
204
+ const startupBlockFor = (method, pathname) => {
205
+ const block = boundDaemonControl()?.startupBlock() ?? null;
206
+ if (block === null)
207
+ return null;
208
+ const admitted = (method === 'GET' && (pathname === '/healthz' || pathname === '/v1/status'))
209
+ || (method === 'POST' && (pathname === '/v1/daemon/migrate' || pathname === '/v1/daemon/admit'))
210
+ || (block.phase === 'recovering' && brokerLaunchRoutes.match(method, pathname) !== null);
211
+ return admitted ? null : block;
212
+ };
183
213
  const sockPath = apiSocketPath();
184
- // Shared request handler. The router's `handle` is total: it never throws and
185
- // turns every failure (no route, bad body, handler throw) into a JSON
186
- // ErrorBody. The `void` + `.catch` is belt-and-suspenders against an
187
- // unexpected rejection.
214
+ // Shared request handler. It owns the request boundary, including URL
215
+ // parsing before the router can take over, so malformed wire input cannot
216
+ // escape Node's request listener and terminate the daemon.
188
217
  const onRequest = (req, res) => {
189
- void router.handle(req, res).catch((err) => {
218
+ const internalFailure = (err) => {
190
219
  emitEvent({
191
220
  event: 'api.request.failed',
192
221
  level: 'error',
@@ -201,7 +230,32 @@ export function createApiServer(opts = {}) {
201
230
  catch {
202
231
  /* response already gone */
203
232
  }
204
- });
233
+ };
234
+ let url;
235
+ try {
236
+ url = new URL(req.url ?? '/', 'http://127.0.0.1');
237
+ }
238
+ catch {
239
+ try {
240
+ res.writeHead(400, { 'content-type': 'application/json; charset=utf-8' });
241
+ res.end(JSON.stringify({ error: { code: 'invalid_request', message: 'request URL is invalid' } }));
242
+ }
243
+ catch {
244
+ /* response already gone */
245
+ }
246
+ return;
247
+ }
248
+ try {
249
+ const block = startupBlockFor((req.method ?? 'GET').toUpperCase(), url.pathname);
250
+ if (block !== null) {
251
+ writeStartupBlocked(res, block.message);
252
+ return;
253
+ }
254
+ void router.handle(req, res).catch(internalFailure);
255
+ }
256
+ catch (err) {
257
+ internalFailure(err);
258
+ }
205
259
  };
206
260
  // Shared upgrade handler: only `GET /v1/nodes/:id/attach` is served; every
207
261
  // other upgrade path destroys the socket, and an unhandled error destroys the
@@ -214,6 +268,11 @@ export function createApiServer(opts = {}) {
214
268
  });
215
269
  try {
216
270
  const url = new URL(req.url ?? '/', 'http://127.0.0.1');
271
+ const block = startupBlockFor((req.method ?? 'GET').toUpperCase(), url.pathname);
272
+ if (block !== null) {
273
+ destroyUpgradeStartupBlocked(socket, block.message);
274
+ return;
275
+ }
217
276
  const nodeId = attachNodeId(req.method ?? 'GET', url.pathname);
218
277
  if (nodeId === null) {
219
278
  // Not an attach upgrade — no other upgrade path is served.