@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
@@ -24,7 +24,7 @@
24
24
  // reviveNode remains the ONLY sanctioned launcher of the node engine.
25
25
  import { existsSync, readFileSync } from 'node:fs';
26
26
  import { isAbsolute } from 'node:path';
27
- import { findNodeBySessionFile, getNode, updateNode, clearPid, recordLaunch, recordPid, setFrozen, clearFrozen, fullName, subscribersOf, } from '../canvas/index.js';
27
+ import { findNodeBySessionFile, getNode, updateNode, beginExecution, recordExecutionPid, abortExecution, endLiveExecution, unbindSession, recordLaunch, setFrozen, clearFrozen, fullName, subscribersOf, } from '../canvas/index.js';
28
28
  import { emitEvent } from '../events/emit.js';
29
29
  import { cancelCronsOnWake } from '../canvas/crons.js';
30
30
  import { isParked, transition } from './lifecycle.js';
@@ -36,8 +36,7 @@ import { readRoadmap } from './roadmap.js';
36
36
  import { encodeKickoffOrigin, KICKOFF_ORIGIN_ENV } from './stamp/protocol.js';
37
37
  import { headlessBrokerHost } from './host.js';
38
38
  import { boundFleet, brokerCapReached, brokerThresholdsForDaemon } from './fleet.js';
39
- import { clearFault, beginBootFaultAttempt } from './fault.js';
40
- import { clearTransportPoisonVerdict } from './busy.js';
39
+ import { beginBootFaultAttempt, clearFault } from '../canvas/recovery.js';
41
40
  import { clearContextExposureState, initializeForkContextExposure, } from '../substrate/injected-store.js';
42
41
  import { rootOfSpine } from './nodes.js';
43
42
  import { isReviewCompanionBound } from '../review/companion.js';
