@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
@@ -0,0 +1,93 @@
1
+ import { openDb, withCanvasWrite } from './db.js';
2
+ function json(value) {
3
+ const encoded = JSON.stringify(value);
4
+ if (encoded === undefined)
5
+ throw new TypeError('worktree value must be JSON-serializable');
6
+ return encoded;
7
+ }
8
+ function parse(value) {
9
+ if (typeof value !== 'string')
10
+ return undefined;
11
+ return JSON.parse(value);
12
+ }
13
+ export function readWorktree(nodeId) {
14
+ const row = openDb().prepare('SELECT * FROM worktrees WHERE node_id = ?').get(nodeId);
15
+ if (row === undefined)
16
+ return null;
17
+ const sweepObserved = parse(row['sweep_observed_json']);
18
+ const attempts = row['attempts'];
19
+ const sweep = sweepObserved === undefined ? undefined : {
20
+ reason: row['sweep_reason'],
21
+ ...(row['sweep_detail'] === null ? {} : { detail: row['sweep_detail'] }),
22
+ observed: sweepObserved,
23
+ attempts,
24
+ last_attempt: row['sweep_last_at'],
25
+ next_attempt: row['next_attempt_at'],
26
+ ...(row['quarantined'] === 1 ? { quarantined: true } : {}),
27
+ };
28
+ return {
29
+ state: row['state'],
30
+ ...(row['cleanup_state'] === null ? {} : { cleanup: row['cleanup_state'] }),
31
+ path: row['path'],
32
+ branch: row['branch'],
33
+ repo_root: row['repo_root'],
34
+ base_ref: row['base_ref'],
35
+ base_sha: row['base_sha'],
36
+ created: row['created_at'],
37
+ ...(row['closed_at'] === null ? {} : { closed: row['closed_at'] }),
38
+ ...(parse(row['abandoned_json']) === undefined ? {} : { abandoned: parse(row['abandoned_json']) }),
39
+ ...(parse(row['land_intent_json']) === undefined ? {} : { land_intent: parse(row['land_intent_json']) }),
40
+ ...(sweep === undefined ? {} : { sweep }),
41
+ ...(parse(row['disposal_json']) === undefined ? {} : { disposal: parse(row['disposal_json']) }),
42
+ };
43
+ }
44
+ export function openWorktree(nodeId, worktree, db = openDb()) {
45
+ db.prepare(`INSERT INTO worktrees (node_id, state, cleanup_state, path, branch, repo_root, base_ref, base_sha, created_at, closed_at, next_attempt_at, attempts, quarantined, sweep_reason, sweep_detail, sweep_last_at, sweep_observed_json, abandoned_json, land_intent_json, disposal_json) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, 0, 0, NULL, NULL, NULL, NULL, NULL, NULL, NULL)`).run(nodeId, worktree.state, worktree.cleanup ?? null, worktree.path, worktree.branch, worktree.repo_root, worktree.base_ref, worktree.base_sha, worktree.created, worktree.closed ?? null);
46
+ }
47
+ export function closeWorktree(nodeId, input) {
48
+ openDb().prepare("UPDATE worktrees SET state = 'closed', cleanup_state = ?, closed_at = ?, land_intent_json = NULL, sweep_reason = NULL, sweep_detail = NULL, sweep_last_at = NULL, sweep_observed_json = NULL, next_attempt_at = NULL, attempts = 0, quarantined = 0 WHERE node_id = ?").run(input.cleanup, input.closedAt, nodeId);
49
+ }
50
+ /** Complete a worktree close and repoint the node in the same SQL transaction.
51
+ * A close invalidates the active transcript because Pi remains in the old
52
+ * checkout; the next launch starts from the durable base checkout. */
53
+ export function closeWorktreeAndRepoint(nodeId, input) {
54
+ withCanvasWrite((db) => {
55
+ db.prepare('UPDATE nodes SET cwd = ? WHERE node_id = ?').run(input.cwd, nodeId);
56
+ if (input.unbindSession)
57
+ db.prepare('UPDATE sessions SET unbound_at = ? WHERE node_id = ? AND unbound_at IS NULL').run(input.closedAt, nodeId);
58
+ db.prepare("UPDATE worktrees SET state = 'closed', cleanup_state = ?, closed_at = ?, land_intent_json = NULL, sweep_reason = NULL, sweep_detail = NULL, sweep_last_at = NULL, sweep_observed_json = NULL, next_attempt_at = NULL, attempts = 0, quarantined = 0 WHERE node_id = ?").run(input.cleanup, input.closedAt, nodeId);
59
+ });
60
+ }
61
+ export function abandonWorktree(nodeId, abandoned) {
62
+ openDb().prepare("UPDATE worktrees SET state = 'abandoned', abandoned_json = ? WHERE node_id = ? AND abandoned_json IS NULL").run(json(abandoned), nodeId);
63
+ }
64
+ export function abandonWorktreeAndRepoint(nodeId, input) {
65
+ withCanvasWrite((db) => {
66
+ db.prepare('UPDATE nodes SET cwd = ? WHERE node_id = ?').run(input.cwd, nodeId);
67
+ db.prepare("UPDATE worktrees SET state = 'abandoned', abandoned_json = ? WHERE node_id = ? AND abandoned_json IS NULL").run(json(input.abandoned), nodeId);
68
+ });
69
+ }
70
+ export function recordLandIntent(nodeId, intent) {
71
+ openDb().prepare('UPDATE worktrees SET land_intent_json = ? WHERE node_id = ? AND land_intent_json IS NULL').run(json(intent), nodeId);
72
+ }
73
+ export function recordDisposalGrant(nodeId, disposal) {
74
+ openDb().prepare('UPDATE worktrees SET disposal_json = ? WHERE node_id = ? AND disposal_json IS NULL').run(json(disposal), nodeId);
75
+ }
76
+ export function recordSweepAttempt(nodeId, input) {
77
+ openDb().prepare('UPDATE worktrees SET sweep_reason = ?, sweep_detail = ?, sweep_observed_json = ?, attempts = ?, sweep_last_at = ?, next_attempt_at = ?, quarantined = ? WHERE node_id = ?').run(input.reason, input.detail ?? null, json(input.observed), input.attempts, input.lastAttemptAt, input.nextAttemptAt, input.quarantined ? 1 : 0, nodeId);
78
+ }
79
+ export function completeCleanup(nodeId) {
80
+ openDb().prepare("UPDATE worktrees SET state = 'closed', cleanup_state = 'complete', sweep_reason = NULL, sweep_detail = NULL, sweep_last_at = NULL, sweep_observed_json = NULL, next_attempt_at = NULL, attempts = 0, quarantined = 0 WHERE node_id = ?").run(nodeId);
81
+ }
82
+ export function hasUnreconciledWorktree(nodeId) {
83
+ return openDb().prepare("SELECT 1 FROM worktrees WHERE node_id = ? AND state != 'abandoned' AND (state = 'open' OR cleanup_state IS NULL OR cleanup_state != 'complete')").get(nodeId) !== undefined;
84
+ }
85
+ export function sweepCandidates(now) {
86
+ return openDb().prepare(`SELECT w.node_id FROM worktrees w JOIN nodes n ON n.node_id = w.node_id WHERE n.status IN ('done', 'dead', 'canceled') AND n.frozen_at IS NULL AND w.state IN ('open', 'closed') AND (w.state = 'open' OR w.cleanup_state IS NULL OR w.cleanup_state != 'complete') AND (w.state = 'closed' OR (n.status = 'done' AND n.final_report IS NOT NULL)) AND (w.next_attempt_at IS NULL OR w.next_attempt_at <= ?) ORDER BY w.next_attempt_at, w.node_id`).all(now).map(({ node_id }) => node_id);
87
+ }
88
+ export function quarantinedWorktrees() {
89
+ return openDb().prepare('SELECT node_id FROM worktrees WHERE quarantined = 1 ORDER BY sweep_last_at').all().flatMap(({ node_id }) => {
90
+ const worktree = readWorktree(node_id);
91
+ return worktree === null ? [] : [{ nodeId: node_id, worktree }];
92
+ });
93
+ }
@@ -1,9 +1,15 @@
1
1
  import type { PluginManifest } from '../../types.js';
