@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
@@ -7,16 +7,19 @@
7
7
  // its helpers in isolation.
8
8
  import { test, before, after, afterEach } from 'node:test';
9
9
  import assert from 'node:assert/strict';
10
- import { existsSync, mkdirSync, readFileSync, realpathSync, rmSync, writeFileSync } from 'node:fs';
10
+ import { existsSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
11
11
  import { join } from 'node:path';
12
12
  import { createHeadlessHarness, readLines } from '../helpers/harness.js';
13
13
  import { createAttachKit } from '../helpers/broker-clients.js';
14
+ import { openDb } from '../../canvas/db.js';
14
15
  import { defaultModelLaddersConfig } from '../../../types.js';
15
- import { admitProviderRetryEpisode, readFault, readProviderRetryEpisode } from '../../runtime/fault.js';
16
+ import { admitEpisode, readEpisode as readProviderRetryEpisode, readFault, recordPendingEpisode } from '../../canvas/recovery.js';
16
17
  import { buildPiArgv, normalizeModel } from '../../runtime/launch.js';
18
+ import { writeRawConfig } from '../../config.js';
17
19
  import { encodeFrame } from '../../runtime/broker-protocol.js';
18
20
  import { CONNECTION_FAULT_RECOVERY_BODY, PROVIDER_FAULT_RECOVERY_BODY, generatedContextText, parseCard } from '../../../shared/generated-context.js';
19
21
  import { RUNTIME_RESTART_CONTINUATION } from '../../runtime/kickoff.js';
22
+ import { writeSubscriptionPool } from '../../subscription-state.js';
20
23
  const MODEL_LADDERS = defaultModelLaddersConfig();
21
24
  function resolvedModel(spec) {
22
25
  const normalized = normalizeModel(spec, MODEL_LADDERS);
@@ -31,26 +34,46 @@ const OPENAI_STRONG = resolvedModel('openai/strong');
31
34
  let h;
32
35
  const kit = createAttachKit(() => h);
33
36
  const { attachUntil } = kit;
34
- function poolPath(provider) {
35
- const name = provider === 'anthropic' ? 'anthropic-subscriptions.json' : 'openai-codex-subscriptions.json';
36
- return join(h.agentHome, '.pi', 'agent', name);
37
+ function episodeSessionId(episode) {
38
+ assert.ok(episode.sessionId !== null, 'a provider retry episode is bound to a session');
39
+ return episode.sessionId;
37
40
  }
38
- function seedPool(provider, cooling) {
39
- const path = poolPath(provider);
40
- mkdirSync(join(h.agentHome, '.pi', 'agent'), { recursive: true });
41
- writeFileSync(path, JSON.stringify([{
41
+ function admitProviderRetryEpisode(nodeId, episode, fault) {
42
+ return admitEpisode(nodeId, { sessionId: episodeSessionId(episode), faultId: fault.faultId, retry: fault.retry });
43
+ }
44
+ function setActiveFaultAnchor(nodeId, anchorEntryId) {
45
+ const result = openDb().prepare('UPDATE node_faults SET anchor_entry_id = ? WHERE node_id = ? AND active = 1').run(anchorEntryId, nodeId);
46
+ assert.equal(result.changes, 1, 'the fixture changes exactly one active fault');
47
+ }
48
+ function restoreAdmittedProviderRetryEpisode(nodeId, episode, fault) {
49
+ const stored = recordPendingEpisode(nodeId, {
50
+ sessionId: episodeSessionId(episode),
51
+ input: {
52
+ link: fault.link,
53
+ op: fault.op,
54
+ kind: fault.kind,
55
+ retry: fault.retry,
56
+ message: fault.message,
57
+ since: fault.since,
58
+ operation_id: fault.operation_id,
59
+ ...(fault.anchorEntryId === undefined ? {} : { anchorEntryId: fault.anchorEntryId }),
60
+ ...(fault.providerError === undefined ? {} : { providerError: fault.providerError }),
61
+ },
62
+ });
63
+ assert.ok(stored !== null, 'the bound session accepts a pending recovery episode');
64
+ assert.equal(admitProviderRetryEpisode(nodeId, episode, stored), true, 'the pending recovery episode becomes admitted');
65
+ }
66
+ async function seedPool(provider, cooling) {
67
+ await writeSubscriptionPool(provider, [{
42
68
  label: 'test-account', refresh: 'refresh', access: 'access', expires: Date.now() + 60_000,
43
69
  rateLimitedUntil: cooling ? Date.now() + 60_000 : 0, lastAttemptAt: 0, lastRateLimitedAt: 0,
44
- }]));
70
+ }]);
45
71
  }
46
72
  function modelRoute(spec) {
47
73
  return { ultra: spec, strong: spec, medium: spec, light: spec };
48
74
  }
49
75
  function seedCapacityFallbackRoutes() {
50
- const userConfigDir = join(h.agentHome, '.crouter');
51
- const userConfigPath = join(userConfigDir, 'config.json');
52
- mkdirSync(userConfigDir, { recursive: true });
53
- writeFileSync(userConfigPath, JSON.stringify({
76
+ writeRawConfig('user', {
54
77
  kinds: { 'test/capacity-fallback': { whenToUse: 'Capacity fallback regression.', model: 'anthropic/medium' } },
55
78
  modelRoutes: {
56
79
  primary: { family: 'anthropic', credentialSource: 'user', providerId: 'fake-primary', models: modelRoute('fake-primary/primary') },
@@ -58,8 +81,7 @@ function seedCapacityFallbackRoutes() {
58
81
  largerFallback: { family: 'anthropic', credentialSource: 'third', providerId: 'fake-large', models: modelRoute('fake-large/large') },
59
82
  },
60
83
  modelRouting: { credentialSourceOrder: ['user', 'northlight', 'third'], familyOrder: ['anthropic'], strengthFallback: ['medium'] },
61
- }));
62
- return userConfigPath;
84
+ });
63
85
  }
64
86
  function seedCapacityFixture(id, largeContextWindow = 999) {
65
87
  const dir = join(h.home, 'nodes', id);
@@ -119,10 +141,12 @@ afterEach(() => {
119
141
  });
120
142
  test('a normalized 503 survives into a durable provider fault and re-drives the turn', { timeout: 30_000 }, async () => {
121
143
  const parent = h.spawnRoot('structured provider failure parent');
122
- seedPool('anthropic', false);
123
- seedPool('openai-codex', false);
144
+ await seedPool('anthropic', false);
145
+ await seedPool('openai-codex', false);
124
146
  const child = await h.spawnHeadlessChild(parent, 'structured provider failure', { model: OPENAI_STRONG.spec });
125
147
  await h.waitFor(() => agentStartCount(child) >= 1, { label: 'initial structured-failure fixture turn started' });
148
+ const kickoffObserver = await attachUntil(child, 'observer', 'structured-503-kickoff-settled', (a) => a.welcome.snapshot.state.isStreaming === false, 'structured 503 kickoff settled');
149
+ kickoffObserver.close();
126
150
  const baselineStarts = agentStartCount(child);
127
151
  h.fakeCmd(child, {
128
152
  cmd: 'stop',
@@ -146,8 +170,8 @@ test('a managed-cooling terminal authors a rate-limit auto fault whose deadline
146
170
  const parent = h.spawnRoot('cooling terminal parent');
147
171
  // The terminal fault path is pool-independent — the stophook classifies purely
148
172
  // off the errorMessage text + the cooling diagnostic, so pool state is inert.
149
- seedPool('anthropic', false);
150
- seedPool('openai-codex', false);
173
+ await seedPool('anthropic', false);
174
+ await seedPool('openai-codex', false);
151
175
  const child = await h.spawnHeadlessChild(parent, 'cooling terminal', { model: OPENAI_STRONG.spec });
152
176
  await h.waitFor(() => agentStartCount(child) >= 1, { label: 'initial cooling fixture turn started' });
153
177
  const kickoffObserver = await attachUntil(child, 'observer', 'cooling-terminal-kickoff-settled', (a) => a.welcome.snapshot.state.isStreaming === false, 'cooling terminal kickoff settled');
@@ -187,8 +211,8 @@ test('a managed-cooling terminal authors a rate-limit auto fault whose deadline
187
211
  });
188
212
  test('a crashed broker re-drives a pending episode and a no-progress admitted one, never a progressed or invalidated episode', { timeout: 90_000 }, async () => {
189
213
  const parent = h.spawnRoot('durable provider episode parent');
190
- seedPool('anthropic', false);
191
- seedPool('openai-codex', false);
214
+ await seedPool('anthropic', false);
215
+ await seedPool('openai-codex', false);
192
216
  const pendingNode = await h.spawnHeadlessChild(parent, 'pending provider episode', { model: OPENAI_STRONG.spec });
193
217
  await h.waitFor(() => agentStartCount(pendingNode) >= 1, { label: 'pending episode fixture started' });
194
218
  const pendingKickoffObserver = await attachUntil(pendingNode, 'observer', 'pending-provider-kickoff-settled', (a) => a.welcome.snapshot.state.isStreaming === false, 'pending provider episode kickoff settled');
@@ -198,7 +222,7 @@ test('a crashed broker re-drives a pending episode and a no-progress admitted on
198
222
  cmd: 'stop', reason: 'error', errorMessage: 'Managed provider pools are temporarily unavailable.', deadline: Date.now() + 2_000,
199
223
  });
200
224
  const pending = await h.waitFor(() => readProviderRetryEpisode(pendingNode)?.state === 'pending' ? readProviderRetryEpisode(pendingNode) : null, { label: 'pending provider episode persisted before crash' });
201
- assert.equal(pending.sessionFile, realpathSync(h.node(pendingNode).pi_session_file), 'the pending episode is tied to its transcript path');
225
+ assert.equal(pending.sessionId, h.node(pendingNode).pi_session_id, 'the pending episode is tied to its bound session');
202
226
  await crashAndRespawn(pendingNode, h.bootCount(pendingNode));
203
227
  await h.waitFor(() => agentStartCount(pendingNode) >= pendingStarts + 1, { label: 'replacement re-drove the pending episode' });
204
228
  await new Promise((resolve) => setTimeout(resolve, 300));
@@ -227,7 +251,7 @@ test('a crashed broker re-drives a pending episode and a no-progress admitted on
227
251
  // already elapsed (see the design's "effectively zero delay" note), so this
228
252
  // proves the re-drive without a real ~60s wait.
229
253
  const admittedFaultForEpisode = { ...admittedFault, retry: { ...admittedFault.retry, nextAt: new Date(Date.now() - 1_000).toISOString() } };
230
- assert.equal(admitProviderRetryEpisode(admittedNode, admittedPending.sessionFile, admittedFaultForEpisode), true, 'the dispatch boundary durably admits the matching episode');
254
+ assert.equal(admitProviderRetryEpisode(admittedNode, admittedPending, admittedFaultForEpisode), true, 'the dispatch boundary durably admits the matching episode');
231
255
  assert.equal(readProviderRetryEpisode(admittedNode)?.state, 'admitted');
232
256
  await crashAndRespawn(admittedNode, h.bootCount(admittedNode));
233
257
  await h.waitFor(() => agentStartCount(admittedNode) >= admittedStarts + 1, { label: 'replacement re-drove the no-progress admitted episode' });
@@ -269,7 +293,7 @@ test('a crashed broker re-drives a pending episode and a no-progress admitted on
269
293
  const progressPending = await h.waitFor(() => readProviderRetryEpisode(progressNode)?.state === 'pending' ? readProviderRetryEpisode(progressNode) : null, { label: 'progress episode pending record persisted' });
270
294
  const progressFault = readFault(progressNode);
271
295
  assert.ok(progressFault !== null, 'the pending episode retains its retry fault before admission');
272
- assert.equal(admitProviderRetryEpisode(progressNode, progressPending.sessionFile, progressFault), true, 'the dispatch boundary durably admits the matching episode');
296
+ assert.equal(admitProviderRetryEpisode(progressNode, progressPending, progressFault), true, 'the dispatch boundary durably admits the matching episode');
273
297
  assert.equal(readProviderRetryEpisode(progressNode)?.state, 'admitted');
274
298
  // A real successful turn past the anchor: proves the dead attempt's slot is
275
299
  // no longer the branch tail, so recoverOnStartup's no-progress proof must
@@ -278,16 +302,12 @@ test('a crashed broker re-drives a pending episode and a no-progress admitted on
278
302
  progressController.send({ type: 'prompt', text: `progress past anchor ${Date.now()}` });
279
303
  await progressController.waitFrame((f) => f.type === 'agent_end', 'the progress turn settles');
280
304
  progressController.close();
281
- // The natural settle above already clears the episode (any non-error settle
282
- // clears it unconditionally) — expected, and orthogonal to what this half
283
- // proves. Restore `admitted` with the SAME original anchor/fault by writing
284
- // the job-dir file directly, bypassing the guarded API (mirrors the
285
- // `reload_auth leaves a parked auth fault` precedent above in this file), so
286
- // the crash below tests the no-progress proof against a genuine post-anchor
287
- // assistant turn instead of an unreliable real-crash race.
305
+ // The natural settle above clears the episode. Restore the same admitted SQL
306
+ // record so the crash below tests the no-progress proof against a genuine
307
+ // post-anchor assistant turn rather than an unreliable real-crash race.
288
308
  await h.waitFor(() => readProviderRetryEpisode(progressNode) === null, { label: 'the settled turn cleared the episode' });
289
- writeFileSync(join(h.home, 'nodes', progressNode, 'job', 'provider-retry'), JSON.stringify({ state: 'admitted', sessionFile: progressPending.sessionFile, anchorEntryId: progressPending.anchorEntryId, fault: progressFault }));
290
- assert.equal(readProviderRetryEpisode(progressNode)?.state, 'admitted', 'the direct write restores the admitted episode for the crash below');
309
+ restoreAdmittedProviderRetryEpisode(progressNode, progressPending, progressFault);
310
+ assert.equal(readProviderRetryEpisode(progressNode)?.state, 'admitted', 'the SQL fixture restores the admitted episode for the crash below');
291
311
  const progressStartsBeforeCrash = agentStartCount(progressNode);
292
312
  await crashAndRespawn(progressNode, h.bootCount(progressNode));
293
313
  await new Promise((resolve) => setTimeout(resolve, 300));
@@ -343,8 +363,8 @@ test('a crashed broker re-drives a pending episode and a no-progress admitted on
343
363
  });
344
364
  test('reload_auth re-drives a parked fatal provider-auth turn exactly once', { timeout: 30_000 }, async () => {
345
365
  const parent = h.spawnRoot('auth reload parent');
346
- seedPool('anthropic', false);
347
- seedPool('openai-codex', false);
366
+ await seedPool('anthropic', false);
367
+ await seedPool('openai-codex', false);
348
368
  const child = await h.spawnHeadlessChild(parent, 'auth reload child', { model: OPENAI_STRONG.spec });
349
369
  await h.waitFor(() => agentStartCount(child) >= 1, { label: 'initial auth fixture turn started' });
350
370
  const baselineStarts = agentStartCount(child);
@@ -373,8 +393,8 @@ test('reload_auth re-drives a parked fatal provider-auth turn exactly once', { t
373
393
  });
374
394
  test('reload_auth leaves a parked auth fault with its original session when navigation is overtaken by replacement', { timeout: 30_000 }, async () => {
375
395
  const parent = h.spawnRoot('auth replacement parent');
376
- seedPool('anthropic', false);
377
- seedPool('openai-codex', false);
396
+ await seedPool('anthropic', false);
397
+ await seedPool('openai-codex', false);
378
398
  const child = await h.spawnHeadlessChild(parent, 'auth replacement child', { model: OPENAI_STRONG.spec });
379
399
  await h.waitFor(() => agentStartCount(child) >= 1, { label: 'initial replacement fixture turn started' });
380
400
  h.fakeCmd(child, {
@@ -391,10 +411,7 @@ test('reload_auth leaves a parked auth fault with its original session when navi
391
411
  // Force the redrive's leaf/anchor comparison to diverge so it actually enters
392
412
  // navigateTree (a matching leaf/anchor legitimately skips the rewind) — the
393
413
  // fixture's fault otherwise anchors to the leaf already current.
394
- const faultFilePath = join(nodeDir, 'job', 'fault');
395
- const rawFault = JSON.parse(readFileSync(faultFilePath, 'utf8'));
396
- rawFault['anchorEntryId'] = 'auth-replacement-anchor-mismatch';
397
- writeFileSync(faultFilePath, JSON.stringify(rawFault));
414
+ setActiveFaultAnchor(child, 'auth-replacement-anchor-mismatch');
398
415
  writeFileSync(join(nodeDir, 'fake-pi.navigate-hold'), '');
399
416
  const controller = await kit.attach(child, 'controller', 'auth-replacement-test');
400
417
  controller.send({ type: 'reload_auth' });
@@ -412,8 +429,8 @@ test('reload_auth leaves a parked auth fault with its original session when navi
412
429
  // definitive failure make the episode terminal.
413
430
  test('a recovered compact-and-retry commits nothing, while a definitive failure publishes one structured overflow fault and terminal notice', { timeout: 30_000 }, async () => {
414
431
  const parent = h.spawnRoot('overflow parent');
415
- seedPool('anthropic', false);
416
- seedPool('openai-codex', false);
432
+ await seedPool('anthropic', false);
433
+ await seedPool('openai-codex', false);
417
434
  const child = await h.spawnHeadlessChild(parent, 'overflow child', { model: OPENAI_STRONG.spec });
418
435
  h.fakeCmd(child, { cmd: 'overflow', outcome: 'recover' });
419
436
  await h.waitFor(() => overflowOutcome(child) === 'recover' ? true : null, { label: 'recovered overflow reached agent_settled' });
@@ -437,18 +454,21 @@ test('a recovered compact-and-retry commits nothing, while a definitive failure
437
454
  assert.equal(autoFault.kind, 'rate-limit', 'ordinary settled provider failures retain broker-owned retry behavior');
438
455
  h.fakeCmd(child, { cmd: 'overflow', outcome: 'fail' });
439
456
  await h.waitFor(() => overflowOutcome(child) === 'fail' ? true : null, { label: 'failed overflow reached agent_settled' });
440
- const fault = readFault(child);
441
- assert.equal(fault?.kind, 'context-overflow');
442
- assert.equal(fault?.op, 'context overflow recovery');
443
- assert.equal(fault?.retry.disposition, 'fatal');
444
- assert.match(fault?.message ?? '', /failed after one compact-and-retry attempt/i);
457
+ const fault = await h.waitFor(() => {
458
+ const current = readFault(child);
459
+ return current?.kind === 'context-overflow' ? current : null;
460
+ }, { label: 'failed overflow publishes its definitive context-overflow fault' });
461
+ assert.equal(fault.kind, 'context-overflow');
462
+ assert.equal(fault.op, 'context overflow recovery');
463
+ assert.equal(fault.retry.disposition, 'fatal');
464
+ assert.match(fault.message, /failed after one compact-and-retry attempt/i);
445
465
  await h.tick();
446
466
  assert.equal(h.status(child), 'dead', 'the daemon applies one terminal child transition');
447
467
  const notices = h.inbox(parent).filter((entry) => /exceeded its model context window/i.test(entry.label));
448
468
  assert.equal(notices.length, 1, 'the parent receives exactly one structured overflow notice');
449
469
  });
450
470
  test('provider-retry and model-not-found fallback both terminate when every available target has a smaller known context window', { timeout: 30_000 }, async () => {
451
- const userConfigPath = seedCapacityFallbackRoutes();
471
+ seedCapacityFallbackRoutes();
452
472
  try {
453
473
  const parent = h.spawnRoot('capacity fallback parent');
454
474
  const providerRetry = await h.spawnHeadlessChild(parent, 'provider retry capacity fallback', { kind: 'test/capacity-fallback' });
@@ -474,7 +494,7 @@ test('provider-retry and model-not-found fallback both terminate when every avai
474
494
  assert.match(modelNotFoundFault.message, /1000 tokens/, 'the terminal fault survives ordinary settlement staging');
475
495
  }
476
496
  finally {
477
- rmSync(userConfigPath, { force: true });
497
+ writeRawConfig('user', {});
478
498
  }
479
499
  });
480
500
  // Two real logical launches keep both user paths non-vacuous: the picker first
@@ -482,14 +502,11 @@ test('provider-retry and model-not-found fallback both terminate when every avai
482
502
  // directly against still-present logical intent before Shift+Tab and replay.
483
503
  test("picker and Alt+M choices each clear logical routing, and Alt+M / Shift+Tab become the durable restart recipe", { timeout: 30_000 }, async () => {
484
504
  const parent = h.spawnRoot('model durability parent');
485
- seedPool('anthropic', false);
486
- seedPool('openai-codex', false);
487
- const userConfigDir = join(h.agentHome, '.crouter');
488
- const userConfigPath = join(userConfigDir, 'config.json');
489
- mkdirSync(userConfigDir, { recursive: true });
490
- writeFileSync(userConfigPath, JSON.stringify({
505
+ await seedPool('anthropic', false);
506
+ await seedPool('openai-codex', false);
507
+ writeRawConfig('user', {
491
508
  kinds: { 'test/logical-openai': { whenToUse: 'Logical OpenAI routing test.', model: 'openai/strong' } },
492
- }));
509
+ });
493
510
  const pickerNode = await h.spawnHeadlessChild(parent, 'picker durability child', { kind: 'test/logical-openai' });
494
511
  const pickerBefore = h.node(pickerNode);
495
512
  assert.deepEqual(pickerBefore.launch?.modelIntent, { family: 'openai', strength: 'strong' }, 'picker fixture starts from a logical launch preference');
@@ -541,17 +558,14 @@ test("picker and Alt+M choices each clear logical routing, and Alt+M / Shift+Tab
541
558
  assert.equal(replay.argv[modelArg + 1], expected, 'the replayed model argument carries both choices');
542
559
  assert.equal(replay.env.CRTR_MODEL_INTENT, undefined, 'the stale logical intent is absent from the restarted broker environment');
543
560
  ladderCtrl.close();
544
- rmSync(userConfigPath, { force: true });
561
+ writeRawConfig('user', {});
545
562
  });
546
563
  test('reload_auth restores a newly authenticated personal route before the next idle prompt', { timeout: 30_000 }, async () => {
547
564
  const personal = { provider: 'personal-anthropic', id: 'claude-personal' };
548
565
  const funded = { provider: 'northlight-openai', id: 'gpt-funded' };
549
566
  const personalSpec = `${personal.provider}/${personal.id}:xhigh`;
550
- const userConfigDir = join(h.agentHome, '.crouter');
551
- const userConfigPath = join(userConfigDir, 'config.json');
552
567
  const unauthenticatedProvider = join(h.home, 'fake-pi.unauthenticated-provider');
553
- mkdirSync(userConfigDir, { recursive: true });
554
- writeFileSync(userConfigPath, JSON.stringify({
568
+ writeRawConfig('user', {
555
569
  // A fixture-private kind, never `general`: the child launches with the
556
570
  // package root as cwd, so an ambient project-scope config above the repo
557
571
  // (`~/.crouter/config.json`) overrides this user-scope `general` and the
@@ -562,7 +576,7 @@ test('reload_auth restores a newly authenticated personal route before the next
562
576
  funded: { family: 'anthropic', credentialSource: 'northlight', providerId: funded.provider, models: { ultra: `${funded.provider}/${funded.id}:medium`, strong: `${funded.provider}/${funded.id}:medium`, medium: `${funded.provider}/${funded.id}:medium`, light: `${funded.provider}/${funded.id}:medium` } },
563
577
  },
564
578
  modelRouting: { credentialSourceOrder: ['user', 'northlight'], familyOrder: ['anthropic'], strengthFallback: ['medium'] },
565
- }));
579
+ });
566
580
  writeFileSync(unauthenticatedProvider, personal.provider);
567
581
  try {
568
582
  const parent = h.spawnRoot('auth route reconciliation parent');
@@ -571,6 +585,9 @@ test('reload_auth restores a newly authenticated personal route before the next
571
585
  const controller = await kit.connectRaw(child, { read: true });
572
586
  controller.send({ type: 'hello', role: 'controller', client_id: 'auth-route-reconciliation' });
573
587
  await h.waitFor(() => controller.frames.some((frame) => frame.type === 'welcome') ? true : null, { label: 'route reconciliation controller welcomed' });
588
+ await h.waitFor(() => agentStartCount(child) >= 1, { label: 'route reconciliation kickoff started' });
589
+ const kickoffObserver = await attachUntil(child, 'observer', 'route-reconciliation-kickoff-settled', (a) => a.welcome.snapshot.state.isStreaming === false, 'route reconciliation kickoff settled');
590
+ kickoffObserver.close();
574
591
  rmSync(unauthenticatedProvider, { force: true });
575
592
  const frameStart = controller.frames.length;
576
593
  controller.writeRaw(encodeFrame({ type: 'reload_auth' })
@@ -595,7 +612,7 @@ test('reload_auth restores a newly authenticated personal route before the next
595
612
  }
596
613
  finally {
597
614
  rmSync(unauthenticatedProvider, { force: true });
598
- rmSync(userConfigPath, { force: true });
615
+ writeRawConfig('user', {});
599
616
  }
600
617
  });
601
618
  // MODEL-LESS LAUNCH RE-DRIVE — MUST REMAIN THE LAST TEST IN THIS FILE. Its
@@ -629,12 +646,9 @@ test('reload_auth restores a newly authenticated personal route before the next
629
646
  // subscription-pool occupancy (broker-sdk-wiring's scope).
630
647
  test('login re-drive → list_models self-heals (0a) and reload_auth resolves a model + fires the deferred kickoff', { timeout: 30_000 }, async () => {
631
648
  const noAuthSentinel = join(h.home, 'fake-pi.no-auth');
632
- const userConfigDir = join(h.agentHome, '.crouter');
633
- const userConfigPath = join(userConfigDir, 'config.json');
634
- mkdirSync(userConfigDir, { recursive: true });
635
- writeFileSync(userConfigPath, JSON.stringify({
649
+ writeRawConfig('user', {
636
650
  kinds: { general: { whenToUse: 'General work.', model: 'anthropic/medium' } },
637
- }));
651
+ });
638
652
  writeFileSync(noAuthSentinel, '1');
639
653
  try {
640
654
  const parent = h.spawnRoot('login re-drive parent');
@@ -663,6 +677,6 @@ test('login re-drive → list_models self-heals (0a) and reload_auth resolves a
663
677
  }
664
678
  finally {
665
679
  rmSync(noAuthSentinel, { force: true });
666
- rmSync(userConfigPath, { force: true });
680
+ writeRawConfig('user', {});
667
681
  }
668
682
  });
@@ -4,8 +4,8 @@
4
4
  // acting root:
5
5
  //
6
6
  // (1) Issue #222/#353 — a real idle-release cycle must settle the inbox watcher
7
- // before the broker exits, and the daemon may only revive for a row strictly
8
- // after the committed cursor.
7
+ // before the broker exits, and the daemon may only revive for a later
8
+ // pending SQL delivery.
9
9
  // (2) The observed 57-broker / ~5.4GB leak — DAEMON SUPERVISION, not an agent
10
10
  // event, owns the unattended clock. A node with no live obligation is
11
11
  // terminalized into prunable history (a resident completed its
@@ -21,7 +21,8 @@ import { after, before, test } from 'node:test';
21
21
  import assert from 'node:assert/strict';
22
22
  import { existsSync, readFileSync, writeFileSync } from 'node:fs';
23
23
  import { join } from 'node:path';
24
- import { appendInbox, readCursor, readInboxSince } from '../../feed/inbox.js';
24
+ import { enqueueNodeMail } from '../../feed/messages.js';
25
+ import { openDb } from '../../canvas/db.js';
25
26
  import { acquireCronRunLease, armCron, setCronState } from '../../canvas/crons.js';
26
27
  import { registerWarmSpare, subscribe, subscriptionsOf, updateNode } from '../../canvas/index.js';
27
28
  import { isPidAlive } from '../../canvas/pid.js';
@@ -31,6 +32,17 @@ import { createAttachKit } from '../helpers/broker-clients.js';
31
32
  let h;
32
33
  let root;
33
34
  const attach = createAttachKit(() => h);
35
+ function deliveryState(deliveryId) {
36
+ return openDb().prepare('SELECT state FROM deliveries WHERE delivery_id = ?').get(deliveryId)?.state;
37
+ }
38
+ function mailTo(nodeId) {
39
+ return openDb().prepare(`SELECT m.sender_node_id, m.body_json, m.label FROM deliveries d JOIN messages m ON m.message_id = d.message_id
40
+ WHERE d.destination_kind = 'node_inbox' AND d.destination_id = ? ORDER BY m.seq`).all(nodeId).map((row) => ({
41
+ sender: row.sender_node_id,
42
+ body: row.body_json === null ? null : JSON.parse(row.body_json),
43
+ label: row.label,
44
+ }));
45
+ }
34
46
  before(async () => {
35
47
  h = await createHarness({ headless: true, sessionPrefix: 'crtr-dormancy' });
36
48
  root = h.spawnRoot('dormancy-release suite root');
@@ -105,8 +117,8 @@ test('idle-release settles its real watcher before exit; daemon revives exactly
105
117
  // run in-process. Keeping this compiled seam at production cadence would add
106
118
  // multiple seconds of polling around two real broker boots, so it uses the
107
119
  // same fast cadence without weakening what it proves: the settlement
108
- // lifecycle, cursor semantics, and hasPendingMessages veto are all
109
- // independent of how fast the watcher polls.
120
+ // lifecycle, delivery acknowledgement semantics, and hasPendingMessages veto
121
+ // are all independent of how fast the watcher polls.
110
122
  const manager = h.fabricateBrokerNode({ id: 'settle-veto-manager', status: 'idle', parent: root });
111
123
  updateNode(manager, {
112
124
  launch: {
@@ -127,12 +139,13 @@ test('idle-release settles its real watcher before exit; daemon revives exactly
127
139
  // awaiting, so its real stophook takes the idle-release branch.
128
140
  const worker = h.fabricateBrokerNode({ parent: manager, status: 'idle', intent: 'idle-release' });
129
141
  subscribe(manager, worker, true);
130
- const first = appendInbox(manager, {
131
- from: 'worker-a',
132
- tier: 'normal',
142
+ const first = enqueueNodeMail(manager, {
143
+ senderNodeId: 'worker-a',
133
144
  kind: 'final',
134
145
  label: 'first completed',
135
- data: { body: 'first completed' },
146
+ bodyJson: { body: 'first completed' },
147
+ urgency: 'normal',
148
+ wake: 1,
136
149
  });
137
150
  await h.awaitWake(manager, { match: /first completed/ });
138
151
  // The real watcher injects an idle message, whose fake session now starts and
@@ -140,27 +153,28 @@ test('idle-release settles its real watcher before exit; daemon revives exactly
140
153
  // broker exit. No test-driven turn or settlement dispatch is involved.
141
154
  await awaitReleased(manager);
142
155
  await h.awaitFleetExit(manager);
143
- assert.equal(readCursor(manager), first.entry_id, 'the exact handed-off row commits before clean shutdown');
156
+ assert.equal(deliveryState(first.deliveryId), 'acknowledged', 'the exact handed-off SQL delivery is acknowledged before clean shutdown');
144
157
  const cyclesAfterRelease = h.node(manager).cycles ?? 0;
145
158
  await h.tick();
146
159
  assert.equal(h.node(manager).cycles ?? 0, cyclesAfterRelease, 'a second supervision tick does not revive a settled row');
147
160
  const injectedBeforeSecond = h.injected(manager).length;
148
- const second = appendInbox(manager, {
149
- from: 'worker-b',
150
- tier: 'normal',
161
+ const second = enqueueNodeMail(manager, {
162
+ senderNodeId: 'worker-b',
151
163
  kind: 'final',
152
164
  label: 'second completed',
153
- data: { body: 'second completed' },
165
+ bodyJson: { body: 'second completed' },
166
+ urgency: 'normal',
167
+ wake: 1,
154
168
  });
155
- assert.ok(readInboxSince(manager, first.entry_id).some((entry) => entry.entry_id === second.entry_id), 'the next row is physically after the committed cursor');
156
- assert.equal(readInboxSince(manager, second.entry_id).length, 0, 'the physical cursor boundary is exclusive');
169
+ assert.equal(deliveryState(first.deliveryId), 'acknowledged', 'the first receipt remains terminal');
170
+ assert.equal(deliveryState(second.deliveryId), 'pending', 'the later delivery is independently pending');
157
171
  await h.tick();
158
172
  assert.equal(h.node(manager).cycles ?? 0, cyclesAfterRelease + 1, 'one later row revives once');
159
173
  await h.awaitBoot(manager, { minCount: 2 });
160
174
  await h.awaitWake(manager, { sinceCount: injectedBeforeSecond, match: /second completed/ });
161
175
  await awaitReleased(manager);
162
176
  await h.awaitFleetExit(manager);
163
- assert.equal(readCursor(manager), second.entry_id, 'the later row commits at its own natural idle release');
177
+ assert.equal(deliveryState(second.deliveryId), 'acknowledged', 'the later delivery is acknowledged at its own natural idle release');
164
178
  await h.tick();
165
179
  assert.equal(h.node(manager).cycles ?? 0, cyclesAfterRelease + 1, 'the settled later row does not cause a duplicate revival');
166
180
  });
@@ -177,7 +191,7 @@ test('an already idle resident with no live obligation reconciles into completed
177
191
  await h.tick();
178
192
  assert.equal(h.node(nodeId)?.status, 'done', 'an older idle row becomes pruning-eligible without needing a broker revive');
179
193
  assert.equal(h.node(nodeId)?.intent, 'parked', 'the reconciled row carries the durable park marker (isParked)');
180
- const wake = readInboxSince(root).find((entry) => entry.from === nodeId && entry.data?.['reason'] === 'child-auto-done');
194
+ const wake = mailTo(root).find((entry) => entry.sender === nodeId && entry.body?.['reason'] === 'child-auto-done');
181
195
  assert.ok(wake, 'the subscribing root learns that the parked resident completed without a final report');
182
196
  });
183
197
  test('an unattended resident with no live obligation completes on the daemon clock', { timeout: 30_000 }, async () => {
@@ -235,7 +249,7 @@ test('an unattended resident with no live obligation completes on the daemon clo
235
249
  await h.awaitFleetExit(nodeId);
236
250
  assert.equal(h.fleet.has(nodeId), false, 'exit policy forgets the parked broker');
237
251
  assert.equal(h.node(nodeId)?.status, 'done', 'the row is terminal and eligible for history pruning');
238
- const wake = readInboxSince(root).find((entry) => entry.from === nodeId && entry.data?.['reason'] === 'child-auto-done');
252
+ const wake = mailTo(root).find((entry) => entry.sender === nodeId && entry.body?.['reason'] === 'child-auto-done');
239
253
  assert.ok(wake, 'the subscribing root is woken when the unattended clock completes the resident');
240
254
  assert.match(wake.label, /submitted no final report/, 'the wake distinguishes automatic completion from a final report');
241
255
  await h.tick();
@@ -7,7 +7,8 @@ import { after, before, test } from 'node:test';
7
7
  import assert from 'node:assert/strict';
8
8
  import { existsSync, readFileSync } from 'node:fs';
9
9
  import { join } from 'node:path';
10
- import { appendInbox, readCursor } from '../../feed/inbox.js';
10
+ import { enqueueNodeMail } from '../../feed/messages.js';
11
+ import { acknowledgedNodeMailCount } from '../../canvas/deliveries.js';
11
12
  import { updateNode } from '../../canvas/index.js';
12
13
  import { reviveNode } from '../../runtime/revive.js';
13
14
  import { createHeadlessHarness } from '../helpers/harness.js';
@@ -44,11 +45,12 @@ test('a Jiti-loaded watcher prepends its held deferred batch to a native broker
44
45
  });
45
46
  await reviveNode(nodeId, { resume: true, capacity: 'freeze' });
46
47
  await h.awaitBoot(nodeId);
47
- const entry = appendInbox(nodeId, {
48
- from: 'child',
49
- tier: 'deferred',
48
+ enqueueNodeMail(nodeId, {
49
+ senderNodeId: 'child',
50
50
  kind: 'update',
51
51
  label: 'held Jiti deferred update',
52
+ urgency: 'deferred',
53
+ wake: 1,
52
54
  });
53
55
  await delay(150);
54
56
  const customPath = join(h.home, 'nodes', nodeId, 'fake-pi.custom-messages.jsonl');
@@ -59,5 +61,5 @@ test('a Jiti-loaded watcher prepends its held deferred batch to a native broker
59
61
  await controller.waitFrame((frame) => frame.type === 'agent_end' && frameHas(frame, prompt), 'the human prompt turn');
60
62
  const customMessages = readFileSync(customPath, 'utf8');
61
63
  assert.match(customMessages, /held Jiti deferred update/, 'the Jiti extension instance joined its held batch before the native broker prompt');
62
- assert.equal(readCursor(nodeId), entry.entry_id, 'agent_settled commits the joined handoff');
64
+ assert.equal(acknowledgedNodeMailCount(nodeId), 1, 'agent_settled acknowledges the joined SQL delivery');
63
65
  });
@@ -7,8 +7,11 @@ import { after, before, test } from 'node:test';
7
7
  import assert from 'node:assert/strict';
8
8
  import { readFileSync } from 'node:fs';
9
9
  import { subscribe } from '../../canvas/index.js';
10
+ import { claimNodeMail } from '../../canvas/deliveries.js';
11
+ import { beginExecution } from '../../canvas/executions.js';
10
12
  import { resolveRef } from '../../canvas/history.js';
11
- import { appendInbox, coalesce, readInboxSince } from '../../feed/inbox.js';
13
+ import { coalesce, nodeMailEntry } from '../../feed/inbox.js';
14
+ import { enqueueNodeMail } from '../../feed/messages.js';
12
15
  import { pushUpdate } from '../../feed/feed.js';
13
16
  import { contextDirNote } from '../../runtime/bearings-render.js';
14
17
  import { coalesceBrokerInbox } from '../../runtime/broker/inbox.js';
@@ -16,15 +19,24 @@ import { createHeadlessHarness } from '../helpers/harness.js';
16
19
  let h;
17
20
  let root;
18
21
  let recipient;
22
+ let recipientExecution;
19
23
  before(async () => {
20
24
  h = await createHeadlessHarness({ sessionPrefix: 'crtr-inbox-reference-guidance' });
21
25
  root = h.spawnRoot('inbox reference guidance seam root');
22
26
  recipient = h.fabricateBrokerNode({ id: 'inbox-reference-recipient', parent: root, status: 'idle' });
27
+ recipientExecution = beginExecution(recipient, { daemonEpoch: h.fleet.epoch() });
23
28
  });
24
29
  after(async () => {
25
30
  if (h !== undefined)
26
31
  await h.dispose();
27
32
  });
33
+ function latestEntry() {
34
+ const claim = claimNodeMail(recipient, recipientExecution, 'wake');
35
+ assert.equal(claim.action, 'claimed');
36
+ const delivery = claim.deliveries.at(-1);
37
+ assert.ok(delivery, 'the newly enqueued delivery is handed to the recipient execution');
38
+ return nodeMailEntry(delivery);
39
+ }
28
40
  function onlyCard(entries) {
29
41
  const deliveries = coalesce(entries);
30
42
  assert.equal(deliveries.length, 1, 'one non-human inbox entry produces one card');
@@ -36,7 +48,7 @@ test('delivered report and message refs name the exact way to read their bodies'
36
48
  subscribe(recipient, publisher, true);
37
49
  const reportBody = 'published report body';
38
50
  await pushUpdate(publisher, reportBody);
39
- const reportEntry = readInboxSince(recipient).at(-1);
51
+ const reportEntry = latestEntry();
40
52
  assert.equal(reportEntry.ref?.startsWith(`${publisher}:reports/`), true, 'delivery carries the canonical report handle');
41
53
  const reportRef = reportEntry.ref;
42
54
  const reportCard = onlyCard([reportEntry]);
@@ -49,13 +61,15 @@ test('delivered report and message refs name the exact way to read their bodies'
49
61
  'a'.repeat(1001),
50
62
  Array.from({ length: 13 }, (_, i) => `line ${i + 1}`).join('\n'),
51
63
  ]) {
52
- const message = appendInbox(recipient, {
53
- from: publisher,
54
- tier: 'normal',
64
+ enqueueNodeMail(recipient, {
65
+ senderNodeId: publisher,
66
+ urgency: 'normal',
67
+ wake: 1,
55
68
  kind: 'message',
56
69
  label: 'long message',
57
- data: { body },
70
+ bodyJson: { body },
58
71
  });
72
+ const message = latestEntry();
59
73
  assert.ok(message.ref?.startsWith('/'), 'a clipped message stores its full body at an absolute recipient path');
60
74
  const messageCard = onlyCard([message]);
61
75
  const brokerMessageCard = coalesceBrokerInbox([message], new Map())[0].text;
@@ -8,11 +8,35 @@ import assert from 'node:assert/strict';
8
8
  import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
9
9
  import { join } from 'node:path';
10
10
  import { closeDb } from '../../canvas/db.js';
11
- import { readOutcomeDelivery } from '../../canvas/node-outcome-deliveries.js';
11
+ import { messageBodyForDelivery, readExternalActionDelivery } from '../../canvas/deliveries.js';
12
12
  import { createHeadlessHarness } from '../helpers/harness.js';
13
13
  let h;
14
14
  let root;
15
15
  let project;
16
+ function outcomeDelivery(nodeId) {
17
+ const row = readExternalActionDelivery(`node-outcome:${nodeId}`);
18
+ if (row === null)
19
+ return null;
20
+ const adapter = row.adapter;
21
+ if (adapter?.adapter !== 'subprocess' || !Array.isArray(adapter.argv) || !adapter.argv.every((entry) => typeof entry === 'string') || typeof adapter.cwd !== 'string')
22
+ throw new Error(`outcome delivery ${row.deliveryId} has an invalid subprocess adapter`);
23
+ if (row.state !== 'armed' && row.state !== 'pending' && row.state !== 'handed_off' && row.state !== 'acknowledged' && row.state !== 'permanent_failed')
24
+ throw new Error(`outcome delivery ${row.deliveryId} has an invalid state`);
25
+ return {
26
+ state: row.state === 'handed_off' ? 'running' : row.state === 'acknowledged' ? 'accepted' : row.state,
27
+ actionName: row.destinationId,
28
+ argv: adapter.argv,
29
+ cwd: adapter.cwd,
30
+ configOrigin: typeof adapter.config_origin === 'string' ? adapter.config_origin : '',
31
+ payload: adapter.payload ?? null,
32
+ document: row.messageId === null ? null : messageBodyForDelivery(row.deliveryId),
33
+ attempt: row.attempts,
34
+ nextAttemptAt: row.nextAttemptAt ?? 0,
35
+ acceptedAt: row.acknowledgedAt,
36
+ permanentFailedAt: row.failedAt,
37
+ lastFailure: row.lastFailure,
38
+ };
39
+ }
16
40
  function assertKeys(value, expected, label) {
17
41
  assert.deepEqual(Object.keys(value).sort(), [...expected].sort(), `${label} has the pinned document fields`);
18
42
  }
@@ -54,7 +78,7 @@ function assertResultDocument(document, nodeId, action, payloadJson, attempt) {
54
78
  function deliveryWhen(nodeId, predicate, label) {
55
79
  return h.waitFor(() => {
56
80
  closeDb();
57
- const delivery = readOutcomeDelivery(nodeId);
81
+ const delivery = outcomeDelivery(nodeId);
58
82
  return delivery !== null && predicate(delivery) ? delivery : null;
59
83
  }, { label, timeoutMs: 20_000 });
60
84
  }
@@ -71,7 +95,7 @@ async function createAndFinalize(action, payload) {
71
95
  const nodeId = createdBody.node_id;
72
96
  await h.awaitBoot(nodeId);
73
97
  closeDb();
74
- const armed = readOutcomeDelivery(nodeId);
98
+ const armed = outcomeDelivery(nodeId);
75
99
  assert.notEqual(armed, null, `node birth arms ${action} delivery`);
76
100
  assert.equal(armed.state, 'armed');
77
101
  assert.equal(armed.actionName, action);