@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
@@ -1,22 +1,10 @@
1
- // canvas-inbox-watcher.ts — in-process delivery of a canvas node's active inbox.
2
- //
3
- // The watcher polls the append-only inbox in physical order, coalesces each quiet
4
- // burst, and hands hidden context cards and visible deliveries to pi. A
5
- // burst mixing a person's words with node reports splits into several visible
6
- // deliveries, one per cycle. A send return is only a handoff. Exact cursor persistence at agent_settled is the
7
- // durable boundary; an uncommitted process exit is recovered by rereading from
8
- // the unchanged cursor.
9
- //
10
- // Context sends never trigger a turn: the visible human delivery is the sole
11
- // trigger, so the agent cannot run on context alone before hearing the person.
12
- // A context-only batch keeps its wake by triggering on its LAST card while idle,
13
- // and rides the running turn while streaming. A deferred-only idle batch stays
14
- // held until either durable mail wakes a turn or the broker prepends it to an
15
- // already-admitted live human prompt; that bridge still commits only at
16
- // agent_settled.
1
+ // Wake mail is claimed through the daemon and acknowledged by delivery identity
2
+ // only after Pi settles. Deferred mail rides a natural input; a refused turn
3
+ // releases its claims instead of acknowledging content the model never consumed.
17
4
  import { envNodeId, envProfileId, envWatcherDebounceMs, envWatcherTickMs } from '../shared/env.js';
18
- import { coalesceBrokerInbox, entryCards, readBrokerCanceledEntryIds, readBrokerCursor, readBrokerInboxSince, reportRefNodeId, } from '../core/runtime/broker/inbox.js';
19
- import { advanceBrokerInboxCursor, brokerExtensionState } from '../core/runtime/broker/daemon-ops.js';
5
+ import { coalesceBrokerInbox, entryCards, reportRefNodeId, } from '../core/runtime/broker/inbox.js';
6
+ import { acknowledgeBrokerMail, claimBrokerMail, brokerExtensionState } from '../core/runtime/broker/daemon-ops.js';
7
+ import { wasTurnRefused } from '../core/runtime/broker/extension-abort.js';
20
8
  import { announceMessageOrigin } from '../core/runtime/stamp/channel.js';
21
9
  import { captureGoalIfAbsent, generateAndCommitInitialName } from './broker-local.js';
22
10
  import { emitEvent } from '../core/events/emit.js';
@@ -45,23 +33,11 @@ function currentNodeId() {
45
33
  const nodeId = envNodeId();
46
34
  return nodeId === undefined || nodeId.trim() === '' ? undefined : nodeId;
47
35
  }
48
- /** The node's profile, read fresh each poll: `crtr profile pause` mutates the
49
- * manifest file directly, so a live broker sees the flip on its next tick with
50
- * no daemon round-trip. '' is the historical no-profile row. */
36
+ /** Profile pause is read fresh each poll from its owning scoped store. */
51
37
  function profilePaused() {
52
38
  const profileId = envProfileId();
53
39
  return profileId !== undefined && profileId !== '' && isProfilePaused(profileId);
54
40
  }