@@ -228,7 +227,7 @@ async function decideRevive(nodeId, opts, wakeParked) {
228
227
  // every status surface. Clearing it is what makes `isFrozenRow` true and
229
228
  // what tells the tick this row has never launched OR has already been
230
229
  // written off, so the freeze lane owes it a launch rather than a verdict.
231
- clearPid(nodeId);
230
+ endLiveExecution(nodeId, { reason: 'frozen' });
232
231
  emitEvent({
233
232
  level: 'info',
234
233
  event: 'broker.frozen',
@@ -278,10 +277,6 @@ async function launchRevive(nodeId, meta, opts) {
278
277
  next,
279
278
  });
280
279
  }
281
- // This is the same durable distinction used to decide between a birth prompt
282
- // and a revive kickoff below. Compute it before launch mutations so a stale
283
- // session identity cannot make an ordinary fresh revive look like a birth.
284
- const isBirth = isUnstartedBirth(nodeId);
285
280
  // Lazy host_kind coerce (§C): every launch now uses the broker host. Persist
286
281
  // that identity only after this call has passed the no-op liveness guard.
287
282
  if (meta.host_kind !== 'broker') {
@@ -293,7 +288,7 @@ async function launchRevive(nodeId, meta, opts) {
293
288
  // immutable fork instead of resuming an inherited transcript.
294
289
  const reviewCompanionUnbound = meta.review_binding != null && !isReviewCompanionBound(nodeId);
295
290
  if (reviewCompanionUnbound && (meta.pi_session_id != null || meta.pi_session_file != null)) {
296
- updateNode(nodeId, { pi_session_id: null, pi_session_file: null });
291
+ unbindSession(nodeId);
297
292
  meta.pi_session_id = null;
298
293
  meta.pi_session_file = null;
299
294
  }
@@ -316,220 +311,234 @@ async function launchRevive(nodeId, meta, opts) {
316
311
  next: `Restore ${forkSourceFile}, or update the node's fork source, then retry \`crtr node lifecycle revive ${nodeId}\`.`,
317
312
  });
318
313
  }
319
- // Every (re)launch is a new cycle — bump the counter so CRTR_CYCLES and the
320
- // footer stay in sync. Mutate the in-memory meta too so buildPiArgv below
321
- // launches with the incremented count.
322
- meta.cycles = (meta.cycles ?? 0) + 1;
323
- updateNode(nodeId, { cycles: meta.cycles });
324
- // Ordinary nodes choose resume/cycle/fresh from their recorded session.
325
- // Persisted pending-refresh state (durable across a pre-session_start
326
- // crash-retry) outranks a caller's strict-resume request. A parked node now
327
- // resumes its transcript too: its isolated parking turn never entered the
328
- // main session, so wake mail continues one intact conversation without a
329
- // fresh-cycle handoff. resumeArgs then selects only an existing absolute
330
- // session path: strict resume continues its leaf, while refresh/cycle mode
331
- // roots a new one.
332
- const refreshPending = meta.intent === 'refresh' || meta.cycle_pending === true;
333
- const effectiveResume = refreshPending ? false : opts.resume;
334
- const resume = forkBirthPending ? {} : resumableSessionArgs(nodeId, meta, effectiveResume);
335
- // hasSessionPath: there is a real `.jsonl` to hand pi via `--session`, whether
336
- // as a true resume OR a cycling refresh-yield (resumeArgs sets `newCycle` in
337
- // the latter case). cycling and resuming are mutually exclusive; `resuming`
338
- // keeps the STRICT true-resume-only meaning `ReviveResult.resumed` and the
339
- // launch-placement hint below have always carried.
340
- const hasSessionPath = resume.resumeSessionPath !== undefined;
341
- const cycling = hasSessionPath && resume.newCycle === true;
342
- const resuming = hasSessionPath && !cycling;
343
- // A cycling launch is provisional until session_start confirms it. Once
344
- // true, this marker is monotonic across failed retries; only that confirmed
345
- // boot may clear it.
346
- const cyclePending = meta.cycle_pending === true || cycling;
347
- updateNode(nodeId, { cycle_pending: cyclePending });
348
- meta.cycle_pending = cyclePending;
349
- // A truly fresh launch — no `.jsonl` to resume OR cycle from —
350
- // starts a NEW pi session, so the old session identity is dead. Clear it from meta NOW,
351
- // before launch, so the recorded state matches what we're about to do. This
352
- // is load-bearing for exit-policy classification (classifyDeadNode row 8):
353
- // if this fresh broker dies BEFORE session_start (recording no new session),
354
- // the exit-policy job reads pi_session_id=NULL → boot-failure, the correct
355
- // terminal outcome (transition('crash') + surfaceBootFailure) — a leftover
356
- // stale id would instead be misread as a resumable session and retried. A
357
- // cycling refresh keeps BOTH ids — the `.jsonl` and its recorded session id
358
- // are exactly what's being reused — so a cycling broker that dies
359
- // pre-session_start classifies as a retryable respawn, same class as a
360
- // resume revive.
361
- if (!forkBirthPending && !hasSessionPath && (meta.pi_session_id != null || meta.pi_session_file != null)) {
362
- updateNode(nodeId, { pi_session_id: null, pi_session_file: null });
363
- meta.pi_session_id = null;
364
- meta.pi_session_file = null;
365
- }
366
- // A true resume needs no kickoff — the saved conversation just continues. A
367
- // fresh revive OR a cycling refresh-yield both get one so the (re-rooted)
368
- // window re-reads its roadmap and continues: drainBearings captures the
369
- // yield note and consumes the feed/persona acknowledgements; the builder is
370
- // then pure. `resume` carries `resumeSessionPath`/`newCycle` through
371
- // unchanged (empty for true-fresh, both set for cycling) so buildPiArgv emits
372
- // the right argv either way.
373
- let inv;
374
- // The provenance of whatever kickoff this branch composes, handed to the
375
- // broker process through inv.env below. A promptless launch announces
376
- // nothing — there is no message to stamp.
377
- let kickoffOrigin = null;
378
- const birthOrigin = () => ({
379
- type: 'runtime',
380
- kind: 'kickoff',
381
- ...(meta.spawned_by != null && meta.spawned_by !== '' ? { spawnedBy: meta.spawned_by } : {}),
382
- });
383
- if (forkBirthPending) {
384
- const goal = readGoal(nodeId);
385
- inv = buildPiArgv(meta, {
386
- forkFrom: forkSourceFile,
387
- ...(goal != null && goal !== '' ? { prompt: goal } : {}),
388
- });
389
- if (goal != null && goal !== '')
390
- kickoffOrigin = birthOrigin();
391
- initializeForkContextExposure(nodeId, findNodeBySessionFile(forkSourceFile)?.node_id ?? null);
392
- }
393
- else if (resuming) {
394
- inv = buildPiArgv(meta, {
395
- ...resume,
396
- ...(opts.wakeReason === 'runtime-restart-abort' ? { prompt: RUNTIME_RESTART_CONTINUATION } : {}),
397
- });
398
- if (opts.wakeReason === 'runtime-restart-abort') {
399
- kickoffOrigin = { type: 'runtime', kind: 'restart-continuation' };
400
- }
401
- }
402
- else if (!hasSessionPath &&
403
- // A clock-driven wake must keep its <crtr-wake> kickoff — the schedule IS
404
- // the instruction to take a turn, so it never degrades to a birth replay.
405
- (opts.wakeReason === undefined || opts.wakeReason === 'runtime-restart-abort') &&
406
- isUnstartedBirth(nodeId)) {
407
- // Never conversed and nothing on disk to rebuild from — relaunch as a birth
408
- // (see isUnstartedBirth). A node with a spawn mandate replays it; a bare
409
- // interactive node boots promptless and takes no turn at all.
410
- const goal = readGoal(nodeId)?.trim();
411
- inv = buildPiArgv(meta, goal !== undefined && goal !== '' ? { prompt: goal } : {});
412
- if (goal !== undefined && goal !== '')
413
- kickoffOrigin = birthOrigin();
414
- clearContextExposureState(nodeId);
415
- }
416
- else {
417
- // Fresh or cycling: drain the one-shot bearings, then build the kickoff.
418
- // When a scheduled bare self-alarm drove this revive, opts.wakeReason
419
- // carries the wake provenance so the kickoff leads with a <crtr-wake> block
420
- // ("a timer woke you"); every other reviveNode caller passes nothing → no
421
- // block.
422
- const bearings = drainBearings(meta);
423
- inv = buildPiArgv(meta, {
424
- ...resume,
425
- prompt: buildReviveKickoff(meta, bearings, opts.wakeReason === 'runtime-restart-abort' ? undefined : opts.wakeReason),
426
- });
427
- kickoffOrigin = opts.wakeReason !== undefined && opts.wakeReason !== 'runtime-restart-abort'
428
- ? { type: 'runtime', kind: 'wake', wake: opts.wakeReason }
429
- : { type: 'runtime', kind: 'revive', cycle: meta.cycles ?? 0 };
430
- clearContextExposureState(nodeId);
431
- }
432
- // Every lifecycle launch emits the authoritative spine root rather than
433
- // inheriting whichever node happened to invoke reviveNode.
434
- inv.env = { ...inv.env, CRTR_SUBTREE: rootOfSpine(nodeId) };
435
- if (kickoffOrigin !== null)
436
- inv.env[KICKOFF_ORIGIN_ENV] = encodeKickoffOrigin(kickoffOrigin);
437
- // The broker host launches the detached engine. reviveNode keeps
438
- // transition+clearPid around it (the crash-safety ordering, unchanged). It
439
- // opens NO viewer — engine-only; existing viewers reconnect, and a viewer-less
440
- // node is brought on screen by the next `focus`.
441
- transition(nodeId, meta.intent === 'refresh' ? 'refresh-launch' : 'revive');
442
- // Clear any active fault the instant this node is (re)launched — don't wait
443
- // for the fresh agent_start. A revive (auto or on-demand) is the recovery
444
- // this marker advertised. Snapshot the boot-fault attempt in the SAME
445
- // breath (see beginBootFaultAttempt): its own verification, not this call's
446
- // unchecked result, is what scopes a later diagnostic read to this attempt.
447
- clearFault(nodeId, { preserveProviderRetryEpisode: true });
448
- const bootFault = beginBootFaultAttempt(nodeId);
449
- // A cron armed --cancel-on-wake belongs to the dormancy being left, so a WAKE
450
- // of its anchor deletes it — an inbox delivery, an attach, an explicit
451
- // revive. A recovery relaunch is not a wake: the broker instance died or was
452
- // torn down and the daemon is putting the same waiting node back on its feet,
453
- // with nothing external having arrived. The wait the deadline bounds is still
454
- // open, so the deadline survives — the same rule lifecycle.ts applies to the
455
- // `crash` transition (a deadline MUST survive instance death).
456
- if (opts.recovery !== true)
457
- cancelCronsOnWake(nodeId);
458
- ensureNodeDirs(nodeId);
459
- clearTransportPoisonVerdict(nodeId);
460
- const lifecycleHooks = discoverLifecycleHookRegistry(meta.cwd, meta.profile_id).plans.get('node:start') ?? [];
461
- await invokeLifecycleHooks(lifecycleHooks, {
462
- node: {
463
- id: nodeId,
464
- name: meta.name,
465
- kind: meta.kind,
466
- mode: meta.mode,
467
- lifecycle: meta.lifecycle,
468
- cwd: meta.cwd,
469
- nodeDir: nodeDir(nodeId),
470
- contextDir: contextDir(nodeId),
471
- jobDir: jobDir(nodeId),
472
- reportsDir: reportsDir(nodeId),
473
- },
474
- runtime: {
475
- isBirth,
476
- canvasHome: crtrHome(),
477
- profile: meta.profile_id ?? null,
478
- },
479
- });
480
- let launched;
314
+ const executionId = beginExecution(nodeId, { daemonEpoch: boundFleet().epoch() });
481
315
  try {
482
- launched = headlessBrokerHost.launch(nodeId, inv, {
483
- cwd: meta.cwd,
484
- name: fullName(meta),
485
- resuming,
316
+ // Every (re)launch is a new cycle — bump the counter so CRTR_CYCLES and the
317
+ // footer stay in sync. Mutate the in-memory meta too so buildPiArgv below
318
+ // launches with the incremented count.
319
+ meta.cycles = (meta.cycles ?? 0) + 1;
320
+ updateNode(nodeId, { cycles: meta.cycles });
321
+ // Ordinary nodes choose resume/cycle/fresh from their recorded session.
322
+ // Persisted pending-refresh state (durable across a pre-session_start
323
+ // crash-retry) outranks a caller's strict-resume request. A parked node now
324
+ // resumes its transcript too: its isolated parking turn never entered the
325
+ // main session, so wake mail continues one intact conversation without a
326
+ // fresh-cycle handoff. resumeArgs then selects only an existing absolute
327
+ // session path: strict resume continues its leaf, while refresh/cycle mode
328
+ // roots a new one.
329
+ const refreshPending = meta.intent === 'refresh' || meta.cycle_pending === true;
330
+ const effectiveResume = refreshPending ? false : opts.resume;
331
+ const resume = forkBirthPending ? {} : resumableSessionArgs(nodeId, meta, effectiveResume);
332
+ // hasSessionPath: there is a real `.jsonl` to hand pi via `--session`, whether
333
+ // as a true resume OR a cycling refresh-yield (resumeArgs sets `newCycle` in
334
+ // the latter case). cycling and resuming are mutually exclusive; `resuming`
335
+ // keeps the STRICT true-resume-only meaning `ReviveResult.resumed` and the
336
+ // launch-placement hint below have always carried.
337
+ const hasSessionPath = resume.resumeSessionPath !== undefined;
338
+ const cycling = hasSessionPath && resume.newCycle === true;
339
+ const resuming = hasSessionPath && !cycling;
340
+ // Never conversed and nothing on disk to rebuild from — relaunch as a birth
341
+ // (see isUnstartedBirth). A clock-driven wake must keep its <crtr-wake>
342
+ // kickoff — the schedule IS the instruction to take a turn, so it never
343
+ // degrades to a birth replay. This one decision selects the kickoff below
344
+ // and is what the node:start hook payload reports as `isBirth`: a hook is
345
+ // told "birth" exactly when the node boots as one.
346
+ const birthReplay = !hasSessionPath
347
+ && (opts.wakeReason === undefined || opts.wakeReason === 'runtime-restart-abort')
348
+ && isUnstartedBirth(nodeId);
349
+ const isBirth = forkBirthPending || birthReplay;
350
+ // A cycling launch is provisional until session_start confirms it. Once
351
+ // true, this marker is monotonic across failed retries; only that confirmed
352
+ // boot may clear it.
353
+ const cyclePending = meta.cycle_pending === true || cycling;
354
+ updateNode(nodeId, { cycle_pending: cyclePending });
355
+ meta.cycle_pending = cyclePending;
356
+ // A truly fresh launch — no `.jsonl` to resume OR cycle from —
357
+ // starts a NEW pi session, so the old session identity is dead. Clear it from meta NOW,
358
+ // before launch, so the recorded state matches what we're about to do. This
359
+ // is load-bearing for exit-policy classification (classifyDeadNode row 8):
360
+ // if this fresh broker dies BEFORE session_start (recording no new session),
361
+ // the exit-policy job reads pi_session_id=NULL → boot-failure, the correct
362
+ // terminal outcome (transition('crash') + surfaceBootFailure) — a leftover
363
+ // stale id would instead be misread as a resumable session and retried. A
364
+ // cycling refresh keeps BOTH ids — the `.jsonl` and its recorded session id
365
+ // are exactly what's being reused — so a cycling broker that dies
366
+ // pre-session_start classifies as a retryable respawn, same class as a
367
+ // resume revive.
368
+ if (!forkBirthPending && !hasSessionPath && (meta.pi_session_id != null || meta.pi_session_file != null)) {
369
+ unbindSession(nodeId);
370
+ meta.pi_session_id = null;
371
+ meta.pi_session_file = null;
372
+ }
373
+ // A true resume needs no kickoff — the saved conversation just continues. A
374
+ // fresh revive OR a cycling refresh-yield both get one so the (re-rooted)
375
+ // window re-reads its roadmap and continues: drainBearings captures the
376
+ // yield note and consumes the feed/persona acknowledgements; the builder is
377
+ // then pure. `resume` carries `resumeSessionPath`/`newCycle` through
378
+ // unchanged (empty for true-fresh, both set for cycling) so buildPiArgv emits
379
+ // the right argv either way.
380
+ let inv;
381
+ // The provenance of whatever kickoff this branch composes, handed to the
382
+ // broker process through inv.env below. A promptless launch announces
383
+ // nothing — there is no message to stamp.
384
+ let kickoffOrigin = null;
385
+ const birthOrigin = () => ({
386
+ type: 'runtime',
387
+ kind: 'kickoff',
388
+ ...(meta.spawned_by != null && meta.spawned_by !== '' ? { spawnedBy: meta.spawned_by } : {}),
486
389
  });
487
- if (launched.pid === null) {
488
- throw new Error(`broker host returned no pid for ${nodeId}`);
390
+ if (forkBirthPending) {
391
+ const goal = readGoal(nodeId);
392
+ inv = buildPiArgv(meta, {
393
+ forkFrom: forkSourceFile,
394
+ ...(goal != null && goal !== '' ? { prompt: goal } : {}),
395
+ });
396
+ if (goal != null && goal !== '')
397
+ kickoffOrigin = birthOrigin();
398
+ initializeForkContextExposure(nodeId, findNodeBySessionFile(forkSourceFile)?.node_id ?? null);
489
399
  }
490
- }
491
- catch (error) {
400
+ else if (resuming) {
401
+ inv = buildPiArgv(meta, {
402
+ ...resume,
403
+ ...(opts.wakeReason === 'runtime-restart-abort' ? { prompt: RUNTIME_RESTART_CONTINUATION } : {}),
404
+ });
405
+ if (opts.wakeReason === 'runtime-restart-abort') {
406
+ kickoffOrigin = { type: 'runtime', kind: 'restart-continuation' };
407
+ }
408
+ }
409
+ else if (birthReplay) {
410
+ // A node with a spawn mandate replays it; a bare interactive node boots
411
+ // promptless and takes no turn at all.
412
+ const goal = readGoal(nodeId)?.trim();
413
+ inv = buildPiArgv(meta, goal !== undefined && goal !== '' ? { prompt: goal } : {});
414
+ if (goal !== undefined && goal !== '')
415
+ kickoffOrigin = birthOrigin();
416
+ clearContextExposureState(nodeId);
417
+ }
418
+ else {
419
+ // Fresh or cycling: drain the one-shot bearings, then build the kickoff.
420
+ // When a scheduled bare self-alarm drove this revive, opts.wakeReason
421
+ // carries the wake provenance so the kickoff leads with a <crtr-wake> block
422
+ // ("a timer woke you"); every other reviveNode caller passes nothing → no
423
+ // block.
424
+ const bearings = drainBearings(meta, executionId);
425
+ inv = buildPiArgv(meta, {
426
+ ...resume,
427
+ prompt: buildReviveKickoff(meta, bearings, opts.wakeReason === 'runtime-restart-abort' ? undefined : opts.wakeReason),
428
+ });
429
+ kickoffOrigin = opts.wakeReason !== undefined && opts.wakeReason !== 'runtime-restart-abort'
430
+ ? { type: 'runtime', kind: 'wake', wake: opts.wakeReason }
431
+ : { type: 'runtime', kind: 'revive', cycle: meta.cycles ?? 0 };
432
+ clearContextExposureState(nodeId);
433
+ }
434
+ // Every lifecycle launch emits the authoritative spine root rather than
435
+ // inheriting whichever node happened to invoke reviveNode.
436
+ inv.env = { ...inv.env, CRTR_SUBTREE: rootOfSpine(nodeId) };
437
+ if (kickoffOrigin !== null)
438
+ inv.env[KICKOFF_ORIGIN_ENV] = encodeKickoffOrigin(kickoffOrigin);
439
+ // The broker host launches the detached engine. reviveNode keeps
440
+ // transition+clearPid around it (the crash-safety ordering, unchanged). It
441
+ // opens NO viewer — engine-only; existing viewers reconnect, and a viewer-less
442
+ // node is brought on screen by the next `focus`.
443
+ transition(nodeId, meta.intent === 'refresh' ? 'refresh-launch' : 'revive');
444
+ // Clear any active fault the instant this node is (re)launched — don't wait
445
+ // for the fresh agent_start. A revive (auto or on-demand) is the recovery
446
+ // this marker advertised. Snapshot the boot-fault attempt in the SAME
447
+ // breath (see beginBootFaultAttempt): its own verification, not this call's
448
+ // unchecked result, is what scopes a later diagnostic read to this attempt.
449
+ clearFault(nodeId, { preserveEpisode: true });
450
+ const bootFault = beginBootFaultAttempt(nodeId);
451
+ // A cron armed --cancel-on-wake belongs to the dormancy being left, so a WAKE
452
+ // of its anchor deletes it — an inbox delivery, an attach, an explicit
453
+ // revive. A recovery relaunch is not a wake: the broker instance died or was
454
+ // torn down and the daemon is putting the same waiting node back on its feet,
455
+ // with nothing external having arrived. The wait the deadline bounds is still
456
+ // open, so the deadline survives — the same rule lifecycle.ts applies to the
457
+ // `crash` transition (a deadline MUST survive instance death).
458
+ if (opts.recovery !== true)
459
+ cancelCronsOnWake(nodeId);
460
+ ensureNodeDirs(nodeId);
461
+ const lifecycleHooks = discoverLifecycleHookRegistry(meta.cwd, meta.profile_id).plans.get('node:start') ?? [];
462
+ await invokeLifecycleHooks(lifecycleHooks, {
463
+ node: {
464
+ id: nodeId,
465
+ name: meta.name,
466
+ kind: meta.kind,
467
+ mode: meta.mode,
468
+ lifecycle: meta.lifecycle,
469
+ cwd: meta.cwd,
470
+ nodeDir: nodeDir(nodeId),
471
+ contextDir: contextDir(nodeId),
472
+ jobDir: jobDir(nodeId),
473
+ reportsDir: reportsDir(nodeId),
474
+ },
475
+ runtime: {
476
+ isBirth,
477
+ canvasHome: crtrHome(),
478
+ profile: meta.profile_id ?? null,
479
+ },
480
+ });
481
+ inv.env = { ...inv.env, CRTR_EXECUTION_ID: executionId };
482
+ let launched;
492
483
  try {
493
- transition(nodeId, 'crash', {
494
- reason: 'launch_failed',
495
- outcome: {
496
- detail: {
497
- schema: 'crtr.node-outcome-detail/v1',
498
- message: error instanceof Error ? error.message : String(error),
499
- },
500
- },
484
+ launched = headlessBrokerHost.launch(nodeId, inv, {
485
+ cwd: meta.cwd,
486
+ name: fullName(meta),
487
+ resuming,
501
488
  });
502
- // Doctrine wake: a launch-refusal here marks the node dead with NOTHING
503
- // else fanning that out — the daemon's own auto-revive is the most common
504
- // caller (superviseTick, best-effort try/catch, stderr-only on failure), so
505
- // without this a manager waiting dormant on this child hangs forever,
506
- // never told it will not come back on its own. Mirrors closeNode's step-4
507
- // fan-out (same subscribers table, same active/passive split).
508
- fanDoctrineWake(nodeId, subscribersOf(nodeId), `Child crashed — ${fullName(meta)} (${nodeId}) failed to relaunch and is now dead: ${error.message}\n\n` +
509
- `It stays dead until you revive it — \`crtr node lifecycle revive ${nodeId}\`.`, { reason: 'child-crashed', child: nodeId });
489
+ if (launched.pid === null) {
490
+ throw new Error(`broker host returned no pid for ${nodeId}`);
491
+ }
492
+ }
493
+ catch (error) {
494
+ try {
495
+ transition(nodeId, 'crash', {
496
+ reason: 'launch_failed',
497
+ outcome: {
498
+ detail: {
499
+ schema: 'crtr.node-outcome-detail/v1',
500
+ message: error instanceof Error ? error.message : String(error),
501
+ },
502
+ },
503
+ });
504
+ // Doctrine wake: a launch-refusal here marks the node dead with NOTHING
505
+ // else fanning that out — the daemon's own auto-revive is the most common
506
+ // caller (superviseTick, best-effort try/catch, stderr-only on failure), so
507
+ // without this a manager waiting dormant on this child hangs forever,
508
+ // never told it will not come back on its own. Mirrors closeNode's step-4
509
+ // fan-out (same subscribers table, same active/passive split).
510
+ fanDoctrineWake(nodeId, subscribersOf(nodeId), `Child crashed — ${fullName(meta)} (${nodeId}) failed to relaunch and is now dead: ${error.message}\n\n` +
511
+ `It stays dead until you revive it — \`crtr node lifecycle revive ${nodeId}\`.`, { reason: 'child-crashed', child: nodeId });
512
+ }
513
+ catch {
514
+ /* best-effort cleanup */
515
+ }
516
+ abortExecution(executionId, { reason: 'launch_failed' });
517
+ throw new Error(`[broker] refusing to revive ${nodeId}: ${error.message}`);
510
518
  }
511
- catch {
512
- /* best-effort cleanup */
519
+ recordLaunch(nodeId, new Date().toISOString());
520
+ // The execution row owns the broker pid. A stale broker that reaches the
521
+ // daemon after its execution was superseded is stopped rather than claiming
522
+ // the current node process slot.
523
+ if (recordExecutionPid(executionId, nodeId, launched.pid) === 'shutdown') {
524
+ abortExecution(executionId, { reason: 'superseded' });
525
+ headlessBrokerHost.teardown(nodeId);
526
+ throw new Error(`[broker] refusing to revive ${nodeId}: execution was superseded during launch`);
513
527
  }
514
- clearPid(nodeId);
515
- throw new Error(`[broker] refusing to revive ${nodeId}: ${error.message}`);
528
+ // A launch is the definition of not-frozen, and every launcher passes
529
+ // through here — so this is the one place the wait mark is retired.
530
+ clearFrozen(nodeId);
531
+ return {
532
+ window: null,
533
+ session: meta.tmux_session ?? null,
534
+ resumed: resuming,
535
+ launch: launched,
536
+ faultExplanation: bootFault.explanation,
537
+ outcome: 'launched',
538
+ };
539
+ }
540
+ catch (error) {
541
+ abortExecution(executionId, { reason: 'launch_failed' });
542
+ throw error;
516
543
  }
517
- recordLaunch(nodeId, new Date().toISOString());
518
- // Record the real broker pid. The pid is METADATA now (design D-13) — the
519
- // liveness authority is the fleet entry registered synchronously inside
520
- // `launch` above — but session_start re-records it after extension bind,
521
- // and status surfaces (canvas views, the daemon's identity rechecks at
522
- // signal boundaries) still read it.
523
- recordPid(nodeId, launched.pid);
524
- // A launch is the definition of not-frozen, and every launcher passes
525
- // through here — so this is the one place the wait mark is retired.
526
- clearFrozen(nodeId);
527
- return {
528
- window: null,
529
- session: meta.tmux_session ?? null,
530
- resumed: resuming,
531
- launch: launched,
532
- faultExplanation: bootFault.explanation,
533
- outcome: 'launched',
534
- };
535
544
  }
@@ -127,8 +127,11 @@ export declare function resolveSpawner(parent: string | undefined, ctxNodeId: st
127
127
  * to the spawner (it carries spawned_by=spawner for provenance only), brought
128
128
  * forefront so a human can pick up the conversation directly. */
129
129
  type BeforeBrokerLaunch = (nodeId: string) => void;
130
- export declare function spawnChildPrepared(opts: SpawnChildOpts, beforeBrokerLaunch?: BeforeBrokerLaunch): Promise<SpawnChildResult>;
130
+ type AfterNodeCreated = (nodeId: string) => void;
131
+ export declare function spawnChildPrepared(opts: SpawnChildOpts, beforeBrokerLaunch?: BeforeBrokerLaunch, afterNodeCreated?: AfterNodeCreated): Promise<SpawnChildResult>;
131
132
  export declare function spawnChild(opts: SpawnChildOpts): Promise<SpawnChildResult>;
133
+ /** Invoke the best-effort node-start plan before any cold broker launch. */
134
+ export declare function invokeBirthLifecycleHooks(meta: NodeMeta): Promise<void>;
132
135
  /** Create an independent, idle root from a node's conversation and durable
133
136
  * context. Runtime/graph state is intentionally not inherited. */
134
137
  export declare function forkNode(sourceId: string): Promise<SpawnChildResult>;
@@ -22,13 +22,13 @@ import { appendSituationalContext, formatSituationalProse } from './situational-
22
22
  import { hasRoadmap, seedRoadmap } from './roadmap.js';
23
23
  import { buildWakeBearings } from './bearings.js';
24
24
  import { encodeKickoffOrigin, KICKOFF_ORIGIN_ENV } from './stamp/protocol.js';
25
- import { canonicalSessionFile, contextDir, findNodeBySessionFile, getNode, fullName, recordPid, setFrozen } from '../canvas/index.js';
25
+ import { beginExecution, canonicalSessionFile, contextDir, crtrHome, findNodeBySessionFile, getNode, fullName, jobDir, nodeDir, recordExecutionPid, abortExecution, reportsDir, setFrozen } from '../canvas/index.js';
26
26
  import { boundFleet, brokerThresholdsForDaemon } from './fleet.js';
27
27
  import { emitEvent } from '../events/emit.js';
28
28
  import { openViewerWindow, focusOf, windowOfPane, } from './placement.js';
29
29
  import { waitForBrokerViewSocket } from './placement-tmux.js';
30
30
  import { transition } from './lifecycle.js';
31
- import { beginBootFaultAttempt } from './fault.js';
31
+ import { beginBootFaultAttempt } from '../canvas/recovery.js';
32
32
  import { piSessionsRoot } from './pi-vendored.js';
33
33
  import { headlessBrokerHost } from './host.js';
34
34
  import { ensureDaemon } from '../../daemon/manage.js';
@@ -37,6 +37,8 @@ import { modelRequestFromConfig, unregisteredConcreteModel } from '../model-rout
37
37
  import { openModelRegistry } from './model-registry.js';
38
38
  import { MODEL_SPEC_NEXT } from './model-selection.js';
39
39
  import { initializeForkContextExposure } from '../substrate/injected-store.js';
40
+ import { discoverLifecycleHookRegistry } from '../command-hooks/discovery.js';
41
+ import { invokeLifecycleHooks } from '../command-hooks/transport/exec-lifecycle.js';
40
42
  // Shared broker-launch wrapper — catch launch/preflight failures at the call
41
43
  // site, mark the node crashed, then rethrow a clear error.
42
44
  function launchFailureOutcome(error) {
@@ -210,7 +212,7 @@ export function resolveSpawner(parent, ctxNodeId, root) {
210
212
  }
211
213
  return spawner;
212
214
  }
213
- export async function spawnChildPrepared(opts, beforeBrokerLaunch) {
215
+ export async function spawnChildPrepared(opts, beforeBrokerLaunch, afterNodeCreated) {
214
216
  try {
215
217
  ensureDaemon();
216
218
  }
@@ -276,7 +278,7 @@ export async function spawnChildPrepared(opts, beforeBrokerLaunch) {
276
278
  if (wantsWorktree) {
277
279
  await assertLaunchModelRegistered(requestFromLaunch(launch), { cwd: spawnCwd, profileId });
278
280
  }
279
- const meta = spawnNode({
281
+ const meta = await spawnNode({
280
282
  kind: opts.kind,
281
283
  mode,
282
284
  lifecycle,
@@ -299,6 +301,7 @@ export async function spawnChildPrepared(opts, beforeBrokerLaunch) {
299
301
  ...(opts.outcomeDelivery === undefined ? {} : { outcomeDelivery: opts.outcomeDelivery }),
300
302
  launch,
301
303
  });
304
+ afterNodeCreated?.(meta.node_id);
302
305
  if (opts.forkFrom !== undefined) {
303
306
  try {
304
307
  initializeForkContextExposure(meta.node_id, forkSourceNodeId);
@@ -345,6 +348,7 @@ export async function spawnChildPrepared(opts, beforeBrokerLaunch) {
345
348
  }
346
349
  // (The three scoped long-term memory stores are seeded for EVERY node at birth
347
350
  // in spawnNode — no orchestrator-gated seeding needed here.)
351
+ await invokeBirthLifecycleHooks(meta);
348
352
  const inv = buildPiArgv(meta, { prompt: kickoff, forkFrom });
349
353
  // CRTR_SUBTREE (the spine root) rides the detached broker's env so it can group
350
354
  // the subtree; the host sets CRTR_FRONT_DOOR itself.
@@ -386,6 +390,8 @@ export async function spawnChildPrepared(opts, beforeBrokerLaunch) {
386
390
  return { node: getNode(meta.node_id), frozen: true, window: null, session: null };
387
391
  }
388
392
  const bootFault = beginBootFaultAttempt(meta.node_id);
393
+ const executionId = beginExecution(meta.node_id, { daemonEpoch: fleet.epoch() });
394
+ inv.env = { ...inv.env, CRTR_EXECUTION_ID: executionId };
389
395
  // Broker is the only host: launch the detached broker ENGINE. A managed child
390
396
  // gets NO viewer — spawning it returns once the engine is launched and a human
391
397
  // opens a viewer on demand via `crtr surface node focus` / surface attach. A --root gets
@@ -398,6 +404,10 @@ export async function spawnChildPrepared(opts, beforeBrokerLaunch) {
398
404
  resuming: false,
399
405
  });
400
406
  }
407
+ catch (error) {
408
+ abortExecution(executionId, { reason: 'launch_failed' });
409
+ throw error;
410
+ }
401
411
  finally {
402
412
  fleet.releaseReservation(meta.node_id);
403
413
  }
@@ -407,10 +417,15 @@ export async function spawnChildPrepared(opts, beforeBrokerLaunch) {
407
417
  // spawnNode). Mirrors the old window===null crash.
408
418
  if (placed.pid === null) {
409
419
  const error = new Error(`broker host returned no pid for ${meta.node_id}`);
420
+ abortExecution(executionId, { reason: 'launch_failed' });
410
421
  transition(meta.node_id, 'crash', { reason: 'launch_failed', outcome: launchFailureOutcome(error) });
411
422
  throw brokerLaunchFailed(`failed to launch the broker engine for ${meta.node_id} (${meta.name}) — the node was not started.`, `Inspect canonical event diagnostics with \`crtr sys logs --node ${meta.node_id}\`, then verify the pi engine can start in this environment.`);
412
423
  }
413
- recordPid(meta.node_id, placed.pid);
424
+ if (recordExecutionPid(executionId, meta.node_id, placed.pid) === 'shutdown') {
425
+ abortExecution(executionId, { reason: 'superseded' });
426
+ headlessBrokerHost.teardown(meta.node_id);
427
+ throw brokerLaunchFailed(`broker execution was superseded for ${meta.node_id}`, 'Retry spawning the node.');
428
+ }
414
429
  // A root created through `node new --root` must accept viewers before the
415
430
  // command returns because its caller may attach immediately. Only that root
416
431
  // waits — a managed child (and every cron-launched
@@ -491,6 +506,29 @@ export async function spawnChildPrepared(opts, beforeBrokerLaunch) {
491
506
  export async function spawnChild(opts) {
492
507
  return spawnChildPrepared(opts);
493
508
  }
509
+ /** Invoke the best-effort node-start plan before any cold broker launch. */
510
+ export async function invokeBirthLifecycleHooks(meta) {
511
+ const lifecycleHooks = discoverLifecycleHookRegistry(meta.cwd, meta.profile_id).plans.get('node:start') ?? [];
512
+ await invokeLifecycleHooks(lifecycleHooks, {
513
+ node: {
514
+ id: meta.node_id,
515
+ name: meta.name,
516
+ kind: meta.kind,
517
+ mode: meta.mode,
518
+ lifecycle: meta.lifecycle,
519
+ cwd: meta.cwd,
520
+ nodeDir: nodeDir(meta.node_id),
521
+ contextDir: contextDir(meta.node_id),
522
+ jobDir: jobDir(meta.node_id),
523
+ reportsDir: reportsDir(meta.node_id),
524
+ },
525
+ runtime: {
526
+ isBirth: true,
527
+ canvasHome: crtrHome(),
528
+ profile: meta.profile_id ?? null,
529
+ },
530
+ });
531
+ }
494
532
  const FORK_CONTEXT_EXCLUDES = new Set(['initial-prompt.md', 'yield-message.md', 'result.json']);
495
533
  /** Create an independent, idle root from a node's conversation and durable
496
534
  * context. Runtime/graph state is intentionally not inherited. */
@@ -30,8 +30,8 @@ export interface WarmRequest {
30
30
  creator: string | null;
31
31
  }
32
32
  /** Claim a spare for `request`, reshaped onto its persona — or null when the
33
- * pool cannot serve it. The caller falls back to a cold spawn; a claim never
34
- * fails the create. */
33
+ * pool cannot serve it. The caller falls back to a cold spawn; a refused
34
+ * create hook leaves the spare untouched and rejects the create. */
35
35
  export declare function claimWarmNode(request: WarmRequest, birth?: {
36
36
  deadlineAt?: string;
37
37
  outcomeDelivery?: OutcomeDeliveryBinding;