2
2
  import { type HookManifestIssue, type ValidatedHookManifest } from './schema.js';
3
+ export interface LifecycleDeclarationCandidate {
4
+ event: string;
5
+ op: string;
6
+ index: number;
7
+ }
3
8
  export interface HookArtifactValidation {
4
9
  manifestPath?: string;
5
10
  executablePath?: string;
6
11
  manifest?: ValidatedHookManifest;
12
+ lifecycleCandidates: readonly LifecycleDeclarationCandidate[];
7
13
  issues: HookManifestIssue[];
8
14
  }
9
15
  /** Statically validates the local hook artifact a source plugin ships. */
@@ -1,6 +1,21 @@
1
1
  import { readFileSync, realpathSync, statSync } from 'node:fs';
2
2
  import { isAbsolute, resolve, sep } from 'node:path';
3
3
  import { validateHookManifest } from './schema.js';
4
+ function lifecycleCandidates(raw) {
5
+ if (typeof raw !== 'object' || raw === null || Array.isArray(raw))
6
+ return [];
7
+ const lifecycle = raw['lifecycle'];
8
+ if (!Array.isArray(lifecycle))
9
+ return [];
10
+ return lifecycle.flatMap((value, index) => {
11
+ if (typeof value !== 'object' || value === null || Array.isArray(value))
12
+ return [];
13
+ const declaration = value;
14
+ return typeof declaration['event'] === 'string'
15
+ ? [{ event: declaration['event'], op: typeof declaration['op'] === 'string' ? declaration['op'] : '<invalid>', index }]
16
+ : [];
17
+ });
18
+ }
4
19
  function safeFilePath(root, pointer) {
5
20
  if (isAbsolute(pointer))
6
21
  return null;
@@ -29,10 +44,10 @@ export function validatePluginHookArtifact(root, manifest) {
29
44
  expected: 'both hooks and hookExecutable',
30
45
  next: hasHooks ? 'Add plugin.json.hookExecutable or remove hooks.' : 'Add plugin.json.hooks or remove hookExecutable.',
31
46
  });
32
- return { issues };
47
+ return { lifecycleCandidates: [], issues };
33
48
  }
34
49
  if (!hasHooks)
35
- return { issues };
50
+ return { lifecycleCandidates: [], issues };
36
51
  if (typeof hooks !== 'string' || hooks.length === 0) {
37
52
  issues.push({
38
53
  code: 'hook_manifest_invalid',
@@ -54,7 +69,7 @@ export function validatePluginHookArtifact(root, manifest) {
54
69
  });
55
70
  }
56
71
  if (issues.length > 0)
57
- return { issues };
72
+ return { lifecycleCandidates: [], issues };
58
73
  const hooksPointer = hooks;
59
74
  const executablePointer = executable;
60
75
  const manifestPath = safeFilePath(root, hooksPointer);
@@ -90,7 +105,7 @@ export function validatePluginHookArtifact(root, manifest) {
90
105
  });
91
106
  }
92
107
  if (manifestPath === null)
93
- return { ...(executablePath === null ? {} : { executablePath }), issues };
108
+ return { lifecycleCandidates: [], ...(executablePath === null ? {} : { executablePath }), issues };
94
109
  let text;
95
110
  try {
96
111
  text = readFileSync(manifestPath, 'utf8');
@@ -103,7 +118,7 @@ export function validatePluginHookArtifact(root, manifest) {
103
118
  expected: 'a readable JSON file',
104
119
  next: 'Reinstall or update the plugin.',
105
120
  });
106
- return { manifestPath, ...(executablePath === null ? {} : { executablePath }), issues };
121
+ return { manifestPath, lifecycleCandidates: [], ...(executablePath === null ? {} : { executablePath }), issues };
107
122
  }
108
123
  let raw;
109
124
  try {
@@ -117,11 +132,12 @@ export function validatePluginHookArtifact(root, manifest) {
117
132
  expected: 'a JSON object',
118
133
  next: 'Regenerate hooks.json.',
119
134
  });
120
- return { manifestPath, ...(executablePath === null ? {} : { executablePath }), issues };
135
+ return { manifestPath, lifecycleCandidates: [], ...(executablePath === null ? {} : { executablePath }), issues };
121
136
  }
122
137
  const validation = validateHookManifest(raw);
123
138
  return {
124
139
  manifestPath,
140
+ lifecycleCandidates: lifecycleCandidates(raw),
125
141
  ...(executablePath === null ? {} : { executablePath }),
126
142
  ...(validation.manifest === undefined ? {} : { manifest: validation.manifest }),
127
143
  issues: [...issues, ...validation.issues],
@@ -1,4 +1,5 @@
1
1
  import type { InstalledPlugin } from '../../types.js';
2
+ import { type LifecycleDeclarationCandidate } from './artifact.js';
2
3
  import type { CoreHookCatalog } from './catalog.js';
3
4
  import type { HookManifestIssue, HookPhase, LifecycleHookPhase, ValidatedHookManifest } from './schema.js';
4
5
  import type { LifecycleEvent } from './lifecycle-catalog.js';
@@ -65,6 +66,7 @@ export interface HookPluginValidation {
65
66
  manifestPath?: string;
66
67
  executablePath?: string;
67
68
  manifest?: ValidatedHookManifest;
69
+ lifecycleCandidates: readonly LifecycleDeclarationCandidate[];
68
70
  hooks: readonly EffectiveHook[];
69
71
  issues: readonly HookDiscoveryIssue[];
70
72
  }
@@ -109,6 +109,7 @@ export function compileHookRegistry(catalog, plugins) {
109
109
  ...(artifact.manifestPath === undefined ? {} : { manifestPath: artifact.manifestPath }),
110
110
  ...(artifact.executablePath === undefined ? {} : { executablePath: artifact.executablePath }),
111
111
  ...(artifact.manifest === undefined ? {} : { manifest: artifact.manifest }),
112
+ lifecycleCandidates: Object.freeze([...artifact.lifecycleCandidates]),
112
113
  hooks: Object.freeze(compiled),
113
114
  issues: Object.freeze(pluginIssues),
114
115
  }));
@@ -183,6 +184,7 @@ export function compileLifecycleHookRegistry(plugins) {
183
184
  ...(artifact.manifestPath === undefined ? {} : { manifestPath: artifact.manifestPath }),
184
185
  ...(artifact.executablePath === undefined ? {} : { executablePath: artifact.executablePath }),
185
186
  ...(artifact.manifest === undefined ? {} : { manifest: artifact.manifest }),
187
+ lifecycleCandidates: Object.freeze([...artifact.lifecycleCandidates]),
186
188
  hooks: Object.freeze([]),
187
189
  issues: Object.freeze(pluginIssues),
188
190
  }));
@@ -1,3 +1,21 @@
1
- export declare const LIFECYCLE_EVENTS: readonly ["node:start", "node:close"];
2
- export type LifecycleEvent = typeof LIFECYCLE_EVENTS[number];
1
+ export declare const LIFECYCLE_CATALOG: {
2
+ readonly 'node:create': {
3
+ readonly phase: "before";
4
+ readonly hookTimeoutMs: 5000;
5
+ readonly eventBudgetMs: 10000;
6
+ };
7
+ readonly 'node:start': {
8
+ readonly phase: "on";
9
+ readonly hookTimeoutMs: 5000;
10
+ readonly eventBudgetMs: 10000;
11
+ };
12
+ readonly 'node:close': {
13
+ readonly phase: "on";
14
+ readonly hookTimeoutMs: 120000;
15
+ readonly eventBudgetMs: 180000;
16
+ };
17
+ };
18
+ export type LifecycleEvent = keyof typeof LIFECYCLE_CATALOG;
19
+ export type LifecycleHookPhase = (typeof LIFECYCLE_CATALOG)[LifecycleEvent]['phase'];
20
+ export declare const LIFECYCLE_EVENTS: readonly ("node:create" | "node:start" | "node:close")[];
3
21
  export declare function isLifecycleEvent(value: string): value is LifecycleEvent;
@@ -1,4 +1,9 @@
1
- export const LIFECYCLE_EVENTS = ['node:start', 'node:close'];
1
+ export const LIFECYCLE_CATALOG = {
2
+ 'node:create': { phase: 'before', hookTimeoutMs: 5_000, eventBudgetMs: 10_000 },
3
+ 'node:start': { phase: 'on', hookTimeoutMs: 5_000, eventBudgetMs: 10_000 },
4
+ 'node:close': { phase: 'on', hookTimeoutMs: 120_000, eventBudgetMs: 180_000 },
5
+ };
6
+ export const LIFECYCLE_EVENTS = Object.freeze(Object.keys(LIFECYCLE_CATALOG));
2
7
  export function isLifecycleEvent(value) {
3
- return LIFECYCLE_EVENTS.includes(value);
8
+ return Object.hasOwn(LIFECYCLE_CATALOG, value);
4
9
  }
@@ -0,0 +1,4 @@
1
+ import type { NodeCreateHookRequest } from '../../hook-authoring.js';
2
+ export type NodeCreateRequest = NodeCreateHookRequest['create'];
3
+ /** Admit one node creation and return the release that must run after its row exists. */
4
+ export declare function admitNodeCreate(create: NodeCreateRequest): Promise<() => void>;
@@ -0,0 +1,51 @@
1
+ import { crtrHome } from '../canvas/paths.js';
2
+ import { nodeCreateRefused } from '../errors.js';
3
+ import { discoverLifecycleHookRegistry } from './discovery.js';
4
+ import { invokeBlockingLifecycleHooks } from './transport/exec-lifecycle.js';
5
+ let nodeCreateGate = Promise.resolve();
6
+ async function enterNodeCreateGate() {
7
+ let resolve;
8
+ const previous = nodeCreateGate;
9
+ nodeCreateGate = new Promise((release) => { resolve = release; });
10
+ await previous;
11
+ let released = false;
12
+ return () => {
13
+ if (released)
14
+ return;
15
+ released = true;
16
+ resolve();
17
+ };
18
+ }
19
+ function refusedCreateHookDiscovery(cwd, profileId) {
20
+ const registry = discoverLifecycleHookRegistry(cwd, profileId);
21
+ const plan = registry.plans.get('node:create') ?? [];
22
+ const failed = registry.validations.flatMap((validation) => validation.lifecycleCandidates
23
+ .filter((candidate) => candidate.event === 'node:create' && !plan.some((hook) => hook.plugin.name === validation.plugin.name && hook.declarationIndex === candidate.index))
24
+ .map((candidate) => ({
25
+ validation,
26
+ candidate,
27
+ issue: validation.issues.find((issue) => issue.path === `lifecycle[${candidate.index}]` || issue.path?.startsWith(`lifecycle[${candidate.index}].`)) ?? validation.issues[0],
28
+ })))[0];
29
+ if (failed === undefined)
30
+ return registry;
31
+ const { validation, candidate, issue } = failed;
32
+ throw nodeCreateRefused(`plugin "${validation.plugin.name}" lifecycle hook "${candidate.op}" for node:create could not be discovered: ${issue.message}`, `Refused by plugin ${validation.plugin.name}, op ${candidate.op}.`);
33
+ }
34
+ /** Admit one node creation and return the release that must run after its row exists. */
35
+ export async function admitNodeCreate(create) {
36
+ const release = await enterNodeCreateGate();
37
+ try {
38
+ const registry = refusedCreateHookDiscovery(create.cwd, create.profile);
39
+ const refusal = await invokeBlockingLifecycleHooks(registry.plans.get('node:create') ?? [], {
40
+ create,
41
+ runtime: { canvasHome: crtrHome(), profile: create.profile },
42
+ });
43
+ if (refusal !== null)
44
+ throw nodeCreateRefused(refusal.message, refusal.next);
45
+ return release;
46
+ }
47
+ catch (error) {
48
+ release();
49
+ throw error;
50
+ }
51
+ }
@@ -1,6 +1,6 @@
1
- import { type LifecycleEvent } from './lifecycle-catalog.js';
1
+ import { type LifecycleEvent, type LifecycleHookPhase } from './lifecycle-catalog.js';
2
+ export type { LifecycleHookPhase } from './lifecycle-catalog.js';
2
3
  export type HookPhase = 'before' | 'after' | 'replace';
3
- export type LifecycleHookPhase = 'on';
4
4
  export interface DeclaredHook {
5
5
  target: string;
6
6
  phase: HookPhase;
@@ -1,5 +1,5 @@
1
1
  import { isRecord } from '../../shared/predicates.js';
2
- import { LIFECYCLE_EVENTS, isLifecycleEvent } from './lifecycle-catalog.js';
2
+ import { LIFECYCLE_CATALOG, LIFECYCLE_EVENTS, isLifecycleEvent } from './lifecycle-catalog.js';
3
3
  function typeName(value) {
4
4
  if (value === null)
5
5
  return 'null';
@@ -61,8 +61,9 @@ function validateLifecycleDeclaration(value, index, issue) {
61
61
  issue('hook_manifest_invalid', 'event must name a supported lifecycle event', String(value['event']), LIFECYCLE_EVENTS.join(' | '), `Set event to one of: ${LIFECYCLE_EVENTS.join(', ')}.`, `${path}.event`);
62
62
  return undefined;
63
63
  }
64
- if (value['phase'] !== 'on') {
65
- issue('hook_manifest_invalid', 'phase must be on', String(value['phase']), 'on', 'Set phase to on.', `${path}.phase`);
64
+ const phase = LIFECYCLE_CATALOG[value['event']].phase;
65
+ if (value['phase'] !== phase) {
66
+ issue('hook_manifest_invalid', `phase must be ${phase}`, String(value['phase']), phase, `Set phase to ${phase}.`, `${path}.phase`);
66
67
  return undefined;
67
68
  }
68
69
  if (!isEffects(value['effects'])) {
@@ -71,7 +72,7 @@ function validateLifecycleDeclaration(value, index, issue) {
71
72
  }
72
73
  return {
73
74
  event: value['event'],
74
- phase: 'on',
75
+ phase,
75
76
  op: value['op'],
76
77
  description: value['description'],
77
78
  effects: value['effects'],
@@ -170,7 +170,9 @@ function buildRequest(spec, invocation, cwd) {
170
170
  if (invocation.result !== undefined) {
171
171
  throw protocolError(spec, invocation, `${spec.phase} hook invocation included a result`, 'result only for after hooks');
172
172
  }
173
- return { ...base, phase: spec.phase };
173
+ if (spec.phase === 'before')
174
+ return { ...base, phase: 'before' };
175
+ return { ...base, phase: 'replace' };
174
176
  }
175
177
  function parseEnvelope(spec, invocation, stdout) {
176
178
  let value;
@@ -1,12 +1,22 @@
1
- import type { LifecycleHookRequest } from '../../../hook-authoring.js';
1
+ import type { LifecycleHookRequest, NodeCreateHookRequest } from '../../../hook-authoring.js';
2
2
  import type { EffectiveLifecycleHook } from '../discovery.js';
3
- export declare const LIFECYCLE_HOOK_TIMEOUT_MS = 5000;
4
- export declare const LIFECYCLE_EVENT_BUDGET_MS = 10000;
5
- export interface LifecycleHookInvocation {
3
+ export interface NodeLifecycleHookInvocation {
6
4
  node: LifecycleHookRequest['node'];
7
5
  runtime: LifecycleHookRequest['runtime'];
8
6
  }
9
- /** Runs one lifecycle hook. The node launch remains authoritative on every failure. */
10
- export declare function invokeLifecycleHook(hook: EffectiveLifecycleHook, invocation: LifecycleHookInvocation, timeoutMs: number): Promise<void>;
11
- /** Executes lifecycle hooks in discovery order, within one bounded event budget. */
12
- export declare function invokeLifecycleHooks(hooks: readonly EffectiveLifecycleHook[], invocation: LifecycleHookInvocation): Promise<void>;
7
+ export interface NodeCreateHookInvocation {
8
+ create: NodeCreateHookRequest['create'];
9
+ runtime: NodeCreateHookRequest['runtime'];
10
+ }
11
+ export interface BlockingLifecycleHookRefusal {
12
+ hook: EffectiveLifecycleHook;
13
+ message: string;
14
+ next: string;
15
+ }
16
+ /** Runs one lifecycle hook. `node:start` and `node:close` remain best-effort. */
17
+ export declare function invokeLifecycleHook(hook: EffectiveLifecycleHook, invocation: NodeLifecycleHookInvocation, timeoutMs: number): Promise<void>;
18
+ /** Executes non-blocking lifecycle hooks in discovery order within the event's catalogued budget. */
19
+ export declare function invokeLifecycleHooks(hooks: readonly EffectiveLifecycleHook[], invocation: NodeLifecycleHookInvocation): Promise<void>;
20
+ /** Runs blocking `node:create` hooks. Every protocol or process failure refuses the create. */
21
+ export declare function invokeBlockingLifecycleHooks(hooks: readonly EffectiveLifecycleHook[], invocation: NodeCreateHookInvocation): Promise<BlockingLifecycleHookRefusal | null>;
22
+ export declare function lifecycleHookAttribution(hook: EffectiveLifecycleHook): string;
@@ -3,53 +3,109 @@ import { spawn } from 'node:child_process';
3
3
  import { PROCESS_GROUP_KILL_GRACE_MS, posixProcessGroupSpawnOptions, terminateProcessGroup } from '../../../hook-process.js';
4
4
  import { isRecord } from '../../../shared/predicates.js';
5
5
  import { diag } from '../../io.js';
6
+ import { LIFECYCLE_CATALOG } from '../lifecycle-catalog.js';
6
7
  const MAX_STDOUT = 10 * 1024 * 1024;
7
- export const LIFECYCLE_HOOK_TIMEOUT_MS = 5_000;
8
- export const LIFECYCLE_EVENT_BUDGET_MS = 10_000;
9
- /** Runs one lifecycle hook. The node launch remains authoritative on every failure. */
8
+ /** Runs one lifecycle hook. `node:start` and `node:close` remain best-effort. */
10
9
  export async function invokeLifecycleHook(hook, invocation, timeoutMs) {
11
10
  try {
12
- const request = {
13
- protocolVersion: 1,
14
- op: hook.op,
15
- event: hook.event,
16
- phase: hook.phase,
17
- operationId: randomBytes(16).toString('hex'),
18
- node: invocation.node,
19
- runtime: invocation.runtime,
20
- context: { cwd: invocation.node.cwd },
21
- };
22
- const result = await runLifecycleProcess(hook, JSON.stringify(request), timeoutMs);
23
- if (result.signal !== null)
24
- throw new Error(`executable was killed by signal ${result.signal}`);
25
- const envelope = parseEnvelope(result.stdout);
26
- if (envelope.ok) {
27
- if (result.status !== 0)
28
- diag(`crtr: ${label(hook)} returned ok:true but exited ${result.status} (honoring the envelope)`);
29
- return;
30
- }
31
- diag(`crtr: ${label(hook)} failed: ${envelope.error.code}: ${envelope.error.message}`);
11
+ const envelope = await executeLifecycleHook(hook, invocation, timeoutMs);
12
+ if (!envelope.ok)
13
+ diag(`crtr: ${label(hook)} failed: ${envelope.error.code}: ${envelope.error.message}`);
32
14
  }
33
15
  catch (error) {
34
16
  diag(`crtr: ${label(hook)} failed: ${describeError(error)}`);
35
17
  }
36
18
  }
37
- /** Executes lifecycle hooks in discovery order, within one bounded event budget. */
19
+ /** Executes non-blocking lifecycle hooks in discovery order within the event's catalogued budget. */
38
20
  export async function invokeLifecycleHooks(hooks, invocation) {
39
- const deadline = Date.now() + LIFECYCLE_EVENT_BUDGET_MS;
21
+ if (hooks.length === 0)
22
+ return;
23
+ const { hookTimeoutMs, eventBudgetMs } = LIFECYCLE_CATALOG[hooks[0].event];
24
+ const deadline = Date.now() + eventBudgetMs;
40
25
  for (const hook of hooks) {
41
26
  const remaining = deadline - Date.now();
42
- // A timed-out hook must be reaped before launch can proceed. Reserve the
43
- // process-group termination grace inside the event budget rather than
44
- // letting the final cleanup extend the node-start critical path.
45
- const timeoutMs = Math.min(LIFECYCLE_HOOK_TIMEOUT_MS, remaining - PROCESS_GROUP_KILL_GRACE_MS);
27
+ const timeoutMs = Math.min(hookTimeoutMs, remaining - PROCESS_GROUP_KILL_GRACE_MS);
46
28
  if (timeoutMs <= 0) {
47
- diag(`crtr: lifecycle hook event ${hook.event} exceeded its ${LIFECYCLE_EVENT_BUDGET_MS}ms aggregate budget; skipped ${label(hook)}`);
29
+ diag(`crtr: lifecycle hook event ${hook.event} exceeded its ${eventBudgetMs}ms aggregate budget; skipped ${label(hook)}`);
48
30
  return;
49
31
  }
50
32
  await invokeLifecycleHook(hook, invocation, timeoutMs);
51
33
  }
52
34
  }
35
+ /** Runs blocking `node:create` hooks. Every protocol or process failure refuses the create. */
36
+ export async function invokeBlockingLifecycleHooks(hooks, invocation) {
37
+ if (hooks.length === 0)
38
+ return null;
39
+ const { hookTimeoutMs, eventBudgetMs } = LIFECYCLE_CATALOG['node:create'];
40
+ const deadline = Date.now() + eventBudgetMs;
41
+ for (const hook of hooks) {
42
+ const remaining = deadline - Date.now();
43
+ const timeoutMs = Math.min(hookTimeoutMs, remaining - PROCESS_GROUP_KILL_GRACE_MS);
44
+ if (timeoutMs <= 0) {
45
+ return refusal(hook, `lifecycle hook event node:create exceeded its ${eventBudgetMs}ms aggregate budget`);
46
+ }
47
+ try {
48
+ const envelope = await executeLifecycleHook(hook, invocation, timeoutMs);
49
+ if (!envelope.ok)
50
+ return {
51
+ hook,
52
+ message: envelope.error.message,
53
+ next: envelope.error.next ?? attribution(hook),
54
+ };
55
+ }
56
+ catch (error) {
57
+ return refusal(hook, describeError(error));
58
+ }
59
+ }
60
+ return null;
61
+ }
62
+ function refusal(hook, cause) {
63
+ return {
64
+ hook,
65
+ message: `${label(hook)} failed: ${cause}`,
66
+ next: attribution(hook),
67
+ };
68
+ }
69
+ export function lifecycleHookAttribution(hook) {
70
+ return attribution(hook);
71
+ }
72
+ async function executeLifecycleHook(hook, invocation, timeoutMs) {
73
+ const request = hook.event === 'node:create'
74
+ ? createRequest(hook, invocation)
75
+ : nodeRequest(hook, invocation);
76
+ const result = await runLifecycleProcess(hook, JSON.stringify(request), timeoutMs);
77
+ if (result.signal !== null)
78
+ throw new Error(`executable was killed by signal ${result.signal}`);
79
+ const envelope = parseEnvelope(result.stdout);
80
+ if (envelope.ok && result.status !== 0) {
81
+ diag(`crtr: ${label(hook)} returned ok:true but exited ${result.status} (honoring the envelope)`);
82
+ }
83
+ return envelope;
84
+ }
85
+ function nodeRequest(hook, invocation) {
86
+ return {
87
+ protocolVersion: 1,
88
+ op: hook.op,
89
+ event: hook.event,
90
+ phase: hook.phase,
91
+ operationId: randomBytes(16).toString('hex'),
92
+ node: invocation.node,
93
+ runtime: invocation.runtime,
94
+ context: { cwd: invocation.node.cwd },
95
+ };
96
+ }
97
+ function createRequest(hook, invocation) {
98
+ return {
99
+ protocolVersion: 1,
100
+ op: hook.op,
101
+ event: 'node:create',
102
+ phase: 'before',
103
+ operationId: randomBytes(16).toString('hex'),
104
+ create: invocation.create,
105
+ runtime: invocation.runtime,
106
+ context: { cwd: invocation.create.cwd },
107
+ };
108
+ }
53
109
  async function runLifecycleProcess(hook, input, timeoutMs) {
54
110
  let child;
55
111
  try {
@@ -147,6 +203,9 @@ function exactKeys(value, required, optional = []) {
147
203
  function label(hook) {
148
204
  return `plugin "${hook.plugin.name}" lifecycle hook "${hook.op}" for ${hook.event}`;
149
205
  }
206
+ function attribution(hook) {
207
+ return `Refused by plugin ${hook.plugin.name}, op ${hook.op}.`;
208
+ }
150
209
  function describeError(error) {
151
210
  return error instanceof Error ? error.message : String(error);
152
211
  }
@@ -3,23 +3,28 @@ import { type BindingId } from './keybindings/catalog.js';
3
3
  export declare function configPath(scope: Scope): string | null;
4
4
  export declare function statePath(scope: Scope): string | null;
5
5
  export declare function readConfig(scope: Scope): ResolvedScopeConfig;
6
- /** Read the config that lives at an EXACT scope-root dir, rather than the
7
- * resolved nearest root for a scope. Lets a caller iterate each visible
8
- * project root's own config when several are in scope (nested `.crouter` dirs
9
- * or a profile-widened stack), instead of collapsing them onto the nearest. */
10
6
  export declare function readConfigAtRoot(root: string): ResolvedScopeConfig;
11
7
  export declare function readState(scope: Scope): ScopeState;
8
+ export type RawSettingsTarget = {
9
+ scope: 'user';
10
+ } | {
11
+ scope: 'profile';
12
+ profileId: string;
13
+ };
14
+ export declare class InvalidRawSettingsError extends Error {
15
+ constructor(cause: unknown);
16
+ }
17
+ export declare function readRawSettingsForTarget(target: RawSettingsTarget): {
18
+ settings: Record<string, unknown> | null;
19
+ revision: string;
20
+ };
21
+ export declare function replaceRawSettingsForTarget(target: RawSettingsTarget, next: Record<string, unknown>, ifRevision: string): {
22
+ revision: string;
23
+ changed: boolean;
24
+ };
12
25
  type RawConfig = Record<string, unknown>;
13
26
  export declare function updateRawConfigAtomically(scope: Scope, mutate: (latest: RawConfig) => RawConfig): RawConfig;
14
27
  export declare function writeConfig(scope: Scope, config: ScopeConfig): void;
15
- /** Persist a RAW PARTIAL config to a scope's config.json exactly as given.
16
- * Unlike `writeConfig`, this makes no promise that every field is present --
17
- * callers pass the sparse partial that should live on disk, so an untouched
18
- * key stays absent (and `readConfig`/`mergeConfig` re-derive it live from
19
- * whichever build is running). This is the single central primitive every
20
- * mutation path funnels through so no writer ever freezes a fully
21
- * materialized `modelLadders` (concrete model ids that go stale across
22
- * builds -- the live-Hearth freeze bug) onto disk. */
23
28
  export declare function writeRawConfig(scope: Scope, config: Partial<ScopeConfig>): void;
24
29
  export declare function writeState(scope: Scope, state: ScopeState): void;
25
30
  export declare function ensureScopeInitialized(scope: Scope, root: string): void;