@north-light/crouter 0.3.312 → 0.3.314

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 (497) 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 +757 -746
  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 +13 -1
  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/fresh-home-commands.test.js +49 -0
  121. package/dist/core/__tests__/integration/host-teardown-process-group.test.js +9 -5
  122. package/dist/core/__tests__/integration/lifecycle-hooks.test.js +16 -3
  123. package/dist/core/__tests__/integration/plugin-revalidate.test.js +4 -0
  124. package/dist/core/__tests__/integration/profile-pause.test.js +4 -4
  125. package/dist/core/__tests__/integration/refresh-stall-recycle.test.js +9 -12
  126. package/dist/core/__tests__/integration/revive.test.js +67 -21
  127. package/dist/core/__tests__/integration/spawn-root.test.js +51 -8
  128. package/dist/core/__tests__/integration/subscription-delivery.test.js +33 -21
  129. package/dist/core/__tests__/integration/worktree-land.test.js +5 -2
  130. package/dist/core/__tests__/integration/worktree-reap.test.js +15 -1
  131. package/dist/core/__tests__/kickoff.test.js +36 -39
  132. package/dist/core/__tests__/lifecycle-terminal-reasons.test.js +2 -1
  133. package/dist/core/__tests__/lifecycle.test.js +2 -1
  134. package/dist/core/__tests__/live-lifecycle-prompt.test.js +13 -1
  135. package/dist/core/__tests__/memory-resolver-precedence.test.js +2 -0
  136. package/dist/core/__tests__/migration.test.js +145 -31
  137. package/dist/core/__tests__/model-pin-durability.test.js +4 -1
  138. package/dist/core/__tests__/model-routes-config.test.js +13 -5
  139. package/dist/core/__tests__/node-outcome.test.js +93 -26
  140. package/dist/core/__tests__/on-read-crouter-home-fence.test.js +2 -1
  141. package/dist/core/__tests__/on-read-dedup-resume.test.js +2 -1
  142. package/dist/core/__tests__/on-read-nested-store.test.js +2 -1
  143. package/dist/core/__tests__/passive-subscription.test.js +50 -157
  144. package/dist/core/__tests__/plugin-kinds.test.js +6 -4
  145. package/dist/core/__tests__/plugin-link-symlinked-scope-root.test.js +6 -1
  146. package/dist/core/__tests__/plugin-page-components.test.js +6 -4
  147. package/dist/core/__tests__/preview-registry-sync.test.js +2 -0
  148. package/dist/core/__tests__/profile-project-memory-delivery.test.js +4 -1
  149. package/dist/core/__tests__/prune.test.js +11 -5
  150. package/dist/core/__tests__/push-final-guard.test.js +14 -7
  151. package/dist/core/__tests__/relaunch-root.test.js +30 -2
  152. package/dist/core/__tests__/remote-canvas-target.test.js +7 -1
  153. package/dist/core/__tests__/repository-root-identity.test.js +4 -0
  154. package/dist/core/__tests__/review-model-floor.test.js +6 -3
  155. package/dist/core/__tests__/revive-all.test.js +13 -3
  156. package/dist/core/__tests__/revive-capacity.test.js +26 -4
  157. package/dist/core/__tests__/revive-parked-fresh.test.js +31 -8
  158. package/dist/core/__tests__/seam/broker-attach-stream.test.js +1 -1
  159. package/dist/core/__tests__/seam/broker-cap-freeze.test.js +4 -3
  160. package/dist/core/__tests__/seam/broker-crash-teardown.test.js +11 -14
  161. package/dist/core/__tests__/seam/broker-provider-retry.test.js +85 -71
  162. package/dist/core/__tests__/seam/dormancy-release.test.js +33 -19
  163. package/dist/core/__tests__/seam/held-deferred-human-prompt.test.js +7 -5
  164. package/dist/core/__tests__/seam/inbox-reference-guidance.test.js +20 -6
  165. package/dist/core/__tests__/seam/node-outcome-delivery.test.js +27 -3
  166. package/dist/core/__tests__/spawn-worktree-id-conflict.test.js +2 -1
  167. package/dist/core/__tests__/stop-guard.test.js +2 -1
  168. package/dist/core/__tests__/subscription-pool-credential-guard.test.js +4 -3
  169. package/dist/core/__tests__/warm-claim-preference-snapshot.test.js +18 -4
  170. package/dist/core/__tests__/yield-ensures-daemon.test.js +2 -1
  171. package/dist/core/bootstrap.js +2 -0
  172. package/dist/core/broker-client/__tests__/transport-socket.test.js +51 -6
  173. package/dist/core/broker-client/transport-socket.d.ts +1 -0
  174. package/dist/core/broker-client/transport-socket.js +13 -5
  175. package/dist/core/canvas/__tests__/attention.test.js +29 -35
  176. package/dist/core/canvas/__tests__/history-transcript.test.js +6 -6
  177. package/dist/core/canvas/__tests__/render-remote.test.js +25 -9
  178. package/dist/core/canvas/attention.d.ts +2 -5
  179. package/dist/core/canvas/attention.js +15 -64
  180. package/dist/core/canvas/boot.d.ts +6 -7
  181. package/dist/core/canvas/boot.js +5 -9
  182. package/dist/core/canvas/canvas.d.ts +13 -109
  183. package/dist/core/canvas/canvas.js +140 -401
  184. package/dist/core/canvas/crons.d.ts +2 -0
  185. package/dist/core/canvas/crons.js +16 -15
  186. package/dist/core/canvas/daemon-owner.js +10 -0
  187. package/dist/core/canvas/db.d.ts +2 -2
  188. package/dist/core/canvas/db.js +2 -18
  189. package/dist/core/canvas/deliveries.d.ts +104 -0
  190. package/dist/core/canvas/deliveries.js +354 -0
  191. package/dist/core/canvas/executions.d.ts +29 -0
  192. package/dist/core/canvas/executions.js +68 -0
  193. package/dist/core/canvas/history.js +35 -35
  194. package/dist/core/canvas/index.d.ts +3 -1
  195. package/dist/core/canvas/index.js +3 -1
  196. package/dist/core/canvas/migrations.js +20 -12
  197. package/dist/core/canvas/nav-model.js +1 -3
  198. package/dist/core/canvas/nav-render.d.ts +4 -4
  199. package/dist/core/canvas/nav-render.js +11 -21
  200. package/dist/core/canvas/node-sql-migration.d.ts +3 -0
  201. package/dist/core/canvas/node-sql-migration.js +261 -0
  202. package/dist/core/canvas/paths.d.ts +2 -16
  203. package/dist/core/canvas/paths.js +2 -31
  204. package/dist/core/canvas/recovery.d.ts +76 -0
  205. package/dist/core/canvas/recovery.js +588 -0
  206. package/dist/core/canvas/remote-canvas-source.js +4 -0
  207. package/dist/core/canvas/render-source.d.ts +16 -27
  208. package/dist/core/canvas/render-source.js +16 -45
  209. package/dist/core/canvas/sessions.d.ts +33 -0
  210. package/dist/core/canvas/sessions.js +78 -0
  211. package/dist/core/canvas/types.d.ts +33 -33
  212. package/dist/core/canvas/types.js +2 -5
  213. package/dist/core/canvas/waits.d.ts +18 -0
  214. package/dist/core/canvas/waits.js +70 -0
  215. package/dist/core/canvas/worktrees.d.ts +40 -0
  216. package/dist/core/canvas/worktrees.js +93 -0
  217. package/dist/core/command.js +11 -0
  218. package/dist/core/config.d.ts +17 -12
  219. package/dist/core/config.js +106 -155
  220. package/dist/core/feed/__tests__/inbox-migrate.test.js +27 -31
  221. package/dist/core/feed/feed.d.ts +0 -6
  222. package/dist/core/feed/feed.js +68 -56
  223. package/dist/core/feed/inbox.d.ts +7 -81
  224. package/dist/core/feed/inbox.js +46 -378
  225. package/dist/core/feed/messages.d.ts +58 -0
  226. package/dist/core/feed/messages.js +217 -0
  227. package/dist/core/fs-utils.d.ts +4 -0
  228. package/dist/core/fs-utils.js +21 -1
  229. package/dist/core/human/answer.d.ts +1 -1
  230. package/dist/core/human/answer.js +1 -1
  231. package/dist/core/human/claim.d.ts +1 -1
  232. package/dist/core/human/claim.js +2 -4
  233. package/dist/core/human/component-docs.js +1 -1
  234. package/dist/core/human/feedback-companion.js +3 -2
  235. package/dist/core/human/feedback.d.ts +5 -8
  236. package/dist/core/human/feedback.js +57 -85
  237. package/dist/core/human/page-authoring.d.ts +30 -0
  238. package/dist/core/human/page-authoring.js +19 -0
  239. package/dist/core/human/requests.d.ts +136 -0
  240. package/dist/core/human/requests.js +515 -0
  241. package/dist/core/human/root.d.ts +0 -7
  242. package/dist/core/human/root.js +0 -17
  243. package/dist/core/human/scan.d.ts +2 -7
  244. package/dist/core/human/scan.js +48 -132
  245. package/dist/core/human/summary.d.ts +1 -1
  246. package/dist/core/human/summary.js +1 -1
  247. package/dist/core/human/tickets.d.ts +23 -83
  248. package/dist/core/human/tickets.js +92 -243
  249. package/dist/core/human/types.d.ts +5 -1
  250. package/dist/core/installed-plugins.js +8 -3
  251. package/dist/core/keybindings/__tests__/resolve.test.js +19 -17
  252. package/dist/core/locked-file.d.ts +6 -3
  253. package/dist/core/locked-file.js +36 -9
  254. package/dist/core/memory/extensions.js +2 -0
  255. package/dist/core/preview-registry.js +0 -1
  256. package/dist/core/profiles/__tests__/env-store.test.js +2 -0
  257. package/dist/core/profiles/default-binding.d.ts +0 -4
  258. package/dist/core/profiles/default-binding.js +12 -58
  259. package/dist/core/profiles/env-store.js +8 -3
  260. package/dist/core/profiles/manifest.d.ts +2 -37
  261. package/dist/core/profiles/manifest.js +35 -161
  262. package/dist/core/profiles/select.js +3 -0
  263. package/dist/core/review/__tests__/capture-origin.test.js +13 -2
  264. package/dist/core/review/__tests__/comments.test.js +28 -10
  265. package/dist/core/review/__tests__/remap.test.js +28 -10
  266. package/dist/core/review/__tests__/stage-identity.test.js +17 -7
  267. package/dist/core/review/birth.d.ts +1 -1
  268. package/dist/core/review/birth.js +34 -5
  269. package/dist/core/review/comments.js +1 -1
  270. package/dist/core/review/document.js +1 -1
  271. package/dist/core/review/project.d.ts +5 -1
  272. package/dist/core/review/project.js +14 -36
  273. package/dist/core/review/realize.d.ts +3 -6
  274. package/dist/core/review/realize.js +51 -168
  275. package/dist/core/review/remap.js +1 -1
  276. package/dist/core/review/stage.d.ts +3 -2
  277. package/dist/core/review/stage.js +2 -3
  278. package/dist/core/review/store.d.ts +3 -21
  279. package/dist/core/review/store.js +140 -186
  280. package/dist/core/review/types.d.ts +0 -1
  281. package/dist/core/runtime/__tests__/install-id-cache-scoping.test.js +9 -5
  282. package/dist/core/runtime/__tests__/spawn-env.test.js +8 -9
  283. package/dist/core/runtime/broker/auth-reload.js +9 -10
  284. package/dist/core/runtime/broker/daemon-ops.d.ts +25 -8
  285. package/dist/core/runtime/broker/daemon-ops.js +42 -9
  286. package/dist/core/runtime/broker/extension-abort.d.ts +3 -0
  287. package/dist/core/runtime/broker/extension-abort.js +11 -0
  288. package/dist/core/runtime/broker/fault-retry.d.ts +15 -6
  289. package/dist/core/runtime/broker/fault-retry.js +142 -95
  290. package/dist/core/runtime/broker/inbox.d.ts +4 -32
  291. package/dist/core/runtime/broker/inbox.js +4 -92
  292. package/dist/core/runtime/broker/rebind.d.ts +1 -0
  293. package/dist/core/runtime/broker/rebind.js +21 -12
  294. package/dist/core/runtime/broker-persona-guidance.d.ts +1 -1
  295. package/dist/core/runtime/broker-persona-guidance.js +2 -13
  296. package/dist/core/runtime/broker.js +28 -22
  297. package/dist/core/runtime/close.d.ts +3 -9
  298. package/dist/core/runtime/close.js +16 -27
  299. package/dist/core/runtime/fault.d.ts +3 -93
  300. package/dist/core/runtime/fault.js +9 -419
  301. package/dist/core/runtime/kickoff.d.ts +3 -6
  302. package/dist/core/runtime/kickoff.js +12 -18
  303. package/dist/core/runtime/launch.js +1 -1
  304. package/dist/core/runtime/lifecycle.js +40 -7
  305. package/dist/core/runtime/placement.js +2 -2
  306. package/dist/core/runtime/recycle.js +8 -2
  307. package/dist/core/runtime/reset.js +22 -6
  308. package/dist/core/runtime/revive.js +226 -218
  309. package/dist/core/runtime/spawn.js +14 -3
  310. package/dist/core/scoped-state/db.d.ts +18 -0
  311. package/dist/core/scoped-state/db.js +135 -0
  312. package/dist/core/scoped-state/migrate.d.ts +8 -0
  313. package/dist/core/scoped-state/migrate.js +181 -0
  314. package/dist/core/scoped-state/paths.d.ts +9 -0
  315. package/dist/core/scoped-state/paths.js +27 -0
  316. package/dist/core/scoped-state/profiles.d.ts +12 -0
  317. package/dist/core/scoped-state/profiles.js +62 -0
  318. package/dist/core/scoped-state/providers.d.ts +16 -0
  319. package/dist/core/scoped-state/providers.js +8 -0
  320. package/dist/core/scoped-state/schema.d.ts +3 -0
  321. package/dist/core/scoped-state/schema.js +26 -0
  322. package/dist/core/scoped-state/settings.d.ts +28 -0
  323. package/dist/core/scoped-state/settings.js +82 -0
  324. package/dist/core/subscription-state.d.ts +29 -48
  325. package/dist/core/subscription-state.js +176 -712
  326. package/dist/core/worktree-close.js +4 -4
  327. package/dist/core/worktree-mutation-async.js +2 -2
  328. package/dist/core/worktree-sweep.js +11 -16
  329. package/dist/core/worktree.js +6 -12
  330. package/dist/daemon/__tests__/autostart-storm.test.js +1 -39
  331. package/dist/daemon/__tests__/helpers/source-daemon.d.ts +2 -2
  332. package/dist/daemon/__tests__/helpers/source-daemon.js +7 -6
  333. package/dist/daemon/__tests__/integration/migration-startup.test.js +59 -26
  334. package/dist/daemon/__tests__/node-deadline-lane.test.js +2 -1
  335. package/dist/daemon/__tests__/node-outcome-birth-invariants.test.js +39 -15
  336. package/dist/daemon/__tests__/node-outcome-delivery.test.js +39 -10
  337. package/dist/daemon/__tests__/reconciler-signature.test.js +2 -2
  338. package/dist/daemon/__tests__/subtree-filter-freshness.test.js +2 -2
  339. package/dist/daemon/api/__tests__/broker-settle-park.test.js +17 -12
  340. package/dist/daemon/api/__tests__/canvas-snapshot-fields.test.js +27 -5
  341. package/dist/daemon/api/__tests__/model-config.test.js +8 -8
  342. package/dist/daemon/api/__tests__/node-create-description.test.js +22 -5
  343. package/dist/daemon/api/__tests__/node-model-validation.test.js +21 -1
  344. package/dist/daemon/api/__tests__/nodes-activity-query.test.js +10 -12
  345. package/dist/daemon/api/__tests__/profile-launch-gates.test.js +4 -1
  346. package/dist/daemon/api/__tests__/profile-metadata-route.test.js +4 -1
  347. package/dist/daemon/api/__tests__/reopen-delivery.test.js +26 -8
  348. package/dist/daemon/api/__tests__/seam/api-server.test.js +24 -32
  349. package/dist/daemon/api/__tests__/seam/b10-attach-modelauth.test.js +4 -1
  350. package/dist/daemon/api/__tests__/seam/leaf-api-parity.test.js +5 -4
  351. package/dist/daemon/api/__tests__/seam/profile-delete.test.js +27 -41
  352. package/dist/daemon/api/__tests__/submit-result.test.js +3 -2
  353. package/dist/daemon/api/bridge.js +1 -1
  354. package/dist/daemon/api/handlers/bash-jobs.js +6 -7
  355. package/dist/daemon/api/handlers/broker-ops.js +47 -84
  356. package/dist/daemon/api/handlers/broker-recovery.d.ts +4 -0
  357. package/dist/daemon/api/handlers/broker-recovery.js +251 -0
  358. package/dist/daemon/api/handlers/canvas.js +2 -44
  359. package/dist/daemon/api/handlers/daemon.js +41 -0
  360. package/dist/daemon/api/handlers/feedback-comments.js +2 -2
  361. package/dist/daemon/api/handlers/health.js +10 -5
  362. package/dist/daemon/api/handlers/human-requests.js +168 -188
  363. package/dist/daemon/api/handlers/human.js +3 -151
  364. package/dist/daemon/api/handlers/inbox-page.d.ts +4 -0
  365. package/dist/daemon/api/handlers/inbox-page.js +89 -0
  366. package/dist/daemon/api/handlers/inbox.js +21 -120
  367. package/dist/daemon/api/handlers/messages.d.ts +1 -0
  368. package/dist/daemon/api/handlers/messages.js +73 -15
  369. package/dist/daemon/api/handlers/node-outcomes.js +58 -13
  370. package/dist/daemon/api/handlers/nodes.js +7 -13
  371. package/dist/daemon/api/handlers/reviews.js +4 -29
  372. package/dist/daemon/api/map.d.ts +25 -3
  373. package/dist/daemon/api/map.js +84 -14
  374. package/dist/daemon/api/server.js +68 -9
  375. package/dist/daemon/control.d.ts +18 -0
  376. package/dist/daemon/cron/disposition.d.ts +2 -0
  377. package/dist/daemon/cron/disposition.js +8 -5
  378. package/dist/daemon/cron/settle.js +37 -13
  379. package/dist/daemon/cron/sinks.d.ts +18 -1
  380. package/dist/daemon/cron/sinks.js +47 -93
  381. package/dist/daemon/crtrd-cli.js +4 -19
  382. package/dist/daemon/crtrd.d.ts +2 -0
  383. package/dist/daemon/crtrd.js +206 -81
  384. package/dist/daemon/fleet.js +5 -6
  385. package/dist/daemon/human/finish.d.ts +12 -24
  386. package/dist/daemon/human/finish.js +88 -285
  387. package/dist/daemon/human/settle.d.ts +40 -0
  388. package/dist/daemon/human/settle.js +89 -0
  389. package/dist/daemon/human/sweep.d.ts +20 -5
  390. package/dist/daemon/human/sweep.js +56 -60
  391. package/dist/daemon/manage.d.ts +21 -16
  392. package/dist/daemon/manage.js +85 -51
  393. package/dist/daemon/messaging/node-message.d.ts +1 -1
  394. package/dist/daemon/messaging/node-message.js +9 -12
  395. package/dist/daemon/park-activity.js +3 -3
  396. package/dist/daemon/reconcilers/bash-deadline.js +6 -7
  397. package/dist/daemon/reconcilers/broker-supervision.d.ts +1 -1
  398. package/dist/daemon/reconcilers/broker-supervision.js +41 -19
  399. package/dist/daemon/reconcilers/controller-death.js +6 -18
  400. package/dist/daemon/reconcilers/delivery-lane.d.ts +9 -0
  401. package/dist/daemon/reconcilers/delivery-lane.js +130 -0
  402. package/dist/daemon/reconcilers/live-obligation.js +3 -3
  403. package/dist/daemon/reconcilers/node-deadline.js +1 -1
  404. package/dist/daemon/reconcilers/node-lifecycle/freeze-lane.d.ts +1 -1
  405. package/dist/daemon/reconcilers/node-lifecycle/freeze-lane.js +10 -12
  406. package/dist/daemon/reconcilers/node-lifecycle/tick.js +5 -5
  407. package/dist/daemon/reconcilers/node-lifecycle/wants-execution.js +1 -1
  408. package/dist/daemon/reconcilers/pending-review-submit.js +3 -3
  409. package/dist/daemon/reconcilers/storage-maintenance.d.ts +2 -8
  410. package/dist/daemon/reconcilers/storage-maintenance.js +13 -43
  411. package/dist/daemon/review/companion.js +5 -11
  412. package/dist/daemon/review/deliver.d.ts +1 -5
  413. package/dist/daemon/review/deliver.js +3 -52
  414. package/dist/daemon/review/finish.d.ts +2 -27
  415. package/dist/daemon/review/finish.js +79 -263
  416. package/dist/daemon/review/sweep.d.ts +3 -10
  417. package/dist/daemon/review/sweep.js +13 -131
  418. package/dist/daemon/startup-policy.d.ts +0 -5
  419. package/dist/daemon/startup-policy.js +0 -5
  420. package/dist/index.d.ts +2 -1
  421. package/dist/index.js +1 -1
  422. package/dist/migrations/__tests__/activation-concurrency.test.js +3 -2
  423. package/dist/migrations/__tests__/activation.test.js +9 -6
  424. package/dist/migrations/activation.d.ts +3 -2
  425. package/dist/migrations/activation.js +2 -4
  426. package/dist/migrations/corpus.d.ts +2 -5
  427. package/dist/migrations/corpus.js +9 -20
  428. package/dist/migrations/runner.d.ts +3 -1
  429. package/dist/migrations/runner.js +6 -2
  430. package/dist/pi-extensions/__tests__/canvas-context-intro.test.js +21 -89
  431. package/dist/pi-extensions/__tests__/canvas-goal-capture-envelope.test.js +4 -2
  432. package/dist/pi-extensions/__tests__/canvas-stophook-agentend.test.js +51 -33
  433. package/dist/pi-extensions/__tests__/canvas-stophook-context-nudge.test.js +16 -2
  434. package/dist/pi-extensions/__tests__/pre-command-gate.test.js +13 -1
  435. package/dist/pi-extensions/canvas-inbox-watcher.js +98 -244
  436. package/dist/pi-extensions/canvas-passive-context.d.ts +4 -8
  437. package/dist/pi-extensions/canvas-passive-context.js +85 -37
  438. package/dist/pi-extensions/canvas-stophook.js +47 -31
  439. package/dist/shared/env.d.ts +1 -0
  440. package/dist/shared/env.js +3 -0
  441. package/package.json +1 -1
  442. package/runtime.lock.json +5 -5
  443. package/dist/clients/attach/__tests__/page-block.test.js +0 -54
  444. package/dist/clients/inbox/review-visibility.d.ts +0 -5
  445. package/dist/clients/inbox/review-visibility.js +0 -19
  446. package/dist/commands/canvas-rebuild-index.d.ts +0 -2
  447. package/dist/commands/canvas-rebuild-index.js +0 -46
  448. package/dist/core/__tests__/human-cancel-guard.test.js +0 -110
  449. package/dist/core/__tests__/human-deliver.test.js +0 -256
  450. package/dist/core/__tests__/human-node-not-supervised.test.d.ts +0 -1
  451. package/dist/core/__tests__/human-node-not-supervised.test.js +0 -89
  452. package/dist/core/__tests__/integration/human-deliver-e2e.test.d.ts +0 -1
  453. package/dist/core/__tests__/integration/human-deliver-e2e.test.js +0 -131
  454. package/dist/core/__tests__/phase4-review-store.test.d.ts +0 -1
  455. package/dist/core/__tests__/phase4-review-store.test.js +0 -144
  456. package/dist/core/__tests__/watchdog-abort-arms-retry.test.d.ts +0 -1
  457. package/dist/core/__tests__/watchdog-abort-arms-retry.test.js +0 -110
  458. package/dist/core/canvas/human-deliveries.d.ts +0 -39
  459. package/dist/core/canvas/human-deliveries.js +0 -75
  460. package/dist/core/canvas/node-outcome-deliveries.d.ts +0 -64
  461. package/dist/core/canvas/node-outcome-deliveries.js +0 -159
  462. package/dist/core/feed/passive.d.ts +0 -3
  463. package/dist/core/feed/passive.js +0 -53
  464. package/dist/core/human/__tests__/inbox-cancel-worker.d.ts +0 -1
  465. package/dist/core/human/__tests__/inbox-cancel-worker.js +0 -7
  466. package/dist/core/human/__tests__/inbox-claim-worker.d.ts +0 -1
  467. package/dist/core/human/__tests__/inbox-claim-worker.js +0 -17
  468. package/dist/core/human/__tests__/inbox-stale-claim-worker.d.ts +0 -1
  469. package/dist/core/human/__tests__/inbox-stale-claim-worker.js +0 -8
  470. package/dist/core/human/__tests__/integration/inbox-core.test.d.ts +0 -1
  471. package/dist/core/human/__tests__/integration/inbox-core.test.js +0 -145
  472. package/dist/core/human/__tests__/page-tickets.test.d.ts +0 -1
  473. package/dist/core/human/__tests__/page-tickets.test.js +0 -161
  474. package/dist/core/review/ticket-filter.d.ts +0 -17
  475. package/dist/core/review/ticket-filter.js +0 -21
  476. package/dist/core/runtime/broker/passive.d.ts +0 -17
  477. package/dist/core/runtime/broker/passive.js +0 -44
  478. package/dist/core/runtime/busy.d.ts +0 -46
  479. package/dist/core/runtime/busy.js +0 -150
  480. package/dist/daemon/__tests__/startup-block-marker.test.d.ts +0 -1
  481. package/dist/daemon/__tests__/startup-block-marker.test.js +0 -86
  482. package/dist/daemon/api/__tests__/review-terminal-conflict.test.d.ts +0 -1
  483. package/dist/daemon/api/__tests__/review-terminal-conflict.test.js +0 -98
  484. package/dist/daemon/human/deliver-action.d.ts +0 -3
  485. package/dist/daemon/human/deliver-action.js +0 -9
  486. package/dist/daemon/node-outcome/deliver-outcome.d.ts +0 -3
  487. package/dist/daemon/node-outcome/deliver-outcome.js +0 -9
  488. package/dist/daemon/reconcilers/human-delivery-lane.d.ts +0 -10
  489. package/dist/daemon/reconcilers/human-delivery-lane.js +0 -41
  490. package/dist/daemon/reconcilers/node-outcome-delivery-lane.d.ts +0 -10
  491. package/dist/daemon/reconcilers/node-outcome-delivery-lane.js +0 -41
  492. package/dist/daemon/startup-block-marker.d.ts +0 -25
  493. package/dist/daemon/startup-block-marker.js +0 -99
  494. /package/dist/{clients/attach/__tests__/page-block.test.d.ts → api/dto/mail.js} +0 -0
  495. /package/dist/{commands/sys/__tests__/migrate.test.d.ts → api/dto/recovery.js} +0 -0
  496. /package/dist/{core/__tests__/human-cancel-guard.test.d.ts → commands/__tests__/integration/migrate.test.d.ts} +0 -0
  497. /package/dist/core/__tests__/{human-deliver.test.d.ts → integration/fresh-home-commands.test.d.ts} +0 -0