55
- function isFilesystemError(error) {
56
- if (typeof error !== 'object' || error === null)
57
- return false;
58
- try {
59
- return typeof error.code === 'string';
60
- }
61
- catch {
62
- return false;
63
- }
64
- }
65
41
  function safeErrorClass(error) {
66
42
  try {
67
43
  return errorClassFromError(error);
@@ -75,7 +51,7 @@ function emitBestEffort(input) {
75
51
  emitEvent(input);
76
52
  }
77
53
  catch {
78
- // Canonical events are observational; delivery and cursor state stay authoritative.
54
+ // SQL delivery state remains authoritative if diagnostics fail.
79
55
  }
80
56
  }
81
57
  function emitFreshBestEffort(input) {
@@ -207,8 +183,8 @@ class CanvasInboxWatcher {
207
183
  lastCtx;
208
184
  streaming = false;
209
185
  nodeId;
210
- cursor;
211
- durableCursor;
186
+ claims = new Map();
187
+ tickInFlight;
212
188
  buffer = [];
213
189
  lastArrival = 0;
214
190
  pendingHandoffs = [];
@@ -272,10 +248,10 @@ class CanvasInboxWatcher {
272
248
  return;
273
249
  try {
274
250
  this.assertNodeOwnership(nodeId, 'settle');
275
- // agent_settled is the authoritative durable boundary: Pi guarantees
276
- // every queued continuation has drained, so hasPendingMessages() must
277
- // not veto the commit here. Await crtrd before changing the in-memory
278
- // durable cursor; a failed commit remains replayable after process exit.
251
+ await this.tickInFlight;
252
+ // Include kickoff claims even when the first turn settled before a poll.
253
+ if (!this.seeded)
254
+ await this.claimMail(nodeId);
279
255
  const result = await this.settle(nodeId, ctx, false, true);
280
256
  if (result === 'refresh_held')
281
257
  this.dispose('refresh', false);
@@ -306,9 +282,7 @@ class CanvasInboxWatcher {
306
282
  });
307
283
  }
308
284
  try {
309
- // This is the graceful fallback when no agent_settled fired before exit.
310
- // crtrd rejects a cursor commit for a refresh, preserving every handoff
311
- // that may have reached this discarded conversation for the next broker.
285
+ // Refresh rejects receipts, preserving claims for the replacement broker.
312
286
  await this.disposeAtSettlement(reason, this.lastCtx);
313
287
  }
314
288
  catch (error) {
@@ -338,11 +312,7 @@ class CanvasInboxWatcher {
338
312
  this.reportNodesRefreshing = false;
339
313
  }
340
314
  }
341
- /** True when delivery must wait a tick for a report-node projection refresh.
342
- * The projection captured at session_start omits every node created since —
343
- * each reply-bearing `crtr human send` bridge and each child this node spawns — so their
344
- * reports would render as bare refs instead of inlined bodies. One refresh
345
- * per unknown node id keeps a genuinely missing node from stalling delivery. */
315
+ /** Refresh once per unknown report sender, so new children can inline their reports. */
346
316
  refreshForUnknownReportRefs() {
347
317
  const unknown = [];
348
318
  for (const entry of this.buffer) {
@@ -371,9 +341,7 @@ class CanvasInboxWatcher {
371
341
  // the normal debounce owns delivery instead of waiting a full tick.
372
342
  this.runTick('schedule');
373
343
  }
374
- /** Settle the graceful-shutdown fallback before disposing its retry state.
375
- * The daemon decides whether a refresh preserves the cursor, and this method
376
- * awaits that decision so a process exit never outruns an accepted commit. */
344
+ /** Await graceful-shutdown receipts before disposing their in-process state. */
377
345
  async disposeAtSettlement(reason, ctx) {
378
346
  const nodeId = currentNodeId();
379
347
  if (nodeId === undefined) {
@@ -402,8 +370,7 @@ class CanvasInboxWatcher {
402
370
  ...this.pendingHandoffs.splice(0),
403
371
  ];
404
372
  this.buffer.length = 0;
405
- this.cursor = undefined;
406
- this.durableCursor = undefined;
373
+ this.claims.clear();
407
374
  this.reportNodes.clear();
408
375
  this.reportNodesAsked.clear();
409
376
  this.pendingCommit = undefined;
@@ -502,85 +469,66 @@ class CanvasInboxWatcher {
502
469
  }
503
470
  this.escapeFatal(fatal, 'schedule');
504
471
  }
505
- runTick(fatalMode = 'throw') {
506
- if (!this.canMutate())
472
+ runTick(_fatalMode = 'throw') {
473
+ if (!this.canMutate() || this.tickInFlight !== undefined)
507
474
  return;
508
- try {
509
- this.tick();
510
- }
511
- catch (error) {
512
- if (error instanceof WatcherFatalError) {
513
- this.escapeFatal(error, fatalMode);
514
- return;
515
- }
516
- if (isFilesystemError(error))
517
- return;
518
- let fatal;
519
- try {
520
- this.raiseReadFatal('poll', error);
521
- }
522
- catch (tagged) {
523
- fatal = tagged;
524
- }
525
- this.escapeFatal(fatal, fatalMode);
526
- }
527
- }
528
- pollRead(read) {
529
- try {
530
- return { ok: true, value: read() };
531
- }
532
- catch (error) {
533
- if (isFilesystemError(error))
534
- return { ok: false };
535
- this.raiseReadFatal('poll', error);
536
- }
475
+ const attempt = this.tick().catch((error) => {
476
+ if (this.canMutate())
477
+ this.scheduleHandlerFatal(error, 'poll');
478
+ }).finally(() => {
479
+ if (this.tickInFlight === attempt)
480
+ this.tickInFlight = undefined;
481
+ });
482
+ this.tickInFlight = attempt;
537
483
  }
538
- seed(nodeId) {
484
+ async claimMail(nodeId) {
539
485
  this.assertNodeOwnership(nodeId, 'poll');
540
- if (this.seeded)
541
- return true;
542
- const initial = this.pollRead(() => readBrokerCursor(nodeId));
543
- if (!initial.ok)
486
+ const result = await claimBrokerMail(nodeId, 'wake');
487
+ if (!this.canMutate())
488
+ return false;
489
+ if (result.action === 'hold')
544
490
  return false;
545
491
  this.nodeId = nodeId;
546
- this.cursor = initial.value;
547
- this.durableCursor = initial.value;
548
492
  this.seeded = true;
493
+ const activeIds = new Set(result.deliveries.map((claim) => claim.entry.entry_id));
494
+ // A canceled buffered delivery disappears from the daemon's claim query.
495
+ const canceled = this.buffer.filter((entry) => !activeIds.has(entry.entry_id));
496
+ for (const entry of canceled)
497
+ this.claims.delete(entry.entry_id);
498
+ this.buffer.splice(0, this.buffer.length, ...this.buffer.filter((entry) => activeIds.has(entry.entry_id)));
499
+ for (const claim of result.deliveries) {
500
+ const id = claim.entry.entry_id;
501
+ if (this.claims.has(id))
502
+ continue;
503
+ this.claims.set(id, claim);
504
+ if (claim.handoff === 'inherited') {
505
+ operationIdContext.fresh((operationId) => {
506
+ this.pendingHandoffs.push({ ...deliveryFacts([claim.entry], 'visible'), operationId, settleAtAgentSettled: true });
507
+ });
508
+ }
509
+ else {
510
+ this.buffer.push(claim.entry);
511
+ this.lastArrival = Date.now();
512
+ }
513
+ }
549
514
  return true;
550
515
  }
551
- tick() {
516
+ async tick() {
552
517
  const nodeId = currentNodeId();
553
- if (nodeId === undefined || !this.seed(nodeId))
518
+ if (nodeId === undefined)
554
519
  return;
555
520
  if (this.pendingCommit !== undefined) {
556
- this.retryPendingCommitOnTick(nodeId);
521
+ const result = await this.retryPendingCommit(nodeId);
522
+ if (result === 'refresh_held' && this.canMutate())
523
+ this.dispose('refresh', false);
557
524
  return;
558
525
  }
559
- if (this.promptJoinInFlight)
560
- return;
561
- // A paused profile is inert: read nothing, send nothing, leave the cursor
562
- // where it is, so every queued entry — critical included, since aborting the
563
- // live turn is exactly the punch-through pause forbids — is still there to
564
- // deliver on resume. Pause is not a kill: the turn already running finishes,
565
- // and an in-flight handoff still commits above.
566
- if (profilePaused())
526
+ if (this.promptJoinInFlight || profilePaused())
567
527
  return;
568
- if (this.buffer.length === 0 && this.pendingHandoffs.length >= MAX_PENDING_HANDOFFS)
528
+ if (this.pendingHandoffs.length >= MAX_PENDING_HANDOFFS)
569
529
  return;
570
- if (this.buffer.length > 0) {
571
- const plan = this.ordinaryPlan();
572
- if (plan.sendCount > 0 && this.pendingHandoffs.length + plan.sendCount > MAX_PENDING_HANDOFFS)
573
- return;
574
- }
575
- const read = this.pollRead(() => readBrokerInboxSince(nodeId, this.cursor));
576
- if (!read.ok)
530
+ if (!await this.claimMail(nodeId))
577
531
  return;
578
- const newEntries = read.value;
579
- if (newEntries.length > 0) {
580
- this.cursor = newEntries.at(-1).entry_id;
581
- this.buffer.push(...newEntries);
582
- this.lastArrival = Date.now();
583
- }
584
532
  if (this.buffer.length > 0 && Date.now() - this.lastArrival >= this.cadence.debounceMs) {
585
533
  if (this.refreshForUnknownReportRefs())
586
534
  return;
@@ -588,16 +536,8 @@ class CanvasInboxWatcher {
588
536
  }
589
537
  }
590
538
  missingChannelEntries(entries) {
591
- // Interrupt-canceled entries (a human Esc racing a not-yet-injected send —
592
- // `POST /v1/nodes/{id}/interrupt`) are excluded from BOTH channels. Read
593
- // fresh each plan build: the cancel can land AFTER the entry was buffered.
594
- // Skipped entries are never handed off; the durable cursor covers them
595
- // positionally when a later entry settles (or they are re-filtered after a
596
- // restart).
597
- const canceled = this.nodeId === undefined ? undefined : readBrokerCanceledEntryIds(this.nodeId);
598
- const live = canceled === undefined || canceled.size === 0
599
- ? entries
600
- : entries.filter((entry) => !canceled.has(entry.entry_id));
539
+ // Each claim refresh removes canceled buffered entries before either channel sends.
540
+ const live = entries.filter((entry) => this.claims.has(entry.entry_id));
601
541
  const hiddenCoverage = this.coveredEntryIds('hidden');
602
542
  const visibleCoverage = this.coveredEntryIds('visible');
603
543
  return {
@@ -863,10 +803,7 @@ class CanvasInboxWatcher {
863
803
  this.requeueFromBatch(batch, execution.requeueEntryIds);
864
804
  return 'attempted';
865
805
  }
866
- /** Claim the deferred-only idle batch for the human prompt already admitted
867
- * by the broker. Each rendered delivery is persisted before that prompt, but
868
- * no cursor may advance until the prompt's agent_settled event proves the
869
- * turn consumed it. */
806
+ /** Deferred mail joins an admitted human prompt but remains unacknowledged until settlement. */
870
807
  async prependHeldDeferredInbox(session) {
871
808
  if (!this.canMutate() || this.nodeId === undefined || profilePaused())
872
809
  return;
@@ -950,15 +887,6 @@ class CanvasInboxWatcher {
950
887
  }
951
888
  return covered;
952
889
  }
953
- retryPendingCommitOnTick(nodeId) {
954
- void this.retryPendingCommit(nodeId).then((result) => {
955
- if (result === 'refresh_held' && this.canMutate())
956
- this.dispose('refresh', false);
957
- }).catch((error) => {
958
- if (this.canMutate())
959
- this.dispose('shutdown', false, undefined, { error, stage: 'settle' });
960
- });
961
- }
962
890
  retryPendingCommit(nodeId) {
963
891
  const pending = this.pendingCommit;
964
892
  if (pending === undefined)
@@ -973,28 +901,40 @@ class CanvasInboxWatcher {
973
901
  return attempt;
974
902
  }
975
903
  async attemptPendingCommit(nodeId, pending) {
904
+ let acknowledged = false;
976
905
  try {
977
- const directive = await advanceBrokerInboxCursor(nodeId, {
978
- throughEntryId: pending.throughEntryId,
979
- brokerPid: process.pid,
980
- });
981
- if (directive.action === 'hold_refresh')
906
+ const directive = await acknowledgeBrokerMail(nodeId, pending.receipts);
907
+ if (directive.action === 'hold')
982
908
  return 'refresh_held';
909
+ for (const receipt of pending.receipts) {
910
+ const result = directive.results.find((result) => result.delivery_id === receipt.delivery_id);
911
+ if (result?.outcome !== receipt.disposition && result?.outcome !== 'stale')
912
+ throw new Error('daemon returned no terminal mail settlement outcome');
913
+ }
914
+ acknowledged = directive.results.every((result) => result.outcome === 'acknowledged');
983
915
  }
984
916
  catch (error) {
985
917
  if (this.pendingCommit === pending) {
986
918
  for (const handoff of pending.handoffs) {
987
- emitDeliveryFailure(handoff.operationId, handoff, 'cursor_commit', error);
919
+ emitDeliveryFailure(handoff.operationId, handoff, 'receipt_commit', error);
988
920
  }
989
921
  }
990
922
  return 'held';
991
923
  }
992
924
  if (this.pendingCommit !== pending)
993
925
  return 'settled';
994
- this.durableCursor = pending.throughEntryId;
926
+ for (const id of pending.entryIds)
927
+ this.claims.delete(id);
928
+ if (pending.receipts.some((receipt) => receipt.disposition === 'released')) {
929
+ const released = new Set(pending.entryIds);
930
+ this.buffer.splice(0, this.buffer.length, ...this.buffer.filter((entry) => !released.has(entry.entry_id)));
931
+ }
995
932
  this.pendingCommit = undefined;
996
- for (const handoff of pending.handoffs)
997
- emitDeliveryCompleted(handoff);
933
+ const completed = this.detachPending((handoff) => handoff.entryIds.every((id) => !this.claims.has(id)));
934
+ for (const handoff of completed) {
935
+ if (acknowledged)
936
+ emitDeliveryCompleted(handoff);
937
+ }
998
938
  return 'settled';
999
939
  }
1000
940
  async settle(nodeId, ctx, holdPendingMessages, atAgentSettled = false) {
@@ -1009,109 +949,23 @@ class CanvasInboxWatcher {
1009
949
  }
1010
950
  if (this.pendingCommit !== undefined)
1011
951
  return this.retryPendingCommit(nodeId);
1012
- if (this.pendingHandoffs.length === 0)
952
+ const refused = wasTurnRefused();
953
+ const buffered = new Set(this.buffer.map((entry) => entry.entry_id));
954
+ const eligible = this.pendingHandoffs.filter((handoff) => atAgentSettled || handoff.settleAtAgentSettled !== true);
955
+ const handedOff = new Set(eligible.flatMap((handoff) => [...handoff.entryIds]));
956
+ const entryIds = [...this.claims.keys()].filter((id) => refused || (handedOff.has(id) && !buffered.has(id)));
957
+ if (entryIds.length === 0)
1013
958
  return 'settled';
1014
- const physical = this.readPhysicalSettlementState(nodeId);
1015
- if (physical.durableIndex >= 0) {
1016
- const alreadyDurable = this.detachPending((handoff) => physical.throughIndexes.get(handoff) <= physical.durableIndex);
1017
- for (const handoff of alreadyDurable)
1018
- emitDeliveryCompleted(handoff);
1019
- }
1020
- const agentSettledBlock = atAgentSettled
1021
- ? undefined
1022
- : this.pendingHandoffs
1023
- .filter((handoff) => handoff.settleAtAgentSettled === true)
1024
- .map((handoff) => physical.throughIndexes.get(handoff))
1025
- .reduce((first, index) => first === undefined ? index : Math.min(first, index), undefined);
1026
- const eligible = this.pendingHandoffs.filter((handoff) => {
1027
- const throughIndex = physical.throughIndexes.get(handoff);
1028
- return (handoff.settleAtAgentSettled !== true || atAgentSettled)
1029
- && (physical.blockedIndex === undefined || throughIndex < physical.blockedIndex)
1030
- && (agentSettledBlock === undefined || throughIndex < agentSettledBlock);
1031
- });
1032
- if (eligible.length === 0)
1033
- return 'settled';
1034
- let target = eligible[0];
1035
- for (const handoff of eligible.slice(1)) {
1036
- if (physical.throughIndexes.get(handoff) > physical.throughIndexes.get(target))
1037
- target = handoff;
1038
- }
1039
- const committing = new Set(eligible);
1040
959
  this.pendingCommit = {
1041
- handoffs: this.detachPending((handoff) => committing.has(handoff)),
1042
- throughEntryId: target.throughEntryId,
960
+ handoffs: eligible,
961
+ entryIds,
962
+ receipts: entryIds.map((id) => {
963
+ const claim = this.claims.get(id);
964
+ return { delivery_id: claim.delivery_id, attempts: claim.attempts, disposition: refused ? 'released' : 'acknowledged' };
965
+ }),
1043
966
  };
1044
967
  return this.retryPendingCommit(nodeId);
1045
968
  }
1046
- readPhysicalSettlementState(nodeId) {
1047
- let entries;
1048
- try {
1049
- entries = readBrokerInboxSince(nodeId);
1050
- }
1051
- catch (error) {
1052
- this.raiseReadFatal('settle', error);
1053
- }
1054
- try {
1055
- const indexes = new Map();
1056
- entries.forEach((entry, index) => indexes.set(entry.entry_id, index));
1057
- const indexOf = (entryId, owner) => {
1058
- const index = indexes.get(entryId);
1059
- if (index === undefined)
1060
- throw new Error(`${owner} entry_id is absent from the physical inbox: ${entryId}`);
1061
- return index;
1062
- };
1063
- const durableIndex = this.durableCursor === undefined ? -1 : indexOf(this.durableCursor, 'durable cursor');
1064
- const cursorIndex = this.cursor === undefined ? -1 : indexOf(this.cursor, 'read cursor');
1065
- if (cursorIndex < durableIndex)
1066
- throw new Error('read cursor precedes the durable cursor');
1067
- let previousBufferIndex = -1;
1068
- let blockedIndex;
1069
- const bufferedIds = new Set();
1070
- for (const entry of this.buffer) {
1071
- if (bufferedIds.has(entry.entry_id))
1072
- throw new Error(`buffer contains duplicate entry_id: ${entry.entry_id}`);
1073
- bufferedIds.add(entry.entry_id);
1074
- const index = indexOf(entry.entry_id, 'buffer');
1075
- if (index <= previousBufferIndex)
1076
- throw new Error('buffer is not in physical inbox order');
1077
- if (index <= durableIndex)
1078
- throw new Error('buffer contains an entry at or before the durable cursor');
1079
- if (cursorIndex < index)
1080
- throw new Error('buffer contains an entry beyond the read cursor');
1081
- previousBufferIndex = index;
1082
- blockedIndex = blockedIndex === undefined ? index : Math.min(blockedIndex, index);
1083
- }
1084
- const throughIndexes = new Map();
1085
- for (const handoff of this.pendingHandoffs) {
1086
- if (handoff.entryIds.length === 0)
1087
- throw new Error('pending handoff has no entry_ids');
1088
- if (handoff.entryIds.at(-1) !== handoff.throughEntryId) {
1089
- throw new Error('pending handoff through_entry_id is not its physical-last attempted entry');
1090
- }
1091
- let previousIndex = -1;
1092
- const handoffIds = new Set();
1093
- for (const entryId of handoff.entryIds) {
1094
- if (handoffIds.has(entryId))
1095
- throw new Error(`pending handoff contains duplicate entry_id: ${entryId}`);
1096
- handoffIds.add(entryId);
1097
- const index = indexOf(entryId, 'pending handoff');
1098
- if (index <= previousIndex)
1099
- throw new Error('pending handoff entry_ids are not in physical inbox order');
1100
- if (cursorIndex < index)
1101
- throw new Error('pending handoff contains an entry beyond the read cursor');
1102
- previousIndex = index;
1103
- }
1104
- const throughIndex = indexOf(handoff.throughEntryId, 'pending handoff through');
1105
- if (throughIndex !== previousIndex)
1106
- throw new Error('pending handoff through_entry_id is physically inconsistent');
1107
- throughIndexes.set(handoff, throughIndex);
1108
- }
1109
- return { throughIndexes, durableIndex, blockedIndex };
1110
- }
1111
- catch (error) {
1112
- this.raiseReadFatal('settle', error);
1113
- }
1114
- }
1115
969
  detachPending(predicate) {
1116
970
  const removed = [];
1117
971
  for (let index = this.pendingHandoffs.length - 1; index >= 0; index -= 1) {
@@ -1,4 +1,5 @@
1
- import { type BrokerPassiveEntry } from '../core/runtime/broker/passive.js';
1
+ import type { MailEntryDTO } from '../api/dto/mail.js';
2
+ type BrokerPassiveEntry = MailEntryDTO;
2
3
  interface InputEventLike {
3
4
  type: 'input';
4
5
  text: string;
@@ -15,7 +16,8 @@ type InputEventResultLike = {
15
16
  action: 'handled';
16
17
  };
17
18
  interface PiLike {
18
- on: (event: 'input', handler: (event: InputEventLike, ctx: any) => InputEventResultLike | void | Promise<InputEventResultLike | void>) => void;
19
+ on(event: 'input', handler: (event: InputEventLike, ctx: any) => InputEventResultLike | void | Promise<InputEventResultLike | void>): void;
20
+ on(event: 'agent_settled' | 'session_shutdown', handler: (event: any, ctx: any) => void | Promise<void>): void;
19
21
  }
20
22
  /**
21
23
  * Render drained passive entries (oldest first) into one XML pre-text block.
@@ -32,11 +34,5 @@ export declare function formatPassive(entries: BrokerPassiveEntry[], reportNodes
32
34
  * becomes the body's first child instead.
33
35
  */
34
36
  export declare function placeBacklog(text: string, preText: string): string;
35
- /**
36
- * Register the passive-context drain on `pi`.
37
- *
38
- * Returns immediately (inert) when CRTR_NODE_ID is absent. The `input` handler
39
- * is the whole extension: drain on every message, prepend when non-empty.
40
- */
41
37
  export declare function registerCanvasPassiveContext(pi: PiLike): void;
42
38
  export default registerCanvasPassiveContext;
@@ -1,28 +1,11 @@
1
- // canvas-passive-context.ts — pi extension for pi-native canvas agent nodes.
2
- //
3
- // Loaded into every canvas node's pi process via the node's launch.extensions
4
- // list. INERT when CRTR_NODE_ID is absent (plain pi session or legacy job agent).
5
- //
6
- // The passive-subscription drain. A PASSIVE subscription (active=false edge)
7
- // never wakes its subscriber: `push` routes its pointers to passive.jsonl, which
8
- // the inbox-watcher never polls. They simply accumulate. This extension is what
9
- // finally surfaces them — the moment the node is MESSAGED.
10
- //
11
- // pi fires an `input` event for every user message (human-typed, an RPC, or an
12
- // extension's sendUserMessage — including the inbox-watcher's own wake). On that
13
- // event we DRAIN the node's passive accumulator and, when non-empty, prepend
14
- // every entry as timestamped XML to the message text via the `transform` action.
15
- // So the backlog rides in as pre-text on whatever message next engages the node,
16
- // before the LLM sees it — and is cleared in the same step (drain = read+clear),
17
- // so it surfaces exactly once.
18
- //
19
- // Plain TS-with-types — no imports from @earendil-works/* so this compiles inside
20
- // crouter's own tsc build without a dep on the pi packages.
1
+ // Passive mail is claimed only on natural input and acknowledged after settlement.
2
+ // Formatting failures retain the SQL claim; refused turns release it for replay.
21
3
  import { envNodeId } from '../shared/env.js';
22
4
  import { existsSync, readFileSync } from 'node:fs';
23
- import { brokerExtensionState } from '../core/runtime/broker/daemon-ops.js';
5
+ import { acknowledgeBrokerMail, claimBrokerMail, brokerExtensionState } from '../core/runtime/broker/daemon-ops.js';
6
+ import { wasTurnRefused } from '../core/runtime/broker/extension-abort.js';
7
+ import { emitEvent } from '../core/events/emit.js';
24
8
  import { reportPathFromRef } from '../core/runtime/broker-extension-render.js';
25
- import { drainBrokerPassive } from '../core/runtime/broker/passive.js';
26
9
  import { parseCard } from '../shared/generated-context.js';
27
10
  // Per-entry body cap so a single fat report can't blow the context budget. The
28
11
  // full report stays on disk at `ref` if the agent needs more.
@@ -126,30 +109,95 @@ export function placeBacklog(text, preText) {
126
109
  return `${text.slice(0, bodyStart)}${preText}\n${text.slice(bodyStart)}`;
127
110
  }
128
111
  // Extension
129
- /**
130
- * Register the passive-context drain on `pi`.
131
- *
132
- * Returns immediately (inert) when CRTR_NODE_ID is absent. The `input` handler
133
- * is the whole extension: drain on every message, prepend when non-empty.
134
- */
135
112
  export function registerCanvasPassiveContext(pi) {
113
+ const claims = new Map();
114
+ let pending;
115
+ let settlement;
116
+ function reportFailure(error) {
117
+ try {
118
+ emitEvent({ level: 'error', event: 'inbox.delivery.failed', outcome: 'failed', error, fields: { channel: 'passive' } });
119
+ }
120
+ catch {
121
+ // SQL claims and pending receipts remain authoritative if diagnostics fail.
122
+ }
123
+ }
124
+ async function settle(nodeId) {
125
+ if (settlement !== undefined)
126
+ return settlement;
127
+ if (pending === undefined) {
128
+ pending = [...claims.values()].filter((item) => item.injected).map(({ claim }) => ({
129
+ delivery_id: claim.delivery_id, attempts: claim.attempts,
130
+ disposition: wasTurnRefused() ? 'released' : 'acknowledged',
131
+ }));
132
+ }
133
+ if (pending.length === 0) {
134
+ pending = undefined;
135
+ return true;
136
+ }
137
+ const receipts = pending;
138
+ const attempt = (async () => {
139
+ try {
140
+ const result = await acknowledgeBrokerMail(nodeId, receipts);
141
+ if (result.action === 'hold')
142
+ return false;
143
+ for (const receipt of receipts) {
144
+ const confirmed = result.results.find((result) => result.delivery_id === receipt.delivery_id);
145
+ if (confirmed?.outcome !== receipt.disposition && confirmed?.outcome !== 'stale')
146
+ throw new Error(`passive mail settlement has no terminal outcome: ${receipt.delivery_id}`);
147
+ }
148
+ for (const receipt of receipts)
149
+ claims.delete(receipt.delivery_id);
150
+ pending = undefined;
151
+ return true;
152
+ }
153
+ catch (error) {
154
+ reportFailure(error);
155
+ return false;
156
+ }
157
+ })().finally(() => { if (settlement === attempt)
158
+ settlement = undefined; });
159
+ settlement = attempt;
160
+ return attempt;
161
+ }
136
162
  pi.on('input', async (event) => {
163
+ const nodeId = envNodeId();
164
+ if (nodeId === undefined || nodeId.trim() === '')
165
+ return;
137
166
  try {
138
- const nodeId = envNodeId();
139
- if (nodeId === undefined || nodeId.trim() === '')
140
- return; // not a canvas node
141
- const drained = drainBrokerPassive(nodeId);
142
- if (drained.length === 0)
143
- return; // nothing accumulated → leave the message as-is
167
+ if (pending !== undefined && !await settle(nodeId))
168
+ return;
169
+ const result = await claimBrokerMail(nodeId, 'passive');
170
+ if (result.action === 'hold')
171
+ return;
172
+ for (const claim of result.deliveries) {
173
+ if (!claims.has(claim.delivery_id))
174
+ claims.set(claim.delivery_id, { claim, injected: false });
175
+ }
176
+ const unsent = [...claims.values()].filter((item) => !item.injected);
177
+ if (unsent.length === 0)
178
+ return;
144
179
  const state = await brokerExtensionState(nodeId);
145
180
  const reportNodes = new Map(state.report_nodes.map((node) => [node.node_id, node]));
146
- const text = placeBacklog(event.text, formatPassive(drained, reportNodes));
181
+ const text = placeBacklog(event.text, formatPassive(unsent.map(({ claim }) => claim.entry), reportNodes));
182
+ for (const item of unsent)
183
+ item.injected = true;
147
184
  return { action: 'transform', text, images: event.images };
148
185
  }
149
- catch {
150
- // Best-effort: a drain/format failure must never drop the user's message.
186
+ catch (error) {
187
+ // The original input proceeds; unformatted mail remains held and unacknowledged.
188
+ reportFailure(error);
151
189
  return;
152
190
  }
153
191
  });
192
+ pi.on('agent_settled', async () => {
193
+ const nodeId = envNodeId();
194
+ if (nodeId !== undefined && nodeId !== '')
195
+ await settle(nodeId);
196
+ });
197
+ pi.on('session_shutdown', async (_event, ctx) => {
198
+ const nodeId = envNodeId();
199
+ if (nodeId !== undefined && nodeId !== '' && ctx?.hasPendingMessages?.() !== true)
200
+ await settle(nodeId);
201
+ });
154
202
  }
155
203
  export default registerCanvasPassiveContext;