@north-light/crouter 0.3.311 → 0.3.313

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (520) hide show
  1. package/dist/api/client.d.ts +27 -30
  2. package/dist/api/client.js +80 -67
  3. package/dist/api/dto/broker-ops.d.ts +10 -20
  4. package/dist/api/dto/canvas.d.ts +1 -10
  5. package/dist/api/dto/health.d.ts +113 -0
  6. package/dist/api/dto/human-requests.d.ts +14 -3
  7. package/dist/api/dto/human.d.ts +2 -48
  8. package/dist/api/dto/human.js +3 -2
  9. package/dist/api/dto/inbox.d.ts +14 -0
  10. package/dist/api/dto/mail.d.ts +43 -0
  11. package/dist/api/dto/nodes.d.ts +11 -8
  12. package/dist/api/dto/recovery.d.ts +96 -0
  13. package/dist/api/dto/reviews.d.ts +2 -2
  14. package/dist/api/routes.d.ts +9 -6
  15. package/dist/api/routes.js +11 -11
  16. package/dist/build-root.d.ts +4 -0
  17. package/dist/build-root.js +11 -1
  18. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/__tests__/integration/provider-rotation.test.ts +35 -47
  19. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/provider-rotation.d.ts +1 -1
  20. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/provider-rotation.js +12 -5
  21. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/provider-rotation.ts +12 -5
  22. package/dist/cli.js +10 -1
  23. package/dist/clients/__tests__/helpers/human-daemon.d.ts +11 -0
  24. package/dist/clients/__tests__/helpers/human-daemon.js +60 -0
  25. package/dist/clients/attach/chrome/canvas-panels.js +1 -1
  26. package/dist/clients/attach/chrome/inbox-strip.d.ts +3 -3
  27. package/dist/clients/attach/chrome/inbox-strip.js +17 -9
  28. package/dist/clients/attach/chrome/ticket-panel.d.ts +2 -2
  29. package/dist/clients/attach/chrome/ticket-panel.js +77 -46
  30. package/dist/clients/attach/overlays/graph.js +6 -9
  31. package/dist/clients/attach/render/chat-view.d.ts +5 -4
  32. package/dist/clients/attach/render/chat-view.js +22 -13
  33. package/dist/clients/attach/render/page-block.d.ts +11 -7
  34. package/dist/clients/attach/render/page-block.js +64 -55
  35. package/dist/clients/attach/viewer.js +746 -735
  36. package/dist/clients/inbox/__tests__/integration/inbox-controller.test.js +56 -56
  37. package/dist/clients/inbox/__tests__/integration/stale-row-activation.test.js +45 -32
  38. package/dist/clients/inbox/controller.d.ts +42 -14
  39. package/dist/clients/inbox/controller.js +240 -124
  40. package/dist/clients/inbox/dto-adapt.d.ts +28 -0
  41. package/dist/clients/inbox/dto-adapt.js +93 -0
  42. package/dist/clients/inbox/page-adapter.d.ts +5 -4
  43. package/dist/clients/inbox/page-adapter.js +12 -12
  44. package/dist/clients/inbox/resolve.d.ts +10 -5
  45. package/dist/clients/inbox/resolve.js +13 -12
  46. package/dist/clients/inbox/review/review-client.d.ts +0 -1
  47. package/dist/clients/inbox/review/review-client.js +0 -4
  48. package/dist/clients/inbox/review-adapter.d.ts +2 -1
  49. package/dist/clients/inbox/review-adapter.js +3 -5
  50. package/dist/clients/inbox/surface.d.ts +2 -2
  51. package/dist/clients/inbox/surface.js +19 -8
  52. package/dist/clients/inbox/tui.d.ts +3 -3
  53. package/dist/commands/__tests__/human.test.js +31 -15
  54. package/dist/commands/__tests__/integration/dashboard-snapshot.test.js +36 -11
  55. package/dist/commands/{sys/__tests__ → __tests__/integration}/migrate.test.js +142 -36
  56. package/dist/commands/__tests__/revive-now-gate.test.js +2 -0
  57. package/dist/commands/api-client.d.ts +5 -4
  58. package/dist/commands/api-client.js +13 -7
  59. package/dist/commands/canvas-history/shared.js +1 -1
  60. package/dist/commands/canvas-history.js +1 -1
  61. package/dist/commands/canvas-roster.js +1 -1
  62. package/dist/commands/canvas.js +1 -2
  63. package/dist/commands/human/feedback.js +20 -17
  64. package/dist/commands/human/prompts.js +45 -36
  65. package/dist/commands/human/queue.js +73 -123
  66. package/dist/commands/human/review.js +6 -16
  67. package/dist/commands/memory/__tests__/command-selector-and-mutation-guards.test.js +2 -0
  68. package/dist/commands/memory/__tests__/memory-list-and-lint-ignore.test.js +2 -0
  69. package/dist/commands/memory/__tests__/repository-root-lint.test.js +2 -0
  70. package/dist/commands/node-inspect-artifacts.js +1 -1
  71. package/dist/commands/node-lifecycle-revive.d.ts +2 -2
  72. package/dist/commands/node-lifecycle-revive.js +9 -19
  73. package/dist/commands/pkg/plugin-manage.js +0 -1
  74. package/dist/commands/profile/show.js +4 -12
  75. package/dist/commands/surface-tmux-spread.js +1 -5
  76. package/dist/commands/sys/__tests__/config-model-ladders.test.js +23 -30
  77. package/dist/commands/sys/__tests__/sync-project-guidance.test.js +37 -11
  78. package/dist/commands/sys/branch.d.ts +2 -0
  79. package/dist/commands/sys/branch.js +19 -0
  80. package/dist/commands/sys/config.js +106 -33
  81. package/dist/commands/sys/daemon.d.ts +2 -0
  82. package/dist/commands/sys/daemon.js +130 -7
  83. package/dist/commands/sys/doctor.js +5 -15
  84. package/dist/commands/sys/migrate.js +21 -92
  85. package/dist/commands/sys/setup-settings-runner.js +4 -2
  86. package/dist/commands/sys.js +2 -15
  87. package/dist/core/__tests__/boot-fault-attempt.test.js +23 -1
  88. package/dist/core/__tests__/boot.test.js +32 -25
  89. package/dist/core/__tests__/broker-launch-candidates.test.js +49 -0
  90. package/dist/core/__tests__/canvas-inbox-watcher-hold.test.js +149 -176
  91. package/dist/core/__tests__/canvas-inbox-watcher-naming.test.js +59 -10
  92. package/dist/core/__tests__/canvas-inbox-watcher.test.js +76 -20
  93. package/dist/core/__tests__/canvas.test.js +18 -47
  94. package/dist/core/__tests__/child-death-wake.test.js +29 -7
  95. package/dist/core/__tests__/cli-help-contract.test.js +2 -0
  96. package/dist/core/__tests__/close.test.js +29 -10
  97. package/dist/core/__tests__/collectible.test.js +2 -1
  98. package/dist/core/__tests__/config-change-delta.test.js +2 -0
  99. package/dist/core/__tests__/context-intro.test.js +17 -5
  100. package/dist/core/__tests__/cron-armed-context-failure.test.js +17 -12
  101. package/dist/core/__tests__/cron-held-settlement.test.js +21 -3
  102. package/dist/core/__tests__/cron-node-sink-parked-root.test.js +29 -10
  103. package/dist/core/__tests__/cron-self-cancel-delivery.test.js +14 -2
  104. package/dist/core/__tests__/cron-timeout-descendants.test.js +13 -1
  105. package/dist/core/__tests__/daemon-boot.test.js +51 -14
  106. package/dist/core/__tests__/fixtures/fake-engine.d.ts +1 -1
  107. package/dist/core/__tests__/fixtures/fake-engine.js +8 -3
  108. package/dist/core/__tests__/focuses.test.js +10 -5
  109. package/dist/core/__tests__/helpers/harness.js +50 -18
  110. package/dist/core/__tests__/history-inbox.test.js +12 -16
  111. package/dist/core/__tests__/human-action-delivery.test.js +48 -28
  112. package/dist/core/__tests__/human-actions.test.js +7 -3
  113. package/dist/core/__tests__/inline-memory-refs.test.js +7 -2
  114. package/dist/core/__tests__/integration/broker-fork-seam.test.js +37 -10
  115. package/dist/core/__tests__/integration/broker-sdk-wiring.test.js +42 -0
  116. package/dist/core/__tests__/integration/broker-snapshot-history.test.js +42 -29
  117. package/dist/core/__tests__/integration/broker-wedge-supervision.test.js +36 -35
  118. package/dist/core/__tests__/integration/command-plugins.test.js +15 -6
  119. package/dist/core/__tests__/integration/deferred-no-wake.test.js +113 -92
  120. package/dist/core/__tests__/integration/host-teardown-process-group.test.js +9 -5
  121. package/dist/core/__tests__/integration/lifecycle-hooks.test.js +312 -1
  122. package/dist/core/__tests__/integration/plugin-revalidate.test.js +4 -0
  123. package/dist/core/__tests__/integration/profile-pause.test.js +4 -4
  124. package/dist/core/__tests__/integration/refresh-stall-recycle.test.js +9 -12
  125. package/dist/core/__tests__/integration/revive.test.js +67 -21
  126. package/dist/core/__tests__/integration/spawn-root.test.js +62 -19
  127. package/dist/core/__tests__/integration/subscription-delivery.test.js +33 -21
  128. package/dist/core/__tests__/integration/worktree-land.test.js +5 -2
  129. package/dist/core/__tests__/integration/worktree-reap.test.js +15 -1
  130. package/dist/core/__tests__/kickoff.test.js +36 -39
  131. package/dist/core/__tests__/lifecycle-terminal-reasons.test.js +2 -1
  132. package/dist/core/__tests__/lifecycle.test.js +2 -1
  133. package/dist/core/__tests__/live-lifecycle-prompt.test.js +13 -1
  134. package/dist/core/__tests__/memory-resolver-precedence.test.js +2 -0
  135. package/dist/core/__tests__/migration.test.js +145 -31
  136. package/dist/core/__tests__/model-pin-durability.test.js +4 -1
  137. package/dist/core/__tests__/model-routes-config.test.js +13 -5
  138. package/dist/core/__tests__/node-outcome.test.js +93 -26
  139. package/dist/core/__tests__/on-read-crouter-home-fence.test.js +4 -3
  140. package/dist/core/__tests__/on-read-dedup-resume.test.js +8 -7
  141. package/dist/core/__tests__/on-read-nested-store.test.js +8 -7
  142. package/dist/core/__tests__/passive-subscription.test.js +50 -157
  143. package/dist/core/__tests__/plugin-kinds.test.js +6 -4
  144. package/dist/core/__tests__/plugin-link-symlinked-scope-root.test.js +6 -1
  145. package/dist/core/__tests__/plugin-page-components.test.js +6 -4
  146. package/dist/core/__tests__/preview-registry-sync.test.js +2 -0
  147. package/dist/core/__tests__/profile-project-memory-delivery.test.js +35 -32
  148. package/dist/core/__tests__/prune.test.js +11 -5
  149. package/dist/core/__tests__/push-final-guard.test.js +14 -7
  150. package/dist/core/__tests__/relaunch-root.test.js +30 -2
  151. package/dist/core/__tests__/remote-canvas-target.test.js +7 -1
  152. package/dist/core/__tests__/repository-root-identity.test.js +4 -0
  153. package/dist/core/__tests__/review-model-floor.test.js +6 -3
  154. package/dist/core/__tests__/revive-all.test.js +13 -3
  155. package/dist/core/__tests__/revive-capacity.test.js +26 -4
  156. package/dist/core/__tests__/revive-parked-fresh.test.js +31 -8
  157. package/dist/core/__tests__/seam/broker-attach-stream.test.js +1 -1
  158. package/dist/core/__tests__/seam/broker-cap-freeze.test.js +4 -3
  159. package/dist/core/__tests__/seam/broker-crash-teardown.test.js +11 -14
  160. package/dist/core/__tests__/seam/broker-provider-retry.test.js +85 -71
  161. package/dist/core/__tests__/seam/dormancy-release.test.js +33 -19
  162. package/dist/core/__tests__/seam/held-deferred-human-prompt.test.js +7 -5
  163. package/dist/core/__tests__/seam/inbox-reference-guidance.test.js +20 -6
  164. package/dist/core/__tests__/seam/node-outcome-delivery.test.js +27 -3
  165. package/dist/core/__tests__/spawn-worktree-id-conflict.test.js +5 -4
  166. package/dist/core/__tests__/stop-guard.test.js +2 -1
  167. package/dist/core/__tests__/subscription-pool-credential-guard.test.js +4 -3
  168. package/dist/core/__tests__/warm-claim-preference-snapshot.test.js +19 -5
  169. package/dist/core/__tests__/yield-ensures-daemon.test.js +2 -1
  170. package/dist/core/bootstrap.js +2 -0
  171. package/dist/core/broker-client/__tests__/transport-socket.test.js +51 -6
  172. package/dist/core/broker-client/transport-socket.d.ts +1 -0
  173. package/dist/core/broker-client/transport-socket.js +13 -5
  174. package/dist/core/canvas/__tests__/attention.test.js +29 -35
  175. package/dist/core/canvas/__tests__/history-transcript.test.js +6 -6
  176. package/dist/core/canvas/__tests__/render-remote.test.js +25 -9
  177. package/dist/core/canvas/attention.d.ts +2 -5
  178. package/dist/core/canvas/attention.js +15 -64
  179. package/dist/core/canvas/boot.d.ts +6 -7
  180. package/dist/core/canvas/boot.js +5 -9
  181. package/dist/core/canvas/canvas.d.ts +13 -109
  182. package/dist/core/canvas/canvas.js +140 -401
  183. package/dist/core/canvas/crons.d.ts +2 -0
  184. package/dist/core/canvas/crons.js +16 -15
  185. package/dist/core/canvas/daemon-owner.js +10 -0
  186. package/dist/core/canvas/db.d.ts +2 -2
  187. package/dist/core/canvas/db.js +2 -18
  188. package/dist/core/canvas/deliveries.d.ts +104 -0
  189. package/dist/core/canvas/deliveries.js +354 -0
  190. package/dist/core/canvas/executions.d.ts +29 -0
  191. package/dist/core/canvas/executions.js +68 -0
  192. package/dist/core/canvas/history.js +35 -35
  193. package/dist/core/canvas/index.d.ts +3 -1
  194. package/dist/core/canvas/index.js +3 -1
  195. package/dist/core/canvas/migrations.js +20 -12
  196. package/dist/core/canvas/nav-model.js +1 -3
  197. package/dist/core/canvas/nav-render.d.ts +4 -4
  198. package/dist/core/canvas/nav-render.js +11 -21
  199. package/dist/core/canvas/node-sql-migration.d.ts +3 -0
  200. package/dist/core/canvas/node-sql-migration.js +261 -0
  201. package/dist/core/canvas/paths.d.ts +2 -16
  202. package/dist/core/canvas/paths.js +2 -31
  203. package/dist/core/canvas/recovery.d.ts +76 -0
  204. package/dist/core/canvas/recovery.js +588 -0
  205. package/dist/core/canvas/remote-canvas-source.js +4 -0
  206. package/dist/core/canvas/render-source.d.ts +16 -27
  207. package/dist/core/canvas/render-source.js +16 -45
  208. package/dist/core/canvas/sessions.d.ts +33 -0
  209. package/dist/core/canvas/sessions.js +78 -0
  210. package/dist/core/canvas/types.d.ts +33 -33
  211. package/dist/core/canvas/types.js +2 -5
  212. package/dist/core/canvas/waits.d.ts +18 -0
  213. package/dist/core/canvas/waits.js +70 -0
  214. package/dist/core/canvas/worktrees.d.ts +40 -0
  215. package/dist/core/canvas/worktrees.js +93 -0
  216. package/dist/core/command-hooks/artifact.d.ts +6 -0
  217. package/dist/core/command-hooks/artifact.js +22 -6
  218. package/dist/core/command-hooks/discovery.d.ts +2 -0
  219. package/dist/core/command-hooks/discovery.js +2 -0
  220. package/dist/core/command-hooks/lifecycle-catalog.d.ts +20 -2
  221. package/dist/core/command-hooks/lifecycle-catalog.js +7 -2
  222. package/dist/core/command-hooks/lifecycle-create.d.ts +4 -0
  223. package/dist/core/command-hooks/lifecycle-create.js +51 -0
  224. package/dist/core/command-hooks/schema.d.ts +2 -2
  225. package/dist/core/command-hooks/schema.js +5 -4
  226. package/dist/core/command-hooks/transport/exec-invoke.js +3 -1
  227. package/dist/core/command-hooks/transport/exec-lifecycle.d.ts +18 -8
  228. package/dist/core/command-hooks/transport/exec-lifecycle.js +89 -30
  229. package/dist/core/config.d.ts +17 -12
  230. package/dist/core/config.js +106 -155
  231. package/dist/core/errors.d.ts +1 -0
  232. package/dist/core/errors.js +4 -0
  233. package/dist/core/feed/__tests__/inbox-migrate.test.js +27 -31
  234. package/dist/core/feed/feed.d.ts +0 -6
  235. package/dist/core/feed/feed.js +68 -56
  236. package/dist/core/feed/inbox.d.ts +7 -81
  237. package/dist/core/feed/inbox.js +46 -378
  238. package/dist/core/feed/messages.d.ts +58 -0
  239. package/dist/core/feed/messages.js +217 -0
  240. package/dist/core/fs-utils.d.ts +4 -0
  241. package/dist/core/fs-utils.js +21 -1
  242. package/dist/core/human/answer.d.ts +1 -1
  243. package/dist/core/human/answer.js +1 -1
  244. package/dist/core/human/claim.d.ts +1 -1
  245. package/dist/core/human/claim.js +2 -4
  246. package/dist/core/human/component-docs.js +1 -1
  247. package/dist/core/human/feedback-companion.js +4 -3
  248. package/dist/core/human/feedback.d.ts +5 -8
  249. package/dist/core/human/feedback.js +57 -85
  250. package/dist/core/human/page-authoring.d.ts +30 -0
  251. package/dist/core/human/page-authoring.js +19 -0
  252. package/dist/core/human/requests.d.ts +136 -0
  253. package/dist/core/human/requests.js +515 -0
  254. package/dist/core/human/root.d.ts +0 -7
  255. package/dist/core/human/root.js +0 -17
  256. package/dist/core/human/scan.d.ts +2 -7
  257. package/dist/core/human/scan.js +48 -132
  258. package/dist/core/human/summary.d.ts +1 -1
  259. package/dist/core/human/summary.js +1 -1
  260. package/dist/core/human/tickets.d.ts +23 -83
  261. package/dist/core/human/tickets.js +92 -243
  262. package/dist/core/human/types.d.ts +5 -1
  263. package/dist/core/installed-plugins.js +8 -3
  264. package/dist/core/keybindings/__tests__/resolve.test.js +19 -17
  265. package/dist/core/locked-file.d.ts +6 -3
  266. package/dist/core/locked-file.js +36 -9
  267. package/dist/core/memory/extensions.js +2 -0
  268. package/dist/core/preview-registry.js +0 -1
  269. package/dist/core/profiles/__tests__/env-store.test.js +2 -0
  270. package/dist/core/profiles/default-binding.d.ts +0 -4
  271. package/dist/core/profiles/default-binding.js +12 -58
  272. package/dist/core/profiles/env-store.js +8 -3
  273. package/dist/core/profiles/manifest.d.ts +2 -37
  274. package/dist/core/profiles/manifest.js +35 -161
  275. package/dist/core/profiles/select.js +3 -0
  276. package/dist/core/review/__tests__/capture-origin.test.js +13 -2
  277. package/dist/core/review/__tests__/comments.test.js +28 -10
  278. package/dist/core/review/__tests__/remap.test.js +28 -10
  279. package/dist/core/review/__tests__/stage-identity.test.js +17 -7
  280. package/dist/core/review/birth.d.ts +1 -1
  281. package/dist/core/review/birth.js +34 -5
  282. package/dist/core/review/comments.js +1 -1
  283. package/dist/core/review/companion.d.ts +1 -1
  284. package/dist/core/review/companion.js +2 -2
  285. package/dist/core/review/document.js +1 -1
  286. package/dist/core/review/project.d.ts +5 -1
  287. package/dist/core/review/project.js +14 -36
  288. package/dist/core/review/realize.d.ts +3 -6
  289. package/dist/core/review/realize.js +51 -168
  290. package/dist/core/review/remap.js +1 -1
  291. package/dist/core/review/stage.d.ts +3 -2
  292. package/dist/core/review/stage.js +2 -3
  293. package/dist/core/review/store.d.ts +3 -21
  294. package/dist/core/review/store.js +140 -186
  295. package/dist/core/review/types.d.ts +0 -1
  296. package/dist/core/runtime/__tests__/install-id-cache-scoping.test.js +9 -5
  297. package/dist/core/runtime/__tests__/spawn-env.test.js +8 -9
  298. package/dist/core/runtime/broker/auth-reload.js +9 -10
  299. package/dist/core/runtime/broker/daemon-ops.d.ts +25 -8
  300. package/dist/core/runtime/broker/daemon-ops.js +42 -9
  301. package/dist/core/runtime/broker/extension-abort.d.ts +3 -0
  302. package/dist/core/runtime/broker/extension-abort.js +11 -0
  303. package/dist/core/runtime/broker/fault-retry.d.ts +15 -6
  304. package/dist/core/runtime/broker/fault-retry.js +142 -95
  305. package/dist/core/runtime/broker/inbox.d.ts +4 -32
  306. package/dist/core/runtime/broker/inbox.js +4 -92
  307. package/dist/core/runtime/broker/rebind.d.ts +1 -0
  308. package/dist/core/runtime/broker/rebind.js +21 -12
  309. package/dist/core/runtime/broker-persona-guidance.d.ts +1 -1
  310. package/dist/core/runtime/broker-persona-guidance.js +2 -13
  311. package/dist/core/runtime/broker.js +28 -22
  312. package/dist/core/runtime/close.d.ts +3 -9
  313. package/dist/core/runtime/close.js +16 -27
  314. package/dist/core/runtime/fault.d.ts +3 -93
  315. package/dist/core/runtime/fault.js +9 -419
  316. package/dist/core/runtime/kickoff.d.ts +3 -6
  317. package/dist/core/runtime/kickoff.js +12 -18
  318. package/dist/core/runtime/launch.js +1 -1
  319. package/dist/core/runtime/lifecycle.js +40 -7
  320. package/dist/core/runtime/nodes.d.ts +5 -1
  321. package/dist/core/runtime/nodes.js +26 -11
  322. package/dist/core/runtime/placement.js +2 -2
  323. package/dist/core/runtime/recycle.js +24 -14
  324. package/dist/core/runtime/reset.js +26 -7
  325. package/dist/core/runtime/revive.js +226 -217
  326. package/dist/core/runtime/spawn.d.ts +4 -1
  327. package/dist/core/runtime/spawn.js +43 -5
  328. package/dist/core/runtime/warm-pool.d.ts +2 -2
  329. package/dist/core/runtime/warm-pool.js +23 -3
  330. package/dist/core/scoped-state/db.d.ts +7 -0
  331. package/dist/core/scoped-state/db.js +92 -0
  332. package/dist/core/scoped-state/migrate.d.ts +11 -0
  333. package/dist/core/scoped-state/migrate.js +198 -0
  334. package/dist/core/scoped-state/paths.d.ts +9 -0
  335. package/dist/core/scoped-state/paths.js +27 -0
  336. package/dist/core/scoped-state/profiles.d.ts +12 -0
  337. package/dist/core/scoped-state/profiles.js +62 -0
  338. package/dist/core/scoped-state/providers.d.ts +16 -0
  339. package/dist/core/scoped-state/providers.js +8 -0
  340. package/dist/core/scoped-state/schema.d.ts +3 -0
  341. package/dist/core/scoped-state/schema.js +26 -0
  342. package/dist/core/scoped-state/settings.d.ts +28 -0
  343. package/dist/core/scoped-state/settings.js +82 -0
  344. package/dist/core/subscription-state.d.ts +29 -48
  345. package/dist/core/subscription-state.js +176 -712
  346. package/dist/core/worktree-close.js +4 -4
  347. package/dist/core/worktree-mutation-async.js +2 -2
  348. package/dist/core/worktree-sweep.js +11 -16
  349. package/dist/core/worktree.js +6 -12
  350. package/dist/daemon/__tests__/autostart-storm.test.js +1 -39
  351. package/dist/daemon/__tests__/helpers/source-daemon.d.ts +2 -2
  352. package/dist/daemon/__tests__/helpers/source-daemon.js +7 -6
  353. package/dist/daemon/__tests__/integration/migration-startup.test.js +59 -26
  354. package/dist/daemon/__tests__/node-deadline-lane.test.js +2 -1
  355. package/dist/daemon/__tests__/node-outcome-birth-invariants.test.js +41 -17
  356. package/dist/daemon/__tests__/node-outcome-delivery.test.js +39 -10
  357. package/dist/daemon/__tests__/reconciler-signature.test.js +2 -2
  358. package/dist/daemon/__tests__/subtree-filter-freshness.test.js +2 -2
  359. package/dist/daemon/api/__tests__/broker-settle-park.test.js +17 -12
  360. package/dist/daemon/api/__tests__/canvas-snapshot-fields.test.js +27 -5
  361. package/dist/daemon/api/__tests__/model-config.test.js +8 -8
  362. package/dist/daemon/api/__tests__/node-create-description.test.js +22 -5
  363. package/dist/daemon/api/__tests__/node-model-validation.test.js +21 -1
  364. package/dist/daemon/api/__tests__/nodes-activity-query.test.js +10 -12
  365. package/dist/daemon/api/__tests__/profile-launch-gates.test.js +6 -3
  366. package/dist/daemon/api/__tests__/profile-metadata-route.test.js +4 -1
  367. package/dist/daemon/api/__tests__/reopen-delivery.test.js +26 -8
  368. package/dist/daemon/api/__tests__/seam/api-server.test.js +24 -32
  369. package/dist/daemon/api/__tests__/seam/b10-attach-modelauth.test.js +4 -1
  370. package/dist/daemon/api/__tests__/seam/leaf-api-parity.test.js +5 -4
  371. package/dist/daemon/api/__tests__/seam/profile-delete.test.js +29 -43
  372. package/dist/daemon/api/__tests__/submit-result.test.js +3 -2
  373. package/dist/daemon/api/bridge.js +1 -1
  374. package/dist/daemon/api/handlers/bash-jobs.js +6 -7
  375. package/dist/daemon/api/handlers/broker-ops.js +47 -84
  376. package/dist/daemon/api/handlers/broker-recovery.d.ts +4 -0
  377. package/dist/daemon/api/handlers/broker-recovery.js +251 -0
  378. package/dist/daemon/api/handlers/canvas.js +2 -44
  379. package/dist/daemon/api/handlers/daemon.js +41 -0
  380. package/dist/daemon/api/handlers/feedback-comments.js +2 -2
  381. package/dist/daemon/api/handlers/health.js +10 -5
  382. package/dist/daemon/api/handlers/human-requests.js +168 -188
  383. package/dist/daemon/api/handlers/human.js +3 -148
  384. package/dist/daemon/api/handlers/inbox-page.d.ts +4 -0
  385. package/dist/daemon/api/handlers/inbox-page.js +89 -0
  386. package/dist/daemon/api/handlers/inbox.js +21 -120
  387. package/dist/daemon/api/handlers/messages.d.ts +1 -0
  388. package/dist/daemon/api/handlers/messages.js +73 -15
  389. package/dist/daemon/api/handlers/node-outcomes.js +58 -13
  390. package/dist/daemon/api/handlers/nodes.d.ts +1 -0
  391. package/dist/daemon/api/handlers/nodes.js +41 -42
  392. package/dist/daemon/api/handlers/reviews.js +4 -29
  393. package/dist/daemon/api/map.d.ts +25 -3
  394. package/dist/daemon/api/map.js +84 -14
  395. package/dist/daemon/api/server.js +68 -9
  396. package/dist/daemon/control.d.ts +18 -0
  397. package/dist/daemon/cron/disposition.d.ts +2 -0
  398. package/dist/daemon/cron/disposition.js +8 -5
  399. package/dist/daemon/cron/settle.js +37 -13
  400. package/dist/daemon/cron/sinks.d.ts +18 -1
  401. package/dist/daemon/cron/sinks.js +47 -93
  402. package/dist/daemon/crtrd-cli.js +4 -19
  403. package/dist/daemon/crtrd.d.ts +2 -0
  404. package/dist/daemon/crtrd.js +206 -81
  405. package/dist/daemon/fleet.js +5 -6
  406. package/dist/daemon/human/finish.d.ts +12 -24
  407. package/dist/daemon/human/finish.js +88 -285
  408. package/dist/daemon/human/settle.d.ts +40 -0
  409. package/dist/daemon/human/settle.js +89 -0
  410. package/dist/daemon/human/sweep.d.ts +20 -5
  411. package/dist/daemon/human/sweep.js +56 -60
  412. package/dist/daemon/manage.d.ts +21 -16
  413. package/dist/daemon/manage.js +85 -51
  414. package/dist/daemon/messaging/node-message.d.ts +1 -1
  415. package/dist/daemon/messaging/node-message.js +9 -12
  416. package/dist/daemon/park-activity.js +3 -3
  417. package/dist/daemon/reconcilers/bash-deadline.js +6 -7
  418. package/dist/daemon/reconcilers/broker-supervision.d.ts +1 -1
  419. package/dist/daemon/reconcilers/broker-supervision.js +41 -19
  420. package/dist/daemon/reconcilers/controller-death.js +6 -18
  421. package/dist/daemon/reconcilers/delivery-lane.d.ts +9 -0
  422. package/dist/daemon/reconcilers/delivery-lane.js +130 -0
  423. package/dist/daemon/reconcilers/live-obligation.js +3 -3
  424. package/dist/daemon/reconcilers/node-deadline.js +1 -1
  425. package/dist/daemon/reconcilers/node-lifecycle/freeze-lane.d.ts +1 -1
  426. package/dist/daemon/reconcilers/node-lifecycle/freeze-lane.js +10 -12
  427. package/dist/daemon/reconcilers/node-lifecycle/tick.js +5 -5
  428. package/dist/daemon/reconcilers/node-lifecycle/wants-execution.js +1 -1
  429. package/dist/daemon/reconcilers/pending-review-submit.js +3 -3
  430. package/dist/daemon/reconcilers/storage-maintenance.d.ts +2 -8
  431. package/dist/daemon/reconcilers/storage-maintenance.js +13 -43
  432. package/dist/daemon/review/companion.js +5 -11
  433. package/dist/daemon/review/deliver.d.ts +1 -5
  434. package/dist/daemon/review/deliver.js +3 -52
  435. package/dist/daemon/review/finish.d.ts +2 -27
  436. package/dist/daemon/review/finish.js +79 -263
  437. package/dist/daemon/review/sweep.d.ts +3 -10
  438. package/dist/daemon/review/sweep.js +13 -131
  439. package/dist/daemon/startup-policy.d.ts +0 -5
  440. package/dist/daemon/startup-policy.js +0 -5
  441. package/dist/hook-authoring.d.ts +28 -2
  442. package/dist/hook-authoring.js +53 -1
  443. package/dist/index.d.ts +3 -2
  444. package/dist/index.js +1 -1
  445. package/dist/migrations/__tests__/activation-concurrency.test.js +3 -2
  446. package/dist/migrations/__tests__/activation.test.js +9 -6
  447. package/dist/migrations/activation.d.ts +3 -2
  448. package/dist/migrations/activation.js +2 -4
  449. package/dist/migrations/corpus.d.ts +2 -5
  450. package/dist/migrations/corpus.js +9 -20
  451. package/dist/migrations/runner.d.ts +3 -1
  452. package/dist/migrations/runner.js +6 -2
  453. package/dist/pi-extensions/__tests__/canvas-context-intro.test.js +21 -89
  454. package/dist/pi-extensions/__tests__/canvas-goal-capture-envelope.test.js +4 -2
  455. package/dist/pi-extensions/__tests__/canvas-stophook-agentend.test.js +51 -33
  456. package/dist/pi-extensions/__tests__/canvas-stophook-context-nudge.test.js +16 -2
  457. package/dist/pi-extensions/__tests__/pre-command-gate.test.js +13 -1
  458. package/dist/pi-extensions/canvas-inbox-watcher.js +98 -244
  459. package/dist/pi-extensions/canvas-passive-context.d.ts +4 -8
  460. package/dist/pi-extensions/canvas-passive-context.js +85 -37
  461. package/dist/pi-extensions/canvas-stophook.js +47 -31
  462. package/dist/shared/env.d.ts +1 -0
  463. package/dist/shared/env.js +3 -0
  464. package/package.json +1 -1
  465. package/runtime.lock.json +5 -5
  466. package/dist/clients/attach/__tests__/page-block.test.js +0 -54
  467. package/dist/clients/inbox/review-visibility.d.ts +0 -5
  468. package/dist/clients/inbox/review-visibility.js +0 -19
  469. package/dist/commands/canvas-rebuild-index.d.ts +0 -2
  470. package/dist/commands/canvas-rebuild-index.js +0 -46
  471. package/dist/core/__tests__/human-cancel-guard.test.js +0 -110
  472. package/dist/core/__tests__/human-deliver.test.d.ts +0 -1
  473. package/dist/core/__tests__/human-deliver.test.js +0 -256
  474. package/dist/core/__tests__/human-node-not-supervised.test.d.ts +0 -1
  475. package/dist/core/__tests__/human-node-not-supervised.test.js +0 -89
  476. package/dist/core/__tests__/integration/human-deliver-e2e.test.d.ts +0 -1
  477. package/dist/core/__tests__/integration/human-deliver-e2e.test.js +0 -131
  478. package/dist/core/__tests__/phase4-review-store.test.d.ts +0 -1
  479. package/dist/core/__tests__/phase4-review-store.test.js +0 -144
  480. package/dist/core/__tests__/watchdog-abort-arms-retry.test.d.ts +0 -1
  481. package/dist/core/__tests__/watchdog-abort-arms-retry.test.js +0 -110
  482. package/dist/core/canvas/human-deliveries.d.ts +0 -39
  483. package/dist/core/canvas/human-deliveries.js +0 -75
  484. package/dist/core/canvas/node-outcome-deliveries.d.ts +0 -64
  485. package/dist/core/canvas/node-outcome-deliveries.js +0 -159
  486. package/dist/core/feed/passive.d.ts +0 -3
  487. package/dist/core/feed/passive.js +0 -53
  488. package/dist/core/human/__tests__/inbox-cancel-worker.d.ts +0 -1
  489. package/dist/core/human/__tests__/inbox-cancel-worker.js +0 -7
  490. package/dist/core/human/__tests__/inbox-claim-worker.d.ts +0 -1
  491. package/dist/core/human/__tests__/inbox-claim-worker.js +0 -17
  492. package/dist/core/human/__tests__/inbox-stale-claim-worker.d.ts +0 -1
  493. package/dist/core/human/__tests__/inbox-stale-claim-worker.js +0 -8
  494. package/dist/core/human/__tests__/integration/inbox-core.test.d.ts +0 -1
  495. package/dist/core/human/__tests__/integration/inbox-core.test.js +0 -145
  496. package/dist/core/human/__tests__/page-tickets.test.d.ts +0 -1
  497. package/dist/core/human/__tests__/page-tickets.test.js +0 -161
  498. package/dist/core/review/ticket-filter.d.ts +0 -17
  499. package/dist/core/review/ticket-filter.js +0 -21
  500. package/dist/core/runtime/broker/passive.d.ts +0 -17
  501. package/dist/core/runtime/broker/passive.js +0 -44
  502. package/dist/core/runtime/busy.d.ts +0 -46
  503. package/dist/core/runtime/busy.js +0 -150
  504. package/dist/daemon/__tests__/startup-block-marker.test.d.ts +0 -1
  505. package/dist/daemon/__tests__/startup-block-marker.test.js +0 -86
  506. package/dist/daemon/api/__tests__/review-terminal-conflict.test.d.ts +0 -1
  507. package/dist/daemon/api/__tests__/review-terminal-conflict.test.js +0 -98
  508. package/dist/daemon/human/deliver-action.d.ts +0 -3
  509. package/dist/daemon/human/deliver-action.js +0 -9
  510. package/dist/daemon/node-outcome/deliver-outcome.d.ts +0 -3
  511. package/dist/daemon/node-outcome/deliver-outcome.js +0 -9
  512. package/dist/daemon/reconcilers/human-delivery-lane.d.ts +0 -10
  513. package/dist/daemon/reconcilers/human-delivery-lane.js +0 -41
  514. package/dist/daemon/reconcilers/node-outcome-delivery-lane.d.ts +0 -10
  515. package/dist/daemon/reconcilers/node-outcome-delivery-lane.js +0 -41
  516. package/dist/daemon/startup-block-marker.d.ts +0 -25
  517. package/dist/daemon/startup-block-marker.js +0 -99
  518. /package/dist/{clients/attach/__tests__/page-block.test.d.ts → api/dto/mail.js} +0 -0
  519. /package/dist/{commands/sys/__tests__/migrate.test.d.ts → api/dto/recovery.js} +0 -0
  520. /package/dist/{core/__tests__/human-cancel-guard.test.d.ts → commands/__tests__/integration/migrate.test.d.ts} +0 -0
@@ -1,145 +0,0 @@
1
- import assert from 'node:assert/strict';
2
- import { existsSync, mkdirSync, mkdtempSync, readFileSync, realpathSync, rmSync, writeFileSync } from 'node:fs';
3
- import { spawn } from 'node:child_process';
4
- import { tmpdir } from 'node:os';
5
- import { join } from 'node:path';
6
- import { submitPage, submitReview, completePage, cancelTicket, readTicketResult } from '../../tickets.js';
7
- import { claimTicket } from '../../claim.js';
8
- import { scanInbox } from '../../scan.js';
9
- import { atomicWriteJson, progressPath, responsePath } from '../../convention.js';
10
- import { ticketDir } from '../../root.js';
11
- const temp = mkdtempSync(join(tmpdir(), 'crtr-inbox-core-'));
12
- process.env['CRTR_HOME'] = temp;
13
- const source = join(temp, 'source.md');
14
- writeFileSync(source, '# Source\n');
15
- const pageSource = join(temp, 'page.tsx');
16
- writeFileSync(pageSource, 'export default () => (\n <Page title="A page" subtitle="Go? requires your attention.">\n <UserText id="go" initialText="" />\n </Page>\n);\n');
17
- const page = (id, emittedAt) => submitPage({ dir: ticketDir(id), sourceFile: pageSource, source: { emittedAt, nodeId: 'node-1' }, delivery: { placement: 'inline', inbox: true, reply: true } });
18
- const answer = { go: { text: 'yes', edited: true } };
19
- async function waitFor(paths) {
20
- const deadline = Date.now() + 10_000;
21
- while (!paths.every(existsSync)) {
22
- if (Date.now() >= deadline)
23
- throw new Error(`workers did not reach barrier: ${paths.join(', ')}`);
24
- await new Promise((resolvePromise) => setTimeout(resolvePromise, 10));
25
- }
26
- }
27
- page('z-last', '2025-01-01T00:00:00.000Z');
28
- const newPage = page('a-first', '2025-01-02T00:00:00.000Z');
29
- const review = submitReview({ dir: ticketDir('review'), review: { file: source, title: 'Review source', subtitle: 'Review findings require your decision.', source: { nodeId: 'node-1' }, blockedSince: '2025-01-02T00:00:00.000Z' } });
30
- assert.throws(() => submitReview({ dir: ticketDir('missing-review-subtitle'), review: { file: source, title: 'Missing subtitle', source: {} } }), /subtitle/, 'a review without an authored subtitle is invalid');
31
- assert.throws(() => submitReview({ dir: ticketDir('bad-review'), review: { file: join(temp, 'missing.md'), title: 'Bad', subtitle: 'This invalid review must be rejected.', source: {} } }), /existing absolute path/);
32
- assert.equal(existsSync(ticketDir('bad-review')), false, 'invalid submission does not strand an id');
33
- const prepared = ticketDir('prepared');
34
- mkdirSync(prepared);
35
- writeFileSync(join(prepared, 'asset.png'), 'prepared asset');
36
- const preparedTicket = submitPage({ dir: prepared, sourceFile: pageSource, source: { emittedAt: '2025-01-02T00:00:00.000Z', nodeId: 'node-1' }, delivery: { placement: 'inline', inbox: true, reply: true } });
37
- assert.equal(preparedTicket.dir, realpathSync(prepared), 'submission accepts a crouter-precreated direct child and colocated assets');
38
- // Same timestamp is stable by id; progress is resumable work, never a hidden lease.
39
- atomicWriteJson(progressPath(newPage.dir), { kind: 'page', responses: {}, savedAt: new Date().toISOString() });
40
- const scanned = scanInbox();
41
- assert.deepEqual(scanned.map((item) => item.id), ['a-first', 'prepared', 'review', 'z-last']);
42
- assert.equal(scanned.find((item) => item.id === 'a-first')?.claim, undefined);
43
- assert.equal(scanned.find((item) => item.id === 'review')?.kind, 'review');
44
- assert.equal(scanned.find((item) => item.id === 'review')?.subtitle, 'Review findings require your decision.', 'review scanning exposes its required subtitle');
45
- const firstClaim = claimTicket(newPage.dir);
46
- assert.notEqual(firstClaim, null);
47
- assert.equal(claimTicket(newPage.dir), null, 'a second controller cannot claim a live ticket');
48
- // A separate process writes a genuine remote claim whose heartbeat is already
49
- // stale, then exits; this process recovers it — cross-process stale recovery.
50
- const staleDir = page('stale', '2025-01-03T00:00:00.000Z').dir;
51
- const staleClaimReady = join(temp, 'stale-claim-ready');
52
- await new Promise((resolveWorker, rejectWorker) => spawn(process.execPath, ['--conditions=crtr-src', '--import', 'tsx', 'src/core/human/__tests__/inbox-stale-claim-worker.ts', staleDir, staleClaimReady], { cwd: process.cwd(), stdio: ['ignore', 'ignore', 'inherit'] }).once('exit', (code) => code === 0 ? resolveWorker() : rejectWorker(new Error('stale-claim worker failed'))));
53
- assert.notEqual(JSON.parse(readFileSync(staleClaimReady, 'utf8')), null, 'a separate process wrote a stale remote claim on disk');
54
- assert.notEqual(claimTicket(staleDir), null, 'a remote claim written by another process recovers once its heartbeat has expired');
55
- const malformedDir = page('malformed-claim', '2025-01-03T00:00:00.000Z').dir;
56
- writeFileSync(join(malformedDir, 'claim.json'), '{');
57
- assert.notEqual(claimTicket(malformedDir), null, 'partial claim crash artifact recovers');
58
- const final = completePage(newPage.dir, answer, firstClaim.token);
59
- assert.equal(final.won, true);
60
- assert.equal(readTicketResult(newPage.dir)?.kind, 'page');
61
- assert.equal(claimTicket(newPage.dir), null, 'terminal tickets cannot be claimed');
62
- const exactResults = [
63
- { schema: 'crtr.page-response/v1', kind: 'page', responses: {}, summary: '', completedAt: '2025-01-04T00:00:00.000Z' },
64
- { schema: 'humanloop.review-response/v1', kind: 'review', result: { file: source, submitted: true, approved: true, comments: [], submittedAt: '2025-01-04T00:00:00.000Z', savedAt: '2025-01-04T00:00:00.000Z', reviewId: 'review-1', changed: false, baselineSha256: 'baseline', approvedSha256: 'approved', companionNodeId: 'companion-1', commentsTotal: 0, commentsUnresolved: 0 }, completedAt: '2025-01-04T00:00:00.000Z' },
65
- { schema: 'humanloop.cancel/v1', kind: 'canceled', canceledAt: '2025-01-04T00:00:00.000Z', reason: 'stop', actor: 'test' },
66
- ];
67
- for (const [index, result] of exactResults.entries()) {
68
- const dir = page(`strict-${index}`, '2025-01-03T00:00:00.000Z').dir;
69
- writeFileSync(responsePath(dir), JSON.stringify(result));
70
- assert.equal(readTicketResult(dir)?.kind, result.kind, `strict decoder accepts exact ${result.kind} result`);
71
- }
72
- const validFeedback = exactResults[1].result;
73
- const iso = '2025-01-04T00:00:00.000Z';
74
- const rejectFixtures = [
75
- { why: 'a wrong kind for a known schema', result: { schema: 'crtr.page-response/v1', kind: 'review', responses: {}, summary: '', completedAt: iso } },
76
- { why: 'an unknown schema id', result: { schema: 'crtr.page-response/v9', kind: 'page', responses: {}, summary: '', completedAt: iso } },
77
- { why: 'a page missing its required completedAt', result: { schema: 'crtr.page-response/v1', kind: 'page', responses: {}, summary: '' } },
78
- { why: 'a page with a non-ISO completedAt', result: { schema: 'crtr.page-response/v1', kind: 'page', responses: {}, summary: '', completedAt: 'now' } },
79
- { why: 'a page whose responses are not a slot-keyed map', result: { schema: 'crtr.page-response/v1', kind: 'page', responses: [{ id: 'go' }], summary: '', completedAt: iso } },
80
- { why: 'a page with an extra top-level field', result: { schema: 'crtr.page-response/v1', kind: 'page', responses: {}, summary: '', completedAt: iso, extra: true } },
81
- { why: 'a review missing its required result', result: { schema: 'humanloop.review-response/v1', kind: 'review', completedAt: iso } },
82
- { why: 'a review whose nested result is not submitted', result: { schema: 'humanloop.review-response/v1', kind: 'review', result: { ...validFeedback, submitted: false }, completedAt: iso } },
83
- { why: 'a review with incomplete comment columns', result: { schema: 'humanloop.review-response/v1', kind: 'review', result: { ...validFeedback, approved: true, comments: [{ id: 'c1', line: 1, endLine: 1, colStart: 1, lineText: '# Source', comment: 'x', createdAt: iso }] }, completedAt: iso } },
84
- { why: 'a review nested comment whose endLine precedes its line', result: { schema: 'humanloop.review-response/v1', kind: 'review', result: { ...validFeedback, approved: false, comments: [{ id: 'c1', line: 5, endLine: 2, lineText: '# Source', comment: 'x', createdAt: iso }] }, completedAt: iso } },
85
- { why: 'a review with an extra top-level field', result: { schema: 'humanloop.review-response/v1', kind: 'review', result: validFeedback, completedAt: iso, extra: true } },
86
- { why: 'a canceled result missing its required canceledAt', result: { schema: 'humanloop.cancel/v1', kind: 'canceled', reason: 'stop' } },
87
- { why: 'a canceled result with a non-ISO canceledAt', result: { schema: 'humanloop.cancel/v1', kind: 'canceled', canceledAt: 'now' } },
88
- { why: 'a canceled result with an extra top-level field', result: { schema: 'humanloop.cancel/v1', kind: 'canceled', canceledAt: iso, extra: true } },
89
- ];
90
- for (const [index, fixture] of rejectFixtures.entries()) {
91
- const dir = page(`strict-reject-${index}`, '2025-01-03T00:00:00.000Z').dir;
92
- writeFileSync(responsePath(dir), JSON.stringify(fixture.result));
93
- assert.equal(readTicketResult(dir), null, `strict decoder rejects ${fixture.why}`);
94
- }
95
- const protocolDir = ticketDir('stale-protocol');
96
- mkdirSync(protocolDir);
97
- writeFileSync(responsePath(protocolDir), '{}');
98
- assert.throws(() => submitPage({ dir: protocolDir, sourceFile: pageSource }), /protocol state/, 'precreated assets may not include humanloop lifecycle state');
99
- const cancelDir = page('cancel-race', '2025-01-03T00:00:00.000Z').dir;
100
- const canceled = cancelTicket(cancelDir, { reason: 'stop' });
101
- assert.equal(canceled.status, 'canceled');
102
- assert.equal(cancelTicket(cancelDir).status, 'already_resolved', 'first final writer wins');
103
- const finalRaceDir = page('final-race', '2025-01-03T00:00:00.000Z').dir;
104
- const finalRaceClaim = claimTicket(finalRaceDir);
105
- const finalBarrier = join(temp, 'final-race-barrier');
106
- const finalReady = join(temp, 'final-race-ready');
107
- const cancelWorker = spawn(process.execPath, ['--conditions=crtr-src', '--import', 'tsx', 'src/core/human/__tests__/inbox-cancel-worker.ts', finalRaceDir, finalBarrier, finalReady], { cwd: process.cwd(), stdio: ['ignore', 'pipe', 'pipe'] });
108
- await waitFor([finalReady]);
109
- writeFileSync(finalBarrier, 'go');
110
- const finalRace = completePage(finalRaceDir, answer, finalRaceClaim.token);
111
- await new Promise((resolveWorker, rejectWorker) => cancelWorker.once('exit', (code) => code === 0 ? resolveWorker() : rejectWorker(new Error('cancel worker failed'))));
112
- assert.equal(readTicketResult(finalRaceDir)?.kind, finalRace.won ? 'page' : 'canceled', 'submit and cancellation race has one canonical winner');
113
- const concurrentDir = page('concurrent-claim', '2025-01-03T00:00:00.000Z').dir;
114
- const claimBarrier = join(temp, 'claim-race-barrier');
115
- // Both workers block on this release until the parent has read both results, so
116
- // neither winner's pid dies during the peer's attempt (a dead same-host pid is
117
- // legitimately reclaimable — that stale-recovery race, not a double claim, is
118
- // what previously flaked this assertion).
119
- const claimRelease = join(temp, 'claim-race-release');
120
- let claimWorkerNumber = 0;
121
- const claimReadyPaths = [];
122
- const claimResultPaths = [];
123
- const runClaimWorker = () => new Promise((resolveWorker, rejectWorker) => {
124
- const n = claimWorkerNumber++;
125
- const ready = join(temp, `claim-race-ready-${n}`);
126
- const result = join(temp, `claim-race-result-${n}`);
127
- claimReadyPaths.push(ready);
128
- claimResultPaths.push(result);
129
- const child = spawn(process.execPath, ['--conditions=crtr-src', '--import', 'tsx', 'src/core/human/__tests__/inbox-claim-worker.ts', concurrentDir, claimBarrier, ready, result, claimRelease], { cwd: process.cwd(), stdio: ['ignore', 'ignore', 'pipe'] });
130
- let errors = '';
131
- child.stderr.on('data', (chunk) => { errors += chunk; });
132
- child.once('exit', (code) => code === 0 ? resolveWorker() : rejectWorker(new Error(errors)));
133
- });
134
- const concurrentClaimRuns = [runClaimWorker(), runClaimWorker()];
135
- await waitFor(claimReadyPaths);
136
- writeFileSync(claimBarrier, 'go');
137
- await waitFor(claimResultPaths);
138
- const concurrentClaims = claimResultPaths.map((path) => JSON.parse(readFileSync(path, 'utf8')));
139
- assert.equal(concurrentClaims.filter((claim) => claim !== null).length, 1, 'separate processes exclusively claim one ticket');
140
- writeFileSync(claimRelease, 'go');
141
- await Promise.all(concurrentClaimRuns);
142
- assert.throws(() => submitReview({ dir: ticketDir('bad-output'), review: { file: source, output: responsePath(review.dir), title: 'Bad output', subtitle: 'This invalid output path must be rejected.', source: {} } }), /must not alias/, 'review output cannot replace a ticket response');
143
- assert.throws(() => submitReview({ dir: ticketDir('source-output'), review: { file: source, output: source, title: 'Bad output', subtitle: 'This invalid output path must be rejected.', source: {} } }), /must not alias/, 'review output cannot overwrite its source');
144
- rmSync(temp, { recursive: true, force: true });
145
- console.log('inbox core tests passed');
@@ -1 +0,0 @@
1
- export {};
@@ -1,161 +0,0 @@
1
- import assert from 'node:assert/strict';
2
- import { existsSync, mkdtempSync, readFileSync, writeFileSync } from 'node:fs';
3
- import { tmpdir } from 'node:os';
4
- import { join } from 'node:path';
5
- import test from 'node:test';
6
- const home = mkdtempSync(join(tmpdir(), 'crtr-page-tickets-'));
7
- process.env['CRTR_HOME'] = home;
8
- const { atomicWriteJson, bindTicketReplyRoute, pageManifestPath, pagePath, progressPath, responsePath } = await import('../convention.js');
9
- const { claimTicket } = await import('../claim.js');
10
- const { scanInbox, scanPageHistory } = await import('../scan.js');
11
- const { ticketDir } = await import('../root.js');
12
- const { cancelTicket, completePage, readPageProgress, readPageTicket, preparePage, readTicketResult, submitPage, takeoverAndCompletePage, validatePageTicketResponses, writePageProgress, } = await import('../tickets.js');
13
- const sources = mkdtempSync(join(tmpdir(), 'crtr-page-sources-'));
14
- function sourceFile(name, body) {
15
- const path = join(sources, name);
16
- writeFileSync(path, body);
17
- return path;
18
- }
19
- const askPage = sourceFile('ask.tsx', `export default function Rollout() {
20
- return (
21
- <Page title="Rollout scope" subtitle="Pick tonight\u2019s rollout">
22
- <p>Which rollout do you want?</p>
23
- <UserQuestion id="rollout" label="Rollout lane" body="Canary limits the blast radius." mode="single" options={[{ id: 'canary', label: 'Canary first' }, { id: 'full', label: 'Full' }]} />
24
- </Page>
25
- );
26
- }
27
- `);
28
- const displayPage = sourceFile('display.tsx', `export default function Nightly() {
29
- return (
30
- <Page title="Nightly report">
31
- <h1>All green</h1>
32
- </Page>
33
- );
34
- }
35
- `);
36
- function submit(id, source, emittedAt, nodeId = 'node-1', replyExpected = true) {
37
- const dir = submitPage({ dir: ticketDir(id), sourceFile: source, source: { nodeId, emittedAt }, delivery: { placement: 'inline', inbox: replyExpected, reply: replyExpected } }).dir;
38
- if (replyExpected)
39
- bindTicketReplyRoute(dir, id);
40
- return dir;
41
- }
42
- test('submitPage publishes the authored source, the derived manifest and the emittedAt stamp', () => {
43
- const submitted = submitPage({ dir: ticketDir('ask'), sourceFile: askPage, source: { nodeId: 'node-1' }, delivery: { placement: 'inline', inbox: true, reply: true } });
44
- assert.equal(submitted.kind, 'page');
45
- assert.equal(submitted.id, 'ask');
46
- assert.equal(readFileSync(pagePath(submitted.dir), 'utf8'), readFileSync(askPage, 'utf8'));
47
- const manifest = JSON.parse(readFileSync(pageManifestPath(submitted.dir), 'utf8'));
48
- assert.equal(manifest['schema'], 'crtr.page/v2');
49
- assert.equal(manifest['document'], 'page.tsx');
50
- assert.deepEqual(manifest['delivery'], { placement: 'inline', inbox: true, reply: true });
51
- const source = manifest['source'];
52
- assert.equal(source.nodeId, 'node-1');
53
- assert.ok(Date.parse(source.emittedAt) > 0, 'submission stamps emittedAt');
54
- // A ticket's request is replaceable in place — what `human send --replace`
55
- // does. Republishing over the same directory revises the page and leaves one
56
- // ticket behind, rather than refusing or raising a second one.
57
- const stamp = { nodeId: 'node-replace', emittedAt: '2026-01-05T00:00:00.000Z' };
58
- const first = submitPage({ dir: ticketDir('revisable'), sourceFile: askPage, source: stamp });
59
- const revised = submitPage({ dir: ticketDir('revisable'), sourceFile: displayPage, source: stamp });
60
- assert.equal(revised.id, first.id);
61
- assert.equal(revised.manifest.title, 'Nightly report');
62
- assert.equal(readFileSync(pagePath(revised.dir), 'utf8'), readFileSync(displayPage, 'utf8'));
63
- assert.deepEqual(scanPageHistory('node-replace').map((entry) => entry.id), ['revisable'], 'a replaced page is one ticket, not two');
64
- bindTicketReplyRoute(submitted.dir, 'ask');
65
- const listed = scanInbox().find((item) => item.id === 'ask');
66
- assert.equal(listed?.kind, 'page');
67
- assert.equal(listed?.kind === 'page' ? listed.awaitsResponse : undefined, true);
68
- assert.deepEqual(listed?.kind === 'page' ? listed.slotKinds : undefined, ['options']);
69
- });
70
- test('an HTML page derives a slotless manifest from declared metadata', () => {
71
- const report = sourceFile('report.html', '<!doctype html><html><head><meta content="Summary" name="description"><title>Report</title></head><body>done</body></html>');
72
- const prepared = preparePage({ sourceFile: report });
73
- assert.equal(prepared.module, undefined);
74
- assert.deepEqual(prepared.manifest, { schema: 'crtr.page/v2', dialect: 'html', title: 'Report', subtitle: 'Summary', delivery: { placement: 'inline', inbox: false, reply: false }, document: 'page.html', steps: 1, slots: [], source: prepared.manifest.source });
75
- });
76
- test('progress round-trips through validation and rejects malformed saved work', () => {
77
- const dir = ticketDir('ask');
78
- assert.equal(readPageProgress(dir), null, 'no progress file reads as null');
79
- writePageProgress(dir, { rollout: { selectedOptionIds: ['canary'], comments: [] } });
80
- assert.deepEqual(readPageProgress(dir), { rollout: { selectedOptionIds: ['canary'], comments: [] } });
81
- assert.throws(() => writePageProgress(dir, { rollout: { selectedOptionIds: ['nope'], comments: [] } }), /nope/);
82
- assert.throws(() => writePageProgress(dir, { unknown: { selectedOptionIds: [], comments: [] } }), /unknown/);
83
- atomicWriteJson(progressPath(dir), { kind: 'page', responses: { rollout: 'not an object' }, savedAt: new Date().toISOString() });
84
- assert.throws(() => readPageProgress(dir), /malformed/, 'malformed progress is an error, never silently discarded');
85
- // Saved work can outlive the slot it answered, because the agent may replace
86
- // the page under it. The stale entry is dropped on read so the ticket stays
87
- // readable; every entry the current page still declares hydrates.
88
- atomicWriteJson(progressPath(dir), { kind: 'page', responses: { rollout: { selectedOptionIds: ['canary'], comments: [] }, retired: { selectedOptionIds: ['gone'], comments: [] } }, savedAt: new Date().toISOString() });
89
- assert.deepEqual(readPageProgress(dir), { rollout: { selectedOptionIds: ['canary'], comments: [] } });
90
- writePageProgress(dir, { rollout: { selectedOptionIds: ['canary'], comments: [] } });
91
- });
92
- test('readPageTicket derives pending, resolved and canceled state and carries the document', () => {
93
- const dir = ticketDir('ask');
94
- const pending = readPageTicket(dir);
95
- assert.equal(pending.state, 'pending');
96
- assert.equal(pending.manifest.title, 'Rollout scope');
97
- assert.ok(pending.document.includes('Which rollout do you want?'));
98
- assert.deepEqual(pending.progress, { rollout: { selectedOptionIds: ['canary'], comments: [] } });
99
- const claim = claimTicket(dir);
100
- assert.notEqual(claim, null, 'a page ticket is claimable');
101
- assert.throws(() => completePage(dir, { rollout: { selectedOptionIds: [], comments: [] } }, claim.token), /rollout/, 'single-select requires one selection');
102
- const completed = completePage(dir, { rollout: { selectedOptionIds: ['full'], comments: [] }, }, claim.token);
103
- assert.equal(completed.won, true);
104
- const written = JSON.parse(readFileSync(responsePath(dir), 'utf8'));
105
- assert.equal(written['schema'], 'crtr.page-response/v1');
106
- assert.equal(written['kind'], 'page');
107
- assert.deepEqual(written['responses'], { rollout: { selectedOptionIds: ['full'], comments: [] } });
108
- assert.equal(written['summary'], 'Full');
109
- assert.equal(readTicketResult(dir)?.kind, 'page', 'the strict decoder accepts the page result');
110
- assert.equal(existsSync(progressPath(dir)), false, 'completion clears owned work');
111
- const resolved = readPageTicket(dir);
112
- assert.equal(resolved.state, 'resolved');
113
- assert.equal(resolved.progress, null);
114
- assert.equal(scanInbox().some((item) => item.id === 'ask'), false, 'a resolved page leaves the pending list');
115
- const canceledDir = submit('canceled', askPage, '2026-01-03T00:00:00.000Z');
116
- cancelTicket(canceledDir, { reason: 'no longer needed' });
117
- assert.equal(readPageTicket(canceledDir).state, 'canceled');
118
- });
119
- test('page scan derives response expectation from the page shape and its reply route', () => {
120
- const dir = submit('display', displayPage, '2026-01-04T00:00:00.000Z');
121
- const ticket = readPageTicket(dir);
122
- assert.equal(ticket.manifest.dialect, 'jsx');
123
- assert.equal(ticket.manifest.slots.length, 0);
124
- const display = scanInbox().find((item) => item.id === 'display');
125
- assert.equal(display?.kind === 'page' ? display.awaitsResponse : undefined, true, 'a reply-bearing page waits even with no response-bearing slots');
126
- const announcementDir = submit('announcement-with-options', askPage, '2026-01-04T00:00:01.000Z', 'node-1', false);
127
- const announcement = scanInbox().find((item) => item.id === 'announcement-with-options');
128
- assert.equal(announcement, undefined, 'a passive page stays out of the inbox');
129
- assert.equal(readPageTicket(announcementDir).state, 'passive');
130
- assert.throws(() => takeoverAndCompletePage(announcementDir, {}, { host: 'crtrd', pid: process.pid }), /page_has_no_response_lifecycle/);
131
- const acknowledged = takeoverAndCompletePage(dir, {}, { host: 'crtrd', pid: process.pid });
132
- assert.equal(acknowledged.won, true);
133
- const written = JSON.parse(readFileSync(responsePath(dir), 'utf8'));
134
- assert.equal(written['schema'], 'crtr.page-response/v1');
135
- assert.deepEqual(written['responses'], {});
136
- assert.equal(written['summary'], '');
137
- });
138
- test('an inbox page that asks something waits on the person with no reply route', () => {
139
- const stamp = (emittedAt) => ({ nodeId: 'node-durable', emittedAt });
140
- // The programmatic request shape: answerable from the inbox, never routed to an agent bridge.
141
- const durable = submitPage({ dir: ticketDir('durable-request'), sourceFile: askPage, source: stamp('2026-01-06T00:00:00.000Z'), delivery: { placement: 'panel', inbox: true, reply: false } }).dir;
142
- submitPage({ dir: ticketDir('durable-notice'), sourceFile: displayPage, source: stamp('2026-01-06T00:00:01.000Z'), delivery: { placement: 'panel', inbox: true, reply: false } });
143
- const listed = scanInbox();
144
- const request = listed.find((item) => item.id === 'durable-request');
145
- assert.equal(request?.kind === 'page' ? request.awaitsResponse : undefined, true, 'a page with response-bearing slots waits on the person even with no bridge to reply to');
146
- const notice = listed.find((item) => item.id === 'durable-notice');
147
- assert.equal(notice?.kind === 'page' ? notice.awaitsResponse : undefined, false, 'a display-only notice waits on nothing');
148
- const claim = claimTicket(durable);
149
- const answered = completePage(durable, { rollout: { selectedOptionIds: ['canary'], comments: [] } }, claim.token);
150
- assert.equal(answered.won, true, 'a request with no reply route still takes a real answer');
151
- assert.deepEqual(JSON.parse(readFileSync(responsePath(durable), 'utf8'))['responses'], { rollout: { selectedOptionIds: ['canary'], comments: [] } });
152
- });
153
- test('scanPageHistory returns every page of one node, oldest first, regardless of terminal file', () => {
154
- submit('older', askPage, '2026-01-01T00:00:00.000Z');
155
- submit('other-node', askPage, '2026-01-02T00:00:00.000Z', 'node-2');
156
- const history = scanPageHistory('node-1');
157
- assert.deepEqual(history.map((entry) => entry.id), ['older', 'canceled', 'display', 'announcement-with-options', 'ask']);
158
- assert.deepEqual(history.map((entry) => entry.state), ['pending', 'canceled', 'resolved', 'passive', 'resolved']);
159
- assert.deepEqual(scanPageHistory('node-2').map((entry) => entry.id), ['other-node']);
160
- assert.deepEqual(scanPageHistory('node-missing'), []);
161
- });
@@ -1,17 +0,0 @@
1
- /**
2
- * Terminal-review ticket visibility filter.
3
- * Hides review tickets by canonical state.
4
- * This module never imports canvas DB directly; daemon call sites apply the filter.
5
- */
6
- import type { TicketSummary } from '../human/types.js';
7
- /**
8
- * Determines if a review ticket should be visible based on its canonical review state.
9
- * Only an open row is visible: 'binding', 'approved', and 'canceled' are hidden, and so is a
10
- * ticket with no row at all — a row is committed before its ticket is ever published.
11
- */
12
- export declare function isReviewTicketVisible(bridgeNodeId: string): boolean;
13
- /**
14
- * Filters a ticket summary array to remove hidden terminal-review tickets.
15
- * Page tickets pass through unchanged. Review tickets are filtered by state.
16
- */
17
- export declare function filterTerminalReviewTickets(items: TicketSummary[]): TicketSummary[];
@@ -1,21 +0,0 @@
1
- /**
2
- * Terminal-review ticket visibility filter.
3
- * Hides review tickets by canonical state.
4
- * This module never imports canvas DB directly; daemon call sites apply the filter.
5
- */
6
- import { getReviewByBridge } from './store.js';
7
- /**
8
- * Determines if a review ticket should be visible based on its canonical review state.
9
- * Only an open row is visible: 'binding', 'approved', and 'canceled' are hidden, and so is a
10
- * ticket with no row at all — a row is committed before its ticket is ever published.
11
- */
12
- export function isReviewTicketVisible(bridgeNodeId) {
13
- return getReviewByBridge(bridgeNodeId)?.state === 'open';
14
- }
15
- /**
16
- * Filters a ticket summary array to remove hidden terminal-review tickets.
17
- * Page tickets pass through unchanged. Review tickets are filtered by state.
18
- */
19
- export function filterTerminalReviewTickets(items) {
20
- return items.filter((item) => (item.kind === 'review' ? isReviewTicketVisible(item.id) : true));
21
- }
@@ -1,17 +0,0 @@
1
- /** Structural copy of an InboxEntry used by broker presentation only. Keeping
2
- * this type here avoids importing the feed implementation into the broker. */
3
- export interface BrokerPassiveEntry {
4
- entry_id: string;
5
- operation_id: string;
6
- ts: string;
7
- from: string | null;
8
- from_name?: string;
9
- tier: string;
10
- kind: string;
11
- ref?: string;
12
- label: string;
13
- data?: Record<string, unknown>;
14
- }
15
- /** Read and clear the passive accumulator, preserving the rename-before-read
16
- * race boundary used by the daemon's feed writer. */
17
- export declare function drainBrokerPassive(nodeId: string): BrokerPassiveEntry[];
@@ -1,44 +0,0 @@
1
- // Broker-local passive feed access. This deliberately avoids the canvas/feed
2
- // barrels: those are daemon-side modules and their import graph reaches the
3
- // canvas database. A broker only needs the node-owned JSONL accumulator.
4
- import { existsSync, readFileSync, renameSync, rmSync, } from 'node:fs';
5
- import { passivePath } from '../../canvas/paths.js';
6
- function parseEntries(text) {
7
- const entries = [];
8
- for (const line of text.split('\n')) {
9
- if (line.trim() === '')
10
- continue;
11
- try {
12
- entries.push(JSON.parse(line));
13
- }
14
- catch {
15
- // Preserve the feed's tolerant per-line corruption behavior.
16
- }
17
- }
18
- return entries;
19
- }
20
- /** Read and clear the passive accumulator, preserving the rename-before-read
21
- * race boundary used by the daemon's feed writer. */
22
- export function drainBrokerPassive(nodeId) {
23
- const path = passivePath(nodeId);
24
- if (!existsSync(path))
25
- return [];
26
- const snapshot = `${path}.draining`;
27
- try {
28
- renameSync(path, snapshot);
29
- }
30
- catch {
31
- return [];
32
- }
33
- let entries = [];
34
- try {
35
- entries = parseEntries(readFileSync(snapshot, 'utf8'));
36
- }
37
- finally {
38
- try {
39
- rmSync(snapshot, { force: true });
40
- }
41
- catch { /* best effort */ }
42
- }
43
- return entries;
44
- }
@@ -1,46 +0,0 @@
1
- export type TransportPoisonVerdict = {
2
- consecutivePasses: number;
3
- routes: string[];
4
- };
5
- /** Mark a node mid-turn (pi entered a turn). Best-effort. A new turn makes any
6
- * prior clean-abort certificate stale before it can affect a later crash. */
7
- export declare function markBusy(nodeId: string): void;
8
- /** Clear the mid-run marker after Pi settles, however it routed. Best-effort. */
9
- export declare function clearBusy(nodeId: string): void;
10
- /** Certify that this broker aborted a busy turn and Pi persisted its settled
11
- * aborted entry before teardown. Settling clears the busy marker, so the daemon
12
- * consumes this durable certificate as the surviving interrupted-turn witness
13
- * and resumes with a continuation. A dirty death instead leaves `busy` behind. */
14
- export declare function markCleanAbort(nodeId: string): void;
15
- /** Remove a consumed clean-abort certificate as a broker boots. */
16
- export declare function clearCleanAbort(nodeId: string): void;
17
- /** Record the provider-rotation verdict immediately before it terminates this broker.
18
- * It survives the broker exit so the fleet can surface the cause if this launch
19
- * exhausts respawn recovery. */
20
- export declare function recordTransportPoisonVerdict(nodeId: string, verdict: TransportPoisonVerdict): void;
21
- /** A verdict belongs to one broker launch. Clear it before every replacement so
22
- * a later unrelated crash loop cannot inherit an old transport diagnosis. */
23
- export declare function clearTransportPoisonVerdict(nodeId: string): void;
24
- /** Read only a complete, structurally valid provider-rotation verdict. */
25
- export declare function readTransportPoisonVerdict(nodeId: string): TransportPoisonVerdict | null;
26
- /** Whether the dead broker left a persisted clean-abort turn witness. */
27
- export declare function hasCleanAbort(nodeId: string): boolean;
28
- /** Is the node currently inside a turn? AND this with `pidAlive` at the call
29
- * site — a stale marker from a crashed pi is harmless because the dead pid
30
- * fails the AND. */
31
- export declare function isBusy(nodeId: string): boolean;
32
- /** Re-touch the mid-turn marker's mtime — the daemon's wedge-detection
33
- * heartbeat (crtrd.ts `handleWedgeDetection`). Called from canvas-stophook on
34
- * every engine-level sign of life WITHIN a turn (a tool starting/streaming/
35
- * ending, a streamed assistant token, a turn ending) so the marker's mtime
36
- * always reflects the last moment the engine visibly did something, not just
37
- * when the run began. No-ops when the node isn't currently busy — a touch
38
- * arriving after `clearBusy` (e.g. a late event racing agent_settled) must
39
- * never resurrect a stale marker. Best-effort. */
40
- export declare function touchBusy(nodeId: string): void;
41
- /** The mid-turn marker's mtime: the last moment ANY engine-level progress was
42
- * observed while the node was busy (see `touchBusy`). Null when the node isn't
43
- * busy or the marker is unreadable. This is the sole heartbeat the daemon's
44
- * wedge detector reads — it needs no new instrumentation beyond re-touching an
45
- * already-existing file. */
46
- export declare function busySince(nodeId: string): number | null;
@@ -1,150 +0,0 @@
1
- // busy.ts — the "is pi actually mid-turn" signal (a marker file, no db column).
2
- //
3
- // The disposition of a focus's OUTGOING node on a hot-swap (placement.ts
4
- // `outgoingDisposition`) must distinguish a terminal worker that is GENUINELY
5
- // mid-turn (keep it running off-screen, Invariant F2) from one merely PARKED at
6
- // its prompt with a live pi (a viewer revived for inspection — despawn it back to
7
- // dormant on focus-away). A live pid is NOT that signal: a parked node has a live
8
- // pid too. This marker is.
9
- //
10
- // `<jobDir>/busy` exists for exactly the span pi is inside a whole run: the
11
- // stophook touches it on `agent_start` and unlinks it at `agent_settled` (and
12
- // defensively on `session_shutdown`). It is always AND-ed with `pidAlive` at the
13
- // read site, so a stale marker (process crashed mid-turn without firing
14
- // agent_end) is harmless — the dead pid fails the AND and the node is reaped.
15
- // No db migration, atomic touch/unlink, best-effort (never throws).
16
- import { existsSync, readFileSync, writeFileSync, rmSync, mkdirSync, statSync } from 'node:fs';
17
- import { join } from 'node:path';
18
- import { jobDir } from '../canvas/paths.js';
19
- function busyPath(nodeId) {
20
- return join(jobDir(nodeId), 'busy');
21
- }
22
- function cleanAbortPath(nodeId) {
23
- return join(jobDir(nodeId), 'clean-abort');
24
- }
25
- function transportPoisonPath(nodeId) {
26
- return join(jobDir(nodeId), 'transport-poison.json');
27
- }
28
- /** Mark a node mid-turn (pi entered a turn). Best-effort. A new turn makes any
29
- * prior clean-abort certificate stale before it can affect a later crash. */
30
- export function markBusy(nodeId) {
31
- try {
32
- mkdirSync(jobDir(nodeId), { recursive: true });
33
- rmSync(cleanAbortPath(nodeId), { force: true });
34
- writeFileSync(busyPath(nodeId), '');
35
- }
36
- catch {
37
- /* best-effort */
38
- }
39
- }
40
- /** Clear the mid-run marker after Pi settles, however it routed. Best-effort. */
41
- export function clearBusy(nodeId) {
42
- try {
43
- rmSync(busyPath(nodeId), { force: true });
44
- }
45
- catch {
46
- /* best-effort */
47
- }
48
- }
49
- /** Certify that this broker aborted a busy turn and Pi persisted its settled
50
- * aborted entry before teardown. Settling clears the busy marker, so the daemon
51
- * consumes this durable certificate as the surviving interrupted-turn witness
52
- * and resumes with a continuation. A dirty death instead leaves `busy` behind. */
53
- export function markCleanAbort(nodeId) {
54
- try {
55
- mkdirSync(jobDir(nodeId), { recursive: true });
56
- writeFileSync(cleanAbortPath(nodeId), '');
57
- }
58
- catch {
59
- /* best-effort */
60
- }
61
- }
62
- /** Remove a consumed clean-abort certificate as a broker boots. */
63
- export function clearCleanAbort(nodeId) {
64
- try {
65
- rmSync(cleanAbortPath(nodeId), { force: true });
66
- }
67
- catch {
68
- /* best-effort */
69
- }
70
- }
71
- /** Record the provider-rotation verdict immediately before it terminates this broker.
72
- * It survives the broker exit so the fleet can surface the cause if this launch
73
- * exhausts respawn recovery. */
74
- export function recordTransportPoisonVerdict(nodeId, verdict) {
75
- try {
76
- mkdirSync(jobDir(nodeId), { recursive: true });
77
- writeFileSync(transportPoisonPath(nodeId), JSON.stringify(verdict));
78
- }
79
- catch {
80
- /* best-effort: no marker means the crash report remains explicitly unknown */
81
- }
82
- }
83
- /** A verdict belongs to one broker launch. Clear it before every replacement so
84
- * a later unrelated crash loop cannot inherit an old transport diagnosis. */
85
- export function clearTransportPoisonVerdict(nodeId) {
86
- try {
87
- rmSync(transportPoisonPath(nodeId), { force: true });
88
- }
89
- catch {
90
- /* best-effort */
91
- }
92
- }
93
- /** Read only a complete, structurally valid provider-rotation verdict. */
94
- export function readTransportPoisonVerdict(nodeId) {
95
- try {
96
- const value = JSON.parse(readFileSync(transportPoisonPath(nodeId), 'utf8'));
97
- if (typeof value !== 'object' || value === null)
98
- return null;
99
- const verdict = value;
100
- const consecutivePasses = verdict.consecutivePasses;
101
- const routes = verdict.routes;
102
- if (typeof consecutivePasses !== 'number' || !Number.isInteger(consecutivePasses) || consecutivePasses < 1 || !Array.isArray(routes) || routes.length === 0 || !routes.every((route) => typeof route === 'string' && route !== ''))
103
- return null;
104
- return { consecutivePasses, routes };
105
- }
106
- catch {
107
- return null;
108
- }
109
- }
110
- /** Whether the dead broker left a persisted clean-abort turn witness. */
111
- export function hasCleanAbort(nodeId) {
112
- return existsSync(cleanAbortPath(nodeId));
113
- }
114
- /** Is the node currently inside a turn? AND this with `pidAlive` at the call
115
- * site — a stale marker from a crashed pi is harmless because the dead pid
116
- * fails the AND. */
117
- export function isBusy(nodeId) {
118
- return existsSync(busyPath(nodeId));
119
- }
120
- /** Re-touch the mid-turn marker's mtime — the daemon's wedge-detection
121
- * heartbeat (crtrd.ts `handleWedgeDetection`). Called from canvas-stophook on
122
- * every engine-level sign of life WITHIN a turn (a tool starting/streaming/
123
- * ending, a streamed assistant token, a turn ending) so the marker's mtime
124
- * always reflects the last moment the engine visibly did something, not just
125
- * when the run began. No-ops when the node isn't currently busy — a touch
126
- * arriving after `clearBusy` (e.g. a late event racing agent_settled) must
127
- * never resurrect a stale marker. Best-effort. */
128
- export function touchBusy(nodeId) {
129
- try {
130
- if (!existsSync(busyPath(nodeId)))
131
- return;
132
- writeFileSync(busyPath(nodeId), '');
133
- }
134
- catch {
135
- /* best-effort */
136
- }
137
- }
138
- /** The mid-turn marker's mtime: the last moment ANY engine-level progress was
139
- * observed while the node was busy (see `touchBusy`). Null when the node isn't
140
- * busy or the marker is unreadable. This is the sole heartbeat the daemon's
141
- * wedge detector reads — it needs no new instrumentation beyond re-touching an
142
- * already-existing file. */
143
- export function busySince(nodeId) {
144
- try {
145
- return statSync(busyPath(nodeId)).mtimeMs;
146
- }
147
- catch {
148
- return null;
149
- }
150
- }
@@ -1 +0,0 @@
1
- export {};