@@ -0,0 +1,588 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { existsSync, readdirSync, readFileSync, realpathSync, statSync } from 'node:fs';
3
+ import { join, resolve } from 'node:path';
4
+ import { openDb, withCanvasWrite } from './db.js';
5
+ import { jobDir, nodesRoot } from './paths.js';
6
+ import { errorClassFromFault } from '../fault-classifier.js';
7
+ import { emitEvent } from '../events/emit.js';
8
+ import { operationIdContext, validateOperationId } from '../events/operation-id.js';
9
+ export const FAULT_QUIET_MS = 5 * 60_000;
10
+ const MESSAGE_CAP = 500;
11
+ const FAULT_LINKS = ['pi→provider', 'viewer↔broker', 'relay↔broker', 'viewer↔crtrd', 'daemon→node', 'crtr→pi'];
12
+ const LEGACY_FAULT_LINKS = [...FAULT_LINKS, 'broker↔crtrd'];
13
+ const FAULT_KINDS = ['rate-limit', 'overloaded', 'connection', 'auth', 'protocol', 'context-overflow', 'other', 'wedged', 'model-not-found'];
14
+ const RETRY_DISPOSITIONS = ['auto', 'manual', 'fatal'];
15
+ const RETRY_OWNERS = ['sdk', 'daemon', 'client'];
16
+ const EPISODE_STATES = ['pending', 'admitted', 'invalidated'];
17
+ function now() {
18
+ return new Date().toISOString();
19
+ }
20
+ function tableColumns(db, table) {
21
+ return new Set(db.prepare(`PRAGMA table_info(${table})`).all().map(({ name }) => name));
22
+ }
23
+ function addExecutionColumns(db) {
24
+ const columns = tableColumns(db, 'executions');
25
+ const additions = [
26
+ ['turn_open_at', 'TEXT'],
27
+ ['turn_progress_at', 'TEXT'],
28
+ ['turn_closed_at', 'TEXT'],
29
+ ['turn_end_reason', "TEXT CHECK (turn_end_reason IS NULL OR turn_end_reason IN ('settled', 'teardown-aborted'))"],
30
+ ['exit_diagnostic_json', 'TEXT'],
31
+ ];
32
+ for (const [name, definition] of additions)
33
+ if (!columns.has(name))
34
+ db.exec(`ALTER TABLE executions ADD COLUMN ${name} ${definition}`);
35
+ }
36
+ function createFaultTable(db) {
37
+ db.exec(`
38
+ CREATE TABLE IF NOT EXISTS node_faults (
39
+ fault_id TEXT PRIMARY KEY,
40
+ node_id TEXT NOT NULL REFERENCES nodes(node_id) ON DELETE CASCADE,
41
+ link TEXT NOT NULL CHECK (link IN ('pi→provider', 'viewer↔broker', 'relay↔broker', 'viewer↔crtrd', 'daemon→node', 'crtr→pi')),
42
+ op TEXT NOT NULL,
43
+ kind TEXT NOT NULL CHECK (kind IN ('rate-limit', 'overloaded', 'connection', 'auth', 'protocol', 'context-overflow', 'other', 'wedged', 'model-not-found')),
44
+ message TEXT NOT NULL,
45
+ since TEXT NOT NULL,
46
+ operation_id TEXT NOT NULL,
47
+ anchor_entry_id TEXT,
48
+ retry_disposition TEXT NOT NULL CHECK (retry_disposition IN ('auto', 'manual', 'fatal')),
49
+ retry_by TEXT CHECK (retry_by IS NULL OR retry_by IN ('sdk', 'daemon', 'client')),
50
+ retry_attempt INTEGER,
51
+ retry_max INTEGER,
52
+ retry_next_at TEXT,
53
+ provider_error_json TEXT,
54
+ active INTEGER NOT NULL DEFAULT 0 CHECK (active IN (0, 1)),
55
+ episode_state TEXT CHECK (episode_state IS NULL OR episode_state IN ('pending', 'admitted', 'invalidated')),
56
+ episode_binding_id TEXT REFERENCES sessions(binding_id),
57
+ created_at TEXT NOT NULL,
58
+ updated_at TEXT NOT NULL
59
+ );
60
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_node_faults_active ON node_faults(node_id) WHERE active = 1;
61
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_node_faults_episode ON node_faults(node_id) WHERE episode_state IS NOT NULL;
62
+ `);
63
+ }
64
+ function contains(values, value) {
65
+ return typeof value === 'string' && values.includes(value);
66
+ }
67
+ function isRecord(value) {
68
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
69
+ }
70
+ export function parseProviderError(value) {
71
+ if (!isRecord(value) || typeof value.provider !== 'string' || value.provider === '' || typeof value.model !== 'string' || value.model === '')
72
+ return undefined;
73
+ const status = typeof value.status === 'number' && Number.isFinite(value.status) ? value.status : undefined;
74
+ const code = typeof value.code === 'string' || typeof value.code === 'number' ? value.code : undefined;
75
+ const errno = typeof value.errno === 'string' ? value.errno : undefined;
76
+ const body = typeof value.body === 'string' ? value.body : undefined;
77
+ const causes = Array.isArray(value.causes)
78
+ ? value.causes.flatMap((item) => {
79
+ if (!isRecord(item))
80
+ return [];
81
+ const cause = {
82
+ ...(typeof item.message === 'string' ? { message: item.message } : {}),
83
+ ...(typeof item.status === 'number' && Number.isFinite(item.status) ? { status: item.status } : {}),
84
+ ...(typeof item.code === 'string' || typeof item.code === 'number' ? { code: item.code } : {}),
85
+ ...(typeof item.errno === 'string' ? { errno: item.errno } : {}),
86
+ ...(typeof item.body === 'string' ? { body: item.body } : {}),
87
+ };
88
+ return Object.keys(cause).length === 0 ? [] : [cause];
89
+ })
90
+ : undefined;
91
+ return {
92
+ provider: value.provider,
93
+ model: value.model,
94
+ ...(status === undefined ? {} : { status }),
95
+ ...(code === undefined ? {} : { code }),
96
+ ...(errno === undefined ? {} : { errno }),
97
+ ...(body === undefined ? {} : { body }),
98
+ ...(causes === undefined || causes.length === 0 ? {} : { causes }),
99
+ };
100
+ }
101
+ function parseLegacyFault(raw) {
102
+ try {
103
+ const parsed = JSON.parse(raw);
104
+ if (!contains(LEGACY_FAULT_LINKS, parsed.link) || typeof parsed.op !== 'string' || parsed.op === '' || !contains(FAULT_KINDS, parsed.kind) || typeof parsed.message !== 'string' || typeof parsed.since !== 'string')
105
+ return null;
106
+ const operationId = validateOperationId(parsed.operation_id);
107
+ if (!isRecord(parsed.retry) || !contains(RETRY_DISPOSITIONS, parsed.retry.disposition))
108
+ return null;
109
+ if (parsed.retry.by !== undefined && !contains(RETRY_OWNERS, parsed.retry.by))
110
+ return null;
111
+ if (parsed.retry.attempt !== undefined && typeof parsed.retry.attempt !== 'number')
112
+ return null;
113
+ if (parsed.retry.max !== undefined && typeof parsed.retry.max !== 'number')
114
+ return null;
115
+ if (parsed.retry.nextAt !== undefined && typeof parsed.retry.nextAt !== 'string')
116
+ return null;
117
+ if (parsed.anchorEntryId !== undefined && (typeof parsed.anchorEntryId !== 'string' || parsed.anchorEntryId === ''))
118
+ return null;
119
+ const providerError = parsed.providerError === undefined ? undefined : parseProviderError(parsed.providerError);
120
+ if (parsed.providerError !== undefined && providerError === undefined)
121
+ return null;
122
+ return {
123
+ link: parsed.link,
124
+ op: parsed.op,
125
+ kind: parsed.kind,
126
+ retry: {
127
+ disposition: parsed.retry.disposition,
128
+ ...(parsed.retry.by === undefined ? {} : { by: parsed.retry.by }),
129
+ ...(parsed.retry.attempt === undefined ? {} : { attempt: parsed.retry.attempt }),
130
+ ...(parsed.retry.max === undefined ? {} : { max: parsed.retry.max }),
131
+ ...(parsed.retry.nextAt === undefined ? {} : { nextAt: parsed.retry.nextAt }),
132
+ },
133
+ message: parsed.message,
134
+ since: parsed.since,
135
+ operation_id: operationId,
136
+ ...(parsed.anchorEntryId === undefined ? {} : { anchorEntryId: parsed.anchorEntryId }),
137
+ ...(providerError === undefined ? {} : { providerError }),
138
+ };
139
+ }
140
+ catch {
141
+ return null;
142
+ }
143
+ }
144
+ function canonicalPath(path) {
145
+ const absolute = resolve(path);
146
+ try {
147
+ return realpathSync(absolute);
148
+ }
149
+ catch {
150
+ return absolute;
151
+ }
152
+ }
153
+ function parseLegacyEpisode(raw) {
154
+ try {
155
+ const parsed = JSON.parse(raw);
156
+ if (!contains(EPISODE_STATES, parsed.state) || typeof parsed.sessionFile !== 'string' || parsed.sessionFile === '')
157
+ return null;
158
+ const fault = JSON.stringify(parsed.fault);
159
+ const parsedFault = parseLegacyFault(fault);
160
+ if (parsedFault === null || parsedFault.link !== 'pi→provider' || parsedFault.retry.disposition !== 'auto' || parsedFault.retry.by !== 'daemon')
161
+ return null;
162
+ const anchor = parsed.anchorEntryId;
163
+ if (anchor !== null && typeof anchor !== 'string')
164
+ return null;
165
+ if ((parsedFault.anchorEntryId ?? null) !== anchor)
166
+ return null;
167
+ return { state: parsed.state, sessionFile: canonicalPath(parsed.sessionFile), fault: parsedFault };
168
+ }
169
+ catch {
170
+ return null;
171
+ }
172
+ }
173
+ function resolveOperationId(explicit) {
174
+ if (explicit !== undefined)
175
+ return validateOperationId(explicit);
176
+ const current = operationIdContext.current();
177
+ return current ?? operationIdContext.fresh((operationId) => operationId);
178
+ }
179
+ function clampMessage(message) {
180
+ return message.slice(0, MESSAGE_CAP);
181
+ }
182
+ function faultPriority(fault) {
183
+ if (fault.link === 'pi→provider' && fault.retry.disposition === 'auto' && fault.retry.by === 'daemon')
184
+ return 3;
185
+ if (fault.retry.disposition === 'fatal')
186
+ return 2;
187
+ return 1;
188
+ }
189
+ function encodeFault(fault) {
190
+ return {
191
+ link: fault.link,
192
+ op: fault.op,
193
+ kind: fault.kind,
194
+ message: fault.message,
195
+ since: fault.since,
196
+ operationId: fault.operation_id,
197
+ anchorEntryId: fault.anchorEntryId ?? null,
198
+ retryDisposition: fault.retry.disposition,
199
+ retryBy: fault.retry.by ?? null,
200
+ retryAttempt: fault.retry.attempt ?? null,
201
+ retryMax: fault.retry.max ?? null,
202
+ retryNextAt: fault.retry.nextAt ?? null,
203
+ providerError: fault.providerError === undefined ? null : JSON.stringify(fault.providerError),
204
+ };
205
+ }
206
+ function hydrateFault(row) {
207
+ const provider = row['provider_error_json'];
208
+ const providerError = typeof provider === 'string' ? parseProviderError(JSON.parse(provider)) : undefined;
209
+ if (typeof provider === 'string' && providerError === undefined)
210
+ throw new Error(`invalid provider error on stored fault ${row['fault_id']}`);
211
+ const retry = {
212
+ disposition: row['retry_disposition'],
213
+ ...(row['retry_by'] === null ? {} : { by: row['retry_by'] }),
214
+ ...(row['retry_attempt'] === null ? {} : { attempt: row['retry_attempt'] }),
215
+ ...(row['retry_max'] === null ? {} : { max: row['retry_max'] }),
216
+ ...(row['retry_next_at'] === null ? {} : { nextAt: row['retry_next_at'] }),
217
+ };
218
+ return {
219
+ faultId: row['fault_id'],
220
+ link: row['link'],
221
+ op: row['op'],
222
+ kind: row['kind'],
223
+ message: row['message'],
224
+ since: row['since'],
225
+ operation_id: row['operation_id'],
226
+ retry,
227
+ ...(row['anchor_entry_id'] === null ? {} : { anchorEntryId: row['anchor_entry_id'] }),
228
+ ...(providerError === undefined ? {} : { providerError }),
229
+ };
230
+ }
231
+ function activeFault(db, nodeId) {
232
+ const row = db.prepare('SELECT * FROM node_faults WHERE node_id = ? AND active = 1').get(nodeId);
233
+ return row === undefined ? null : hydrateFault(row);
234
+ }
235
+ function storedFault(input) {
236
+ if (!contains(FAULT_LINKS, input.link))
237
+ throw new Error(`fault link is not durable: ${input.link}`);
238
+ return {
239
+ link: input.link,
240
+ op: input.op,
241
+ kind: input.kind,
242
+ retry: input.retry,
243
+ message: clampMessage(input.message),
244
+ since: input.since ?? now(),
245
+ operation_id: resolveOperationId(input.operation_id),
246
+ ...(typeof input.anchorEntryId === 'string' && input.anchorEntryId !== '' ? { anchorEntryId: input.anchorEntryId } : {}),
247
+ ...(input.providerError === undefined ? {} : { providerError: input.providerError }),
248
+ };
249
+ }
250
+ function emitRecordedFault(nodeId, fault) {
251
+ emitEvent({
252
+ level: fault.retry.disposition === 'fatal' ? 'error' : 'warn',
253
+ event: 'fault.recorded',
254
+ operation_id: fault.operation_id,
255
+ node_id: nodeId,
256
+ error_class: errorClassFromFault(fault),
257
+ fields: {
258
+ link: fault.link, op: fault.op, kind: fault.kind, retry: fault.retry, message: fault.message,
259
+ ...(fault.anchorEntryId === undefined ? {} : { anchor_entry_id: fault.anchorEntryId }),
260
+ ...(fault.providerError === undefined ? {} : { provider_error: fault.providerError }),
261
+ },
262
+ });
263
+ }
264
+ function insertFault(db, nodeId, fault, active, episode) {
265
+ const faultId = randomUUID();
266
+ const encoded = encodeFault(fault);
267
+ const at = now();
268
+ db.prepare('INSERT INTO node_faults (fault_id, node_id, link, op, kind, message, since, operation_id, anchor_entry_id, retry_disposition, retry_by, retry_attempt, retry_max, retry_next_at, provider_error_json, active, episode_state, episode_binding_id, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)')
269
+ .run(faultId, nodeId, encoded.link, encoded.op, encoded.kind, encoded.message, encoded.since, encoded.operationId, encoded.anchorEntryId, encoded.retryDisposition, encoded.retryBy, encoded.retryAttempt, encoded.retryMax, encoded.retryNextAt, encoded.providerError, active ? 1 : 0, episode?.state ?? null, episode?.bindingId ?? null, at, at);
270
+ return { ...fault, faultId };
271
+ }
272
+ function updateFault(db, faultId, fault, episode) {
273
+ const encoded = encodeFault(fault);
274
+ db.prepare('UPDATE node_faults SET link = ?, op = ?, kind = ?, message = ?, since = ?, operation_id = ?, anchor_entry_id = ?, retry_disposition = ?, retry_by = ?, retry_attempt = ?, retry_max = ?, retry_next_at = ?, provider_error_json = ?, episode_state = COALESCE(?, episode_state), episode_binding_id = COALESCE(?, episode_binding_id), updated_at = ? WHERE fault_id = ?')
275
+ .run(encoded.link, encoded.op, encoded.kind, encoded.message, encoded.since, encoded.operationId, encoded.anchorEntryId, encoded.retryDisposition, encoded.retryBy, encoded.retryAttempt, encoded.retryMax, encoded.retryNextAt, encoded.providerError, episode?.state ?? null, episode?.bindingId ?? null, now(), faultId);
276
+ return { ...fault, faultId };
277
+ }
278
+ function deleteIfUnretained(db, faultId) {
279
+ db.prepare('DELETE FROM node_faults WHERE fault_id = ? AND active = 0 AND episode_state IS NULL').run(faultId);
280
+ }
281
+ function replaceActiveFault(db, nodeId, fault, episode) {
282
+ const current = activeFault(db, nodeId);
283
+ if (current === null) {
284
+ // A revive clears the active marker while retaining its bound provider-retry
285
+ // episode. Startup re-records that same fault before arming its retry, so
286
+ // restore the retained row instead of creating an unpaired active fault.
287
+ const retained = db.prepare('SELECT fault_id, link, kind, since, operation_id FROM node_faults WHERE node_id = ? AND active = 0 AND episode_state IS NOT NULL').get(nodeId);
288
+ if (retained !== undefined && retained.link === fault.link && retained.kind === fault.kind && retained.since === fault.since && retained.operation_id === fault.operation_id) {
289
+ db.prepare('UPDATE node_faults SET active = 1, updated_at = ? WHERE fault_id = ?').run(now(), retained.fault_id);
290
+ return updateFault(db, retained.fault_id, fault);
291
+ }
292
+ return insertFault(db, nodeId, fault, true, episode);
293
+ }
294
+ if (current.link === fault.link && current.kind === fault.kind) {
295
+ const retry = { ...current.retry, ...fault.retry };
296
+ if (fault.retry.disposition === 'fatal' && !('nextAt' in fault.retry))
297
+ delete retry.nextAt;
298
+ return updateFault(db, current.faultId, { ...fault, since: current.since, anchorEntryId: current.anchorEntryId ?? fault.anchorEntryId, retry }, episode);
299
+ }
300
+ if (faultPriority(current) > faultPriority(fault))
301
+ return null;
302
+ db.prepare('UPDATE node_faults SET active = 0, updated_at = ? WHERE fault_id = ?').run(now(), current.faultId);
303
+ deleteIfUnretained(db, current.faultId);
304
+ return insertFault(db, nodeId, fault, true, episode);
305
+ }
306
+ function liveBinding(db, nodeId, sessionId) {
307
+ const row = db.prepare('SELECT binding_id FROM sessions WHERE node_id = ? AND session_id = ? AND unbound_at IS NULL').get(nodeId, sessionId);
308
+ return row?.binding_id ?? null;
309
+ }
310
+ export function openTurn(executionId, _opts) {
311
+ return withCanvasWrite((db) => {
312
+ const at = now();
313
+ const open = db.prepare("UPDATE executions SET turn_progress_at = ? WHERE execution_id = ? AND state != 'exited' AND turn_open_at IS NOT NULL AND turn_closed_at IS NULL").run(at, executionId);
314
+ if (open.changes > 0)
315
+ return true;
316
+ return db.prepare("UPDATE executions SET turn_open_at = ?, turn_progress_at = ?, turn_closed_at = NULL, turn_end_reason = NULL WHERE execution_id = ? AND state != 'exited'").run(at, at, executionId).changes > 0;
317
+ });
318
+ }
319
+ export function recordTurnProgress(executionId) {
320
+ return withCanvasWrite((db) => db.prepare("UPDATE executions SET turn_progress_at = ? WHERE execution_id = ? AND state != 'exited' AND turn_open_at IS NOT NULL AND turn_closed_at IS NULL").run(now(), executionId).changes > 0);
321
+ }
322
+ export function closeTurn(executionId, opts) {
323
+ return withCanvasWrite((db) => db.prepare("UPDATE executions SET turn_closed_at = ?, turn_end_reason = ? WHERE execution_id = ? AND state != 'exited' AND turn_open_at IS NOT NULL AND turn_closed_at IS NULL").run(now(), opts.endReason, executionId).changes > 0);
324
+ }
325
+ export function recordTransportPoisonVerdict(executionId, verdict) {
326
+ if (!Number.isInteger(verdict.consecutivePasses) || verdict.consecutivePasses < 1 || verdict.routes.length === 0 || !verdict.routes.every((route) => route !== ''))
327
+ throw new Error('invalid transport-poison verdict');
328
+ return withCanvasWrite((db) => db.prepare("UPDATE executions SET exit_diagnostic_json = ? WHERE execution_id = ? AND state != 'exited'").run(JSON.stringify({ schema: 'crtr.execution-exit-diagnostic/v1', transport_poison: { consecutive_passes: verdict.consecutivePasses, routes: verdict.routes } }), executionId).changes > 0);
329
+ }
330
+ export function readTransportPoisonVerdict(nodeId) {
331
+ const row = openDb().prepare("SELECT exit_diagnostic_json FROM executions WHERE node_id = ? ORDER BY rowid DESC LIMIT 1").get(nodeId);
332
+ if (row === undefined || row.exit_diagnostic_json === null)
333
+ return null;
334
+ try {
335
+ const parsed = JSON.parse(row.exit_diagnostic_json);
336
+ const poison = parsed.transport_poison;
337
+ if (parsed.schema !== 'crtr.execution-exit-diagnostic/v1' || poison === undefined || !Number.isInteger(poison.consecutive_passes) || poison.consecutive_passes < 1 || !Array.isArray(poison.routes) || poison.routes.length === 0 || !poison.routes.every((route) => typeof route === 'string' && route !== ''))
338
+ return null;
339
+ return { consecutivePasses: poison.consecutive_passes, routes: poison.routes };
340
+ }
341
+ catch {
342
+ return null;
343
+ }
344
+ }
345
+ export function readLiveTurn(nodeId) {
346
+ const row = openDb().prepare("SELECT turn_open_at, turn_closed_at, turn_progress_at FROM executions WHERE node_id = ? AND state != 'exited'").get(nodeId);
347
+ if (row === undefined)
348
+ return null;
349
+ return { busy: row.turn_open_at !== null && row.turn_closed_at === null, progressAt: row.turn_progress_at };
350
+ }
351
+ export function readInterruptedTurn(nodeId) {
352
+ const db = openDb();
353
+ const bearing = db.prepare("SELECT turn_closed_at, turn_progress_at FROM executions WHERE node_id = ? AND turn_open_at IS NOT NULL ORDER BY rowid DESC LIMIT 1").get(nodeId);
354
+ if (bearing === undefined)
355
+ return null;
356
+ const latest = db.prepare('SELECT turn_end_reason FROM executions WHERE node_id = ? ORDER BY rowid DESC LIMIT 1').get(nodeId);
357
+ return { interrupted: bearing.turn_closed_at === null || latest?.turn_end_reason === 'teardown-aborted', progressAt: bearing.turn_progress_at };
358
+ }
359
+ export function recordFault(nodeId, input) {
360
+ const fault = storedFault(input);
361
+ emitRecordedFault(nodeId, fault);
362
+ return withCanvasWrite((db) => replaceActiveFault(db, nodeId, fault));
363
+ }
364
+ export function clearFault(nodeId, opts) {
365
+ return withCanvasWrite((db) => {
366
+ const current = activeFault(db, nodeId);
367
+ if (current === null || (opts?.link !== undefined && current.link !== opts.link))
368
+ return false;
369
+ const episode = db.prepare('SELECT episode_state FROM node_faults WHERE fault_id = ?').get(current.faultId);
370
+ db.prepare('UPDATE node_faults SET active = 0, updated_at = ? WHERE fault_id = ?').run(now(), current.faultId);
371
+ if (episode.episode_state === 'pending' && opts?.preserveEpisode !== true)
372
+ db.prepare('UPDATE node_faults SET episode_state = NULL, episode_binding_id = NULL, updated_at = ? WHERE fault_id = ?').run(now(), current.faultId);
373
+ deleteIfUnretained(db, current.faultId);
374
+ const operationId = resolveOperationId();
375
+ emitEvent({
376
+ level: 'info', event: 'fault.cleared', operation_id: operationId, node_id: nodeId, error_class: errorClassFromFault(current),
377
+ fields: { cleared_fault_operation_id: current.operation_id, link: current.link, op: current.op, kind: current.kind, retry: current.retry, message: current.message, since: current.since, ...(current.anchorEntryId === undefined ? {} : { anchor_entry_id: current.anchorEntryId }) },
378
+ });
379
+ return true;
380
+ });
381
+ }
382
+ export function readFault(nodeId) {
383
+ return activeFault(openDb(), nodeId);
384
+ }
385
+ export function recordPendingEpisode(nodeId, input) {
386
+ const fault = storedFault(input.input);
387
+ if (fault.link !== 'pi→provider' || fault.retry.disposition !== 'auto' || fault.retry.by !== 'daemon')
388
+ throw new Error('provider retry episode requires an automatic daemon provider fault');
389
+ emitRecordedFault(nodeId, fault);
390
+ return withCanvasWrite((db) => {
391
+ const bindingId = liveBinding(db, nodeId, input.sessionId);
392
+ if (bindingId === null)
393
+ return null;
394
+ const current = activeFault(db, nodeId);
395
+ if (current !== null && (current.link !== fault.link || current.kind !== fault.kind) && faultPriority(current) > faultPriority(fault))
396
+ return null;
397
+ const existingEpisodes = db.prepare('SELECT fault_id FROM node_faults WHERE node_id = ? AND episode_state IS NOT NULL').all(nodeId);
398
+ db.prepare('UPDATE node_faults SET episode_state = NULL, episode_binding_id = NULL, updated_at = ? WHERE node_id = ? AND episode_state IS NOT NULL').run(now(), nodeId);
399
+ for (const row of existingEpisodes)
400
+ deleteIfUnretained(db, row.fault_id);
401
+ const stored = replaceActiveFault(db, nodeId, fault, { state: 'pending', bindingId });
402
+ if (stored === null)
403
+ return null;
404
+ db.prepare('UPDATE node_faults SET episode_state = ?, episode_binding_id = ?, updated_at = ? WHERE fault_id = ?').run('pending', bindingId, now(), stored.faultId);
405
+ return stored;
406
+ });
407
+ }
408
+ export function admitEpisode(nodeId, input) {
409
+ return withCanvasWrite((db) => {
410
+ const bindingId = liveBinding(db, nodeId, input.sessionId);
411
+ if (bindingId === null)
412
+ return false;
413
+ return db.prepare("UPDATE node_faults SET episode_state = 'admitted', retry_disposition = ?, retry_by = ?, retry_attempt = ?, retry_max = ?, retry_next_at = ?, updated_at = ? WHERE node_id = ? AND fault_id = ? AND episode_state = 'pending' AND episode_binding_id = ?").run(input.retry.disposition, input.retry.by ?? null, input.retry.attempt ?? null, input.retry.max ?? null, input.retry.nextAt ?? null, now(), nodeId, input.faultId, bindingId).changes > 0;
414
+ });
415
+ }
416
+ export function reopenEpisode(nodeId, input) {
417
+ return withCanvasWrite((db) => {
418
+ const bindingId = liveBinding(db, nodeId, input.sessionId);
419
+ if (bindingId === null)
420
+ return false;
421
+ return db.prepare("UPDATE node_faults SET episode_state = 'pending', updated_at = ? WHERE node_id = ? AND episode_state = 'admitted' AND episode_binding_id = ?").run(now(), nodeId, bindingId).changes > 0;
422
+ });
423
+ }
424
+ export function invalidateEpisode(nodeId, input) {
425
+ return withCanvasWrite((db) => {
426
+ const bindingId = liveBinding(db, nodeId, input.sessionId);
427
+ if (bindingId === null)
428
+ return false;
429
+ return db.prepare("UPDATE node_faults SET episode_state = 'invalidated', updated_at = ? WHERE node_id = ? AND episode_state != 'invalidated' AND episode_state IS NOT NULL AND episode_binding_id = ?").run(now(), nodeId, bindingId).changes > 0;
430
+ });
431
+ }
432
+ export function clearEpisode(nodeId, input) {
433
+ return withCanvasWrite((db) => {
434
+ const bindingId = liveBinding(db, nodeId, input.sessionId);
435
+ if (bindingId === null)
436
+ return false;
437
+ const rows = db.prepare('SELECT fault_id FROM node_faults WHERE node_id = ? AND episode_binding_id = ? AND episode_state IS NOT NULL').all(nodeId, bindingId);
438
+ if (rows.length === 0)
439
+ return false;
440
+ db.prepare('UPDATE node_faults SET episode_state = NULL, episode_binding_id = NULL, updated_at = ? WHERE node_id = ? AND episode_binding_id = ? AND episode_state IS NOT NULL').run(now(), nodeId, bindingId);
441
+ for (const row of rows)
442
+ deleteIfUnretained(db, row.fault_id);
443
+ return true;
444
+ });
445
+ }
446
+ export function readEpisode(nodeId) {
447
+ const row = openDb().prepare('SELECT f.*, s.session_id FROM node_faults f JOIN sessions s ON s.binding_id = f.episode_binding_id WHERE f.node_id = ? AND f.episode_state IS NOT NULL').get(nodeId);
448
+ if (row === undefined)
449
+ return null;
450
+ return { state: row['episode_state'], sessionId: row['session_id'], fault: hydrateFault(row) };
451
+ }
452
+ export function beginBootFaultAttempt(nodeId) {
453
+ const preLaunchClear = readFault(nodeId) === null;
454
+ return {
455
+ explanation() {
456
+ if (!preLaunchClear)
457
+ return null;
458
+ const fault = readFault(nodeId);
459
+ if (fault === null || fault.link !== 'crtr→pi' || fault.retry.disposition !== 'fatal')
460
+ return null;
461
+ const op = fault.op.trim();
462
+ const message = fault.message.trim();
463
+ return op === '' || message === '' ? null : `${op}: ${message}`;
464
+ },
465
+ };
466
+ }
467
+ function markerMtime(path, fallback) {
468
+ try {
469
+ return statSync(path).mtime.toISOString();
470
+ }
471
+ catch {
472
+ return fallback;
473
+ }
474
+ }
475
+ function legacyFile(path, required) {
476
+ if (!existsSync(path))
477
+ return null;
478
+ try {
479
+ return readFileSync(path, 'utf8');
480
+ }
481
+ catch (error) {
482
+ if (!required)
483
+ return null;
484
+ throw new Error(`cannot import recovery input ${path}: ${error.message}`);
485
+ }
486
+ }
487
+ function latestExecution(db, nodeId) {
488
+ const row = db.prepare('SELECT execution_id, started_at FROM executions WHERE node_id = ? ORDER BY rowid DESC LIMIT 1').get(nodeId);
489
+ return row ?? null;
490
+ }
491
+ function syntheticExecution(db, nodeId) {
492
+ const columns = tableColumns(db, 'nodes');
493
+ const launched = columns.has('launched_at')
494
+ ? db.prepare('SELECT COALESCE(launched_at, created) AS started_at FROM nodes WHERE node_id = ?').get(nodeId)
495
+ : db.prepare('SELECT created AS started_at FROM nodes WHERE node_id = ?').get(nodeId);
496
+ if (launched === undefined)
497
+ throw new Error(`cannot import recovery witness for unknown node ${nodeId}`);
498
+ const executionId = randomUUID();
499
+ db.prepare("INSERT INTO executions (execution_id, node_id, daemon_epoch, pid, process_identity, state, started_at, ended_at, exit_reason) VALUES (?, ?, NULL, NULL, NULL, 'exited', ?, ?, 'pre-consolidation')").run(executionId, nodeId, launched.started_at, launched.started_at);
500
+ return { execution_id: executionId, started_at: launched.started_at };
501
+ }
502
+ function insertMigratedFault(db, nodeId, fault, active, episode) {
503
+ return insertFault(db, nodeId, fault, active, episode);
504
+ }
505
+ function migrationBinding(db, nodeId, transcriptPath, legacyPath) {
506
+ const current = db.prepare('SELECT binding_id, node_id FROM sessions WHERE transcript_path = ? AND unbound_at IS NULL').get(transcriptPath);
507
+ if (current !== undefined) {
508
+ if (current.node_id !== nodeId)
509
+ throw new Error(`cannot import provider retry ${legacyPath}: transcript is live-owned by ${current.node_id}`);
510
+ return current.binding_id;
511
+ }
512
+ const known = db.prepare('SELECT binding_id FROM sessions WHERE node_id = ? AND transcript_path = ? ORDER BY rowid DESC LIMIT 1').get(nodeId, transcriptPath);
513
+ if (known !== undefined)
514
+ return known.binding_id;
515
+ const created = db.prepare('SELECT created FROM nodes WHERE node_id = ?').get(nodeId);
516
+ if (created === undefined)
517
+ throw new Error(`cannot import provider retry ${legacyPath}: unknown node ${nodeId}`);
518
+ const bindingId = randomUUID();
519
+ db.prepare('INSERT INTO sessions (binding_id, session_id, node_id, transcript_path, source_session_id, fork_entry_id, created_at, bound_at, unbound_at) VALUES (?, NULL, ?, ?, NULL, NULL, ?, ?, ?)').run(bindingId, nodeId, transcriptPath, created.created, created.created, now());
520
+ return bindingId;
521
+ }
522
+ /** Import legacy witness and recovery files inside the parent's one shared consolidation transaction. */
523
+ export function migrateRecoveryWitnesses(db) {
524
+ addExecutionColumns(db);
525
+ createFaultTable(db);
526
+ if (!existsSync(nodesRoot()))
527
+ return;
528
+ const entries = readdirSync(nodesRoot(), { withFileTypes: true }).filter((entry) => entry.isDirectory()).sort((a, b) => a.name.localeCompare(b.name));
529
+ const nodeExists = db.prepare('SELECT 1 FROM nodes WHERE node_id = ?');
530
+ for (const entry of entries) {
531
+ const nodeId = entry.name;
532
+ const directory = jobDir(nodeId);
533
+ const busyPath = join(directory, 'busy');
534
+ const abortPath = join(directory, 'clean-abort');
535
+ const poisonPath = join(directory, 'transport-poison.json');
536
+ const faultPath = join(directory, 'fault');
537
+ const episodePath = join(directory, 'provider-retry');
538
+ const busy = existsSync(busyPath);
539
+ const aborted = existsSync(abortPath);
540
+ if (!busy && !aborted && !existsSync(poisonPath) && !existsSync(faultPath) && !existsSync(episodePath))
541
+ continue;
542
+ if (!existsSync(join(nodesRoot(), nodeId, 'meta.json')) && nodeExists.get(nodeId) === undefined)
543
+ continue;
544
+ const poison = legacyFile(poisonPath, false);
545
+ const legacyFault = legacyFile(faultPath, true);
546
+ const legacyEpisode = legacyFile(episodePath, true);
547
+ if (!busy && !aborted && poison === null && legacyFault === null && legacyEpisode === null)
548
+ continue;
549
+ let execution = latestExecution(db, nodeId);
550
+ if (execution === null)
551
+ execution = syntheticExecution(db, nodeId);
552
+ if (busy) {
553
+ const at = markerMtime(busyPath, execution.started_at);
554
+ db.prepare('UPDATE executions SET turn_open_at = ?, turn_progress_at = ?, turn_closed_at = NULL, turn_end_reason = NULL WHERE execution_id = ?').run(at, at, execution.execution_id);
555
+ }
556
+ else if (aborted) {
557
+ const at = markerMtime(abortPath, execution.started_at);
558
+ db.prepare("UPDATE executions SET turn_open_at = ?, turn_progress_at = ?, turn_closed_at = ?, turn_end_reason = 'teardown-aborted' WHERE execution_id = ?").run(at, at, at, execution.execution_id);
559
+ }
560
+ if (poison !== null) {
561
+ try {
562
+ const parsed = JSON.parse(poison);
563
+ if (Number.isInteger(parsed.consecutivePasses) && parsed.consecutivePasses >= 1 && Array.isArray(parsed.routes) && parsed.routes.length > 0 && parsed.routes.every((route) => typeof route === 'string' && route !== '')) {
564
+ db.prepare('UPDATE executions SET exit_diagnostic_json = ? WHERE execution_id = ?').run(JSON.stringify({ schema: 'crtr.execution-exit-diagnostic/v1', transport_poison: { consecutive_passes: parsed.consecutivePasses, routes: parsed.routes } }), execution.execution_id);
565
+ }
566
+ }
567
+ catch {
568
+ // Diagnostic-only legacy context is intentionally omitted when corrupt.
569
+ }
570
+ }
571
+ const active = legacyFault === null ? null : parseLegacyFault(legacyFault);
572
+ if (legacyFault !== null && active === null)
573
+ throw new Error(`cannot import recovery fault ${faultPath}`);
574
+ const episode = legacyEpisode === null ? null : parseLegacyEpisode(legacyEpisode);
575
+ if (legacyEpisode !== null && episode === null)
576
+ throw new Error(`cannot import provider retry ${episodePath}`);
577
+ let activeStored = active === null || active.link === 'broker↔crtrd' ? null : insertMigratedFault(db, nodeId, active, true);
578
+ if (episode !== null) {
579
+ const bindingId = migrationBinding(db, nodeId, episode.sessionFile, episodePath);
580
+ if (activeStored !== null && activeStored.since === episode.fault.since) {
581
+ db.prepare('UPDATE node_faults SET episode_state = ?, episode_binding_id = ?, updated_at = ? WHERE fault_id = ?').run(episode.state, bindingId, now(), activeStored.faultId);
582
+ }
583
+ else {
584
+ insertMigratedFault(db, nodeId, episode.fault, false, { state: episode.state, bindingId });
585
+ }
586
+ }
587
+ }
588
+ }
@@ -216,6 +216,10 @@ export class RemoteCanvasSource {
216
216
  window: n.window ?? null,
217
217
  tmux_session: n.tmux_session ?? null,
218
218
  pane: n.pane ?? null,
219
+ // Recovery state projected by the REMOTE canvas's own daemon (stale-provider
220
+ // overlay already applied there), never a read of this host's files.
221
+ fault: n.fault ?? null,
222
+ streaming: n.streaming === true,
219
223
  };
220
224
  }
221
225
  async listNodes(filter) {