@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,24 +1,24 @@
1
- import { existsSync, statSync, watch } from 'node:fs';
2
- import { basename } from 'node:path';
1
+ import { existsSync, watch } from 'node:fs';
3
2
  import { visibleWidth } from '@earendil-works/pi-tui';
4
3
  import { renderMarkdown } from '../../core/termrender/termrender.js';
5
4
  import { BOLD, CYAN, DIM, GRAY, RESET, YELLOW, clipLine } from './tui/ansi.js';
6
5
  import { buildInboxLines, recommendedLabel } from './tui.js';
7
6
  import { inboxLayout } from './layout.js';
8
- import { scanInbox } from '../../core/human/scan.js';
9
7
  import { resolvePageComponents } from '../../core/config.js';
10
8
  import { watchInboxActivity } from '../../core/human/root.js';
11
9
  import { claimTicket, heartbeatClaim, releaseClaim } from '../../core/human/claim.js';
12
- import { readPageTicket, readTicketResult } from '../../core/human/tickets.js';
13
- import { claimPath, clearProgress, isResolved, pagePath, responsePath } from '../../core/human/convention.js';
10
+ import { claimPath, clearProgress } from '../../core/human/convention.js';
14
11
  import { emitEvent } from '../../core/events/emit.js';
12
+ import { cliClient } from '../../commands/api-client.js';
15
13
  import { projectPageDisplayMarkdown } from '../../core/human/page-markdown.js';
16
14
  import { projectHtmlDocumentMarkdown } from '../../core/human/page-html-markdown.js';
17
15
  import { pageBodyLines } from './tui/page-body.js';
16
+ import { byNewestFirst, inboxRowFromDTO, pageViewFromDTO, settledStatusOf } from './dto-adapt.js';
18
17
  import { PageAdapter } from './page-adapter.js';
19
18
  import { ReviewAdapter } from './review-adapter.js';
19
+ import { createReviewClient } from './review/review-client.js';
20
20
  import { editBufferInEditor } from './review/roundtrip.js';
21
- import { cancelTicketViaDaemon, daemonErrorMessage, ticketIdFor, resolveFailureKind, resolveTicketPage, } from './resolve.js';
21
+ import { daemonErrorMessage, dismissTicketViaDaemon, resolveFailureKind, resolveTicketPage, } from './resolve.js';
22
22
  /** Host-agnostic model for scanning, stable selection, claims, and rendering. */
23
23
  export class InboxController {
24
24
  options;
@@ -42,39 +42,66 @@ export class InboxController {
42
42
  heartbeat;
43
43
  started = false;
44
44
  closed = false;
45
- /** The selected page's projected body, kept across frames because building it
46
- * reads and evaluates the page document. Keyed by what changes it. */
45
+ /** The selected page's projection, kept across frames because fetching it is
46
+ * a round trip. `view` is held so a width change re-wraps without one. */
47
47
  preview;
48
+ /** A preview read that failed, so the panel says so instead of staying blank. */
49
+ previewFailed;
50
+ /** In-flight preview fetch, keyed by dir: one request per selection at a time. */
51
+ previewFetching;
52
+ /** Set by `invalidate`: the held preview may no longer be the current revision. */
53
+ previewStale = false;
54
+ /** The revision the open detail was loaded from, so a replacement published
55
+ * while the reader has the page open reloads it. */
56
+ openRevision;
48
57
  constructor(options) {
49
58
  this.options = options;
50
59
  this.host = options.host;
51
- this.scan = options.scan ?? (() => scanInbox());
60
+ this.scan = options.scan ?? defaultScan;
52
61
  this.cols = options.cols ?? 80;
53
62
  this.rows = options.rows ?? 24;
54
63
  // Seeded before the first rescan, which preserves a selected dir it finds.
55
64
  this.selectedDir = options.initialSelection;
56
- this.rescan();
65
+ void this.rescan();
57
66
  }
58
67
  snapshot() {
59
68
  return { items: [...this.items], selectedDir: this.selectedDir, screen: this.screen, inputBuffer: this.adapter?.inputBuffer() };
60
69
  }
61
- rescan() {
70
+ async rescan() {
62
71
  const priorDir = this.selectedDir;
63
72
  const priorIndex = this.selectedIndex;
64
- this.items = this.scan();
73
+ const claim = this.claim;
74
+ // The row list is a round trip, and most rescans are fired from inbox
75
+ // activity with nobody awaiting them. A failed read keeps the rows already
76
+ // on screen and says so in the status line, rather than rejecting into a
77
+ // caller that cannot handle it.
78
+ try {
79
+ this.items = await this.scan();
80
+ }
81
+ catch (error) {
82
+ if (this.closed)
83
+ return;
84
+ this.status = daemonErrorMessage(error);
85
+ this.markDirty();
86
+ return;
87
+ }
88
+ if (this.closed)
89
+ return;
65
90
  const found = priorDir === undefined ? -1 : this.items.findIndex((item) => item.dir === priorDir);
66
91
  if (found >= 0) {
67
92
  this.selectedIndex = found;
68
93
  this.selectedDir = priorDir;
69
94
  return;
70
95
  }
71
- if (this.adapter !== undefined && priorDir !== undefined) {
72
- const canceled = readTicketResultOrNull(priorDir)?.kind === 'canceled';
96
+ if (this.adapter !== undefined && priorDir !== undefined && claim !== undefined) {
97
+ const canceled = (await this.requestOrNull(claim.id))?.state === 'canceled';
98
+ if (this.closed)
99
+ return;
73
100
  if (canceled)
74
101
  clearProgress(priorDir);
75
102
  // Suppress the "resolved elsewhere" banner when WE are the ones resolving
76
- // this ticket: the response.json we just published trips the root watch
77
- // mid-submit, and that is our own completion, not an external event.
103
+ // this ticket: the settlement we just submitted drops the row from the
104
+ // scan mid-submit, and that is our own completion, not an external event.
78
105
  if (priorDir !== this.submittingDir)
79
106
  this.status = canceled ? 'canceled by requester' : 'ticket resolved elsewhere';
80
107
  this.leaveDetail(priorDir !== this.submittingDir);
@@ -97,7 +124,24 @@ export class InboxController {
97
124
  heartbeatClaim(this.claim.dir, this.claim.token);
98
125
  }, 10_000);
99
126
  }
100
- invalidate() { this.rescan(); this.markDirty(); }
127
+ /** Inbox activity is the one signal a replacement always emits, and it is
128
+ * emitted after the new revision is selected — so an open page reloads here
129
+ * rather than from a file watch that cannot see a directory swap. */
130
+ async invalidate() {
131
+ // Activity is the one signal a replacement always emits, and it fires after
132
+ // the new revision is selected — so the held preview and any open page are
133
+ // re-read from the daemon here rather than from a file watch, which cannot
134
+ // see a directory swap at all.
135
+ this.previewStale = true;
136
+ await this.rescan();
137
+ if (this.closed)
138
+ return;
139
+ if (this.adapter !== undefined && this.claim !== undefined) {
140
+ await this.reloadSelectedPage();
141
+ return;
142
+ }
143
+ this.markDirty();
144
+ }
101
145
  resize(cols, rows) {
102
146
  this.cols = cols;
103
147
  this.rows = rows;
@@ -149,10 +193,10 @@ export class InboxController {
149
193
  }
150
194
  if (this.cancelConfirmation !== undefined) {
151
195
  if (input === 'y' || input === 'Y') {
152
- const dir = this.cancelConfirmation.dir;
196
+ const item = this.cancelConfirmation.item;
153
197
  this.cancelConfirmation = undefined;
154
198
  this.status = 'canceling…';
155
- void this.cancelFromInbox(dir);
199
+ void this.cancelFromInbox(item);
156
200
  }
157
201
  else if (input === 'n' || input === 'N' || key.escape) {
158
202
  this.cancelConfirmation = undefined;
@@ -164,7 +208,7 @@ export class InboxController {
164
208
  if (input === 'x' || input === 'X') {
165
209
  const selected = this.items[this.selectedIndex];
166
210
  if (selected !== undefined) {
167
- this.cancelConfirmation = { dir: selected.dir, title: selected.title };
211
+ this.cancelConfirmation = { item: selected };
168
212
  this.status = undefined;
169
213
  }
170
214
  this.markDirty();
@@ -186,7 +230,7 @@ export class InboxController {
186
230
  else if (input === 'g' || input === 'G')
187
231
  void this.focusSelectedSource();
188
232
  else if (key.return || input === 'a')
189
- this.activate();
233
+ void this.activate();
190
234
  this.markDirty();
191
235
  }
192
236
  /** Settle the selected ticket from the list with what crouter already put on
@@ -201,15 +245,18 @@ export class InboxController {
201
245
  this.status = 'no recommended answer on this ticket — Enter opens it';
202
246
  return;
203
247
  }
248
+ // Settlement is a daemon-owned state, so a claim still succeeds on a
249
+ // settled request's directory. The record decides whether this row is
250
+ // stale; only then does a refused claim mean another inbox holds it.
251
+ const request = await this.requestOrNull(item.id);
252
+ if (isSettled(request)) {
253
+ this.status = settledStatus(request);
254
+ await this.rescan();
255
+ return;
256
+ }
204
257
  const claim = claimTicket(item.dir);
205
258
  if (claim === null) {
206
- // A null claim means either the ticket already settled (its response.json
207
- // exists) or a live claim is held. Only the latter is "another inbox";
208
- // the former is a stale row this scan should drop.
209
- const resolved = isResolved(item.dir);
210
- this.status = resolved ? safeCanonicalResultStatus(item.dir) : 'ticket is being edited by another inbox';
211
- if (resolved)
212
- this.rescan();
259
+ this.status = 'ticket is being edited by another inbox';
213
260
  return;
214
261
  }
215
262
  const settled = item.fastAction.kind === 'acknowledge'
@@ -217,25 +264,30 @@ export class InboxController {
217
264
  : `sent: ${recommendedLabel(item) ?? 'recommended answer'}`;
218
265
  this.submittingDir = item.dir;
219
266
  try {
220
- await resolveTicketPage(item.dir, item.fastAction.responses);
267
+ await resolveTicketPage(item.id, item.fastAction.responses);
221
268
  this.status = settled;
222
269
  }
223
270
  catch (error) {
224
- this.status = resolveFailureKind(error) === 'already_resolved'
225
- ? safeCanonicalResultStatus(item.dir)
271
+ this.status = resolveFailureKind(error) === 'already_settled'
272
+ ? settledStatus(await this.requestOrNull(item.id))
226
273
  : daemonErrorMessage(error);
227
274
  }
228
275
  finally {
229
276
  releaseClaim(item.dir, claim.token);
230
277
  this.submittingDir = undefined;
231
- this.rescan();
278
+ await this.rescan();
232
279
  this.markDirty();
233
280
  }
234
281
  }
235
- async cancelFromInbox(dir) {
236
- this.submittingDir = dir;
282
+ /** A review and a page are two different durable records; the inbox dismisses
283
+ * whichever one the selected row actually is. */
284
+ async cancelFromInbox(item) {
285
+ this.submittingDir = item.dir;
237
286
  try {
238
- await cancelTicketViaDaemon(dir, { reason: 'Canceled from the inbox.', actor: 'human', disposition: 'dismissed' });
287
+ if (item.kind === 'review')
288
+ await createReviewClient().cancel(item.id, 'Canceled from the inbox.');
289
+ else
290
+ await dismissTicketViaDaemon(item.id, { reason: 'Canceled from the inbox.', actor: 'human' });
239
291
  this.status = 'canceled';
240
292
  }
241
293
  catch (error) {
@@ -243,46 +295,67 @@ export class InboxController {
243
295
  }
244
296
  finally {
245
297
  this.submittingDir = undefined;
246
- this.rescan();
298
+ await this.rescan();
247
299
  this.markDirty();
248
300
  }
249
301
  }
250
- activate() {
302
+ async activate() {
251
303
  const item = this.items[this.selectedIndex];
252
304
  if (item === undefined)
253
305
  return;
254
306
  if (item.kind === 'review') {
255
- void this.activateReview(item);
307
+ await this.activateReview(item);
256
308
  return;
257
309
  }
258
- if (item.kind !== 'page')
310
+ const request = await this.requestOrNull(item.id);
311
+ if (this.closed)
259
312
  return;
313
+ if (isSettled(request)) {
314
+ this.status = settledStatus(request);
315
+ await this.rescan();
316
+ return;
317
+ }
260
318
  const claim = claimTicket(item.dir);
261
319
  if (claim === null) {
262
- const resolved = isResolved(item.dir);
263
- this.status = resolved ? safeCanonicalResultStatus(item.dir) : 'ticket is being edited by another inbox';
264
- if (resolved)
265
- this.rescan();
320
+ this.status = 'ticket is being edited by another inbox';
266
321
  return;
267
322
  }
268
- this.claim = { dir: item.dir, token: claim.token };
269
- let loaded;
323
+ // One projection carries the revision AND the content it belongs to, so
324
+ // there is no window between reading the two in which a replacement lands.
325
+ let view;
326
+ let revision;
270
327
  try {
271
- loaded = readPageTicket(item.dir);
328
+ const page = await cliClient().getHumanInboxPage(item.inboxTicketId);
329
+ // The claim is a presenter lock, not settlement authority, so a request
330
+ // that settled between the pre-claim check and this read would still
331
+ // claim. This projection already carries the state that decides it.
332
+ if (page.state === 'resolved' || page.state === 'canceled') {
333
+ releaseClaim(item.dir, claim.token);
334
+ this.status = settledStatusOf(page);
335
+ await this.rescan();
336
+ return;
337
+ }
338
+ view = pageViewFromDTO(page);
339
+ revision = page.revision;
272
340
  }
273
341
  catch {
274
342
  releaseClaim(item.dir, claim.token);
275
- this.claim = undefined;
276
343
  this.status = 'page could not be read';
277
- this.invalidate();
344
+ await this.invalidate();
345
+ return;
346
+ }
347
+ // Another activation (or a close) may have landed while this read was in
348
+ // flight; that one owns the detail screen, so this claim is released.
349
+ if (this.closed || this.claim !== undefined || this.adapter !== undefined) {
350
+ releaseClaim(item.dir, claim.token);
278
351
  return;
279
352
  }
280
- const productKinds = resolvePageComponents();
353
+ this.claim = { dir: item.dir, id: item.id, ticketId: item.inboxTicketId, token: claim.token };
281
354
  this.screen = 'detail';
282
355
  this.adapter = new PageAdapter({
283
- dir: item.dir,
284
- ticket: loaded,
285
- productKinds,
356
+ ticketId: item.inboxTicketId,
357
+ page: view,
358
+ productKinds: resolvePageComponents(),
286
359
  cols: this.detailSize().cols,
287
360
  rows: this.detailSize().rows,
288
361
  onDirty: () => this.markDirty(),
@@ -290,11 +363,13 @@ export class InboxController {
290
363
  onBack: () => { this.leaveDetail(); this.markDirty(); },
291
364
  onComplete: (responses) => { void this.complete(responses); },
292
365
  });
366
+ this.openRevision = revision;
293
367
  this.watchSelected(item.dir);
368
+ this.markDirty();
294
369
  }
295
370
  async activateReview(item) {
296
371
  this.reviewAdapter = new ReviewAdapter({
297
- dir: item.dir,
372
+ reviewId: item.id,
298
373
  host: this.host,
299
374
  });
300
375
  try {
@@ -305,7 +380,7 @@ export class InboxController {
305
380
  }
306
381
  finally {
307
382
  this.reviewAdapter = undefined;
308
- this.rescan();
383
+ await this.rescan();
309
384
  this.markDirty();
310
385
  }
311
386
  }
@@ -317,13 +392,21 @@ export class InboxController {
317
392
  this.markDirty();
318
393
  }
319
394
  }
320
- reloadSelectedPage() {
321
- if (this.adapter === undefined || this.claim === undefined) {
395
+ /** Re-read the open page and reload it only when its revision actually moved. */
396
+ async reloadSelectedPage() {
397
+ const claim = this.claim;
398
+ if (this.adapter === undefined || claim === undefined) {
322
399
  this.markDirty();
323
400
  return;
324
401
  }
325
402
  try {
326
- this.adapter.reload(readPageTicket(this.claim.dir));
403
+ const page = await cliClient().getHumanInboxPage(claim.ticketId);
404
+ if (this.closed || this.claim !== claim || this.adapter === undefined)
405
+ return;
406
+ if (page.revision !== this.openRevision) {
407
+ this.openRevision = page.revision;
408
+ this.adapter.reload(pageViewFromDTO(page));
409
+ }
327
410
  }
328
411
  catch {
329
412
  this.status = 'page could not be read';
@@ -353,7 +436,7 @@ export class InboxController {
353
436
  return;
354
437
  this.submittingDir = claim.dir;
355
438
  try {
356
- await resolveTicketPage(claim.dir, responses);
439
+ await resolveTicketPage(claim.id, responses);
357
440
  }
358
441
  catch (error) {
359
442
  this.submittingDir = undefined;
@@ -363,28 +446,28 @@ export class InboxController {
363
446
  this.markDirty();
364
447
  return;
365
448
  }
366
- if (failure === 'already_resolved')
367
- this.status = safeCanonicalResultStatus(claim.dir);
449
+ if (failure === 'already_settled')
450
+ this.status = settledStatus(await this.requestOrNull(claim.id));
368
451
  this.leaveDetail(false);
369
- this.rescan();
452
+ await this.rescan();
370
453
  this.markDirty();
371
454
  return;
372
455
  }
373
456
  this.submittingDir = undefined;
374
457
  this.leaveDetail(false);
375
- this.rescan();
458
+ await this.rescan();
376
459
  // A settled page whose row survives its own post-resolve rescan is the
377
- // "row won't leave" defect. This records the disk truth at the exact moment
378
- // the row should have dropped, so a live recurrence is diagnosable without a
379
- // reproduction: was the response published, does the scan still list it, and
380
- // was the claim left behind.
460
+ // "row won't leave" defect. This records the truth at the exact moment the
461
+ // row should have dropped, so a live recurrence is diagnosable without a
462
+ // reproduction: did the request reach a terminal state, does the scan still
463
+ // list it, and was the claim left behind.
381
464
  const stillListed = this.items.some((item) => item.dir === claim.dir);
382
465
  emitEvent({
383
466
  level: stillListed ? 'warn' : 'info',
384
467
  event: 'inbox.page.resolved',
385
468
  fields: {
386
- ticket_id: basename(claim.dir),
387
- response_published: existsSync(responsePath(claim.dir)),
469
+ ticket_id: claim.id,
470
+ request_settled: isSettled(await this.requestOrNull(claim.id)),
388
471
  still_listed: stillListed,
389
472
  claim_left_behind: existsSync(claimPath(claim.dir)),
390
473
  },
@@ -417,6 +500,7 @@ export class InboxController {
417
500
  if (release && this.claim !== undefined)
418
501
  releaseClaim(this.claim.dir, this.claim.token);
419
502
  this.claim = undefined;
503
+ this.openRevision = undefined;
420
504
  this.screen = 'list';
421
505
  }
422
506
  select(index) {
@@ -453,14 +537,21 @@ export class InboxController {
453
537
  focusAvailable(_dir) {
454
538
  return this.host.capabilities.canFocusNode;
455
539
  }
456
- /** Ask the host to bring the selected ticket's node viewer into view. */
540
+ /** Ask the host to bring the requesting node's viewer into view — the
541
+ * conversation that raised this ask. */
457
542
  async focusSelectedSource() {
458
543
  const item = this.items[this.selectedIndex];
459
544
  if (item === undefined || this.focusingSource)
460
545
  return;
546
+ const requesterNodeId = item.source.nodeId;
547
+ if (requesterNodeId === undefined) {
548
+ this.status = 'This request was not raised by a canvas node';
549
+ this.markDirty();
550
+ return;
551
+ }
461
552
  this.focusingSource = true;
462
553
  try {
463
- const focused = await this.host.focusNode(ticketIdFor(item.dir));
554
+ const focused = await this.host.focusNode(requesterNodeId);
464
555
  if (focused)
465
556
  this.close();
466
557
  else {
@@ -472,31 +563,65 @@ export class InboxController {
472
563
  this.focusingSource = false;
473
564
  }
474
565
  }
475
- /** What the selected page actually says, projected once per document and
476
- * kept until the document, the selection, or the pane width changes — the
477
- * projection reads the ticket from disk and evaluates it. */
566
+ /** What the selected page actually says. Rendering is synchronous and the
567
+ * page is a round trip, so the projection is held and a miss schedules the
568
+ * fetch, marking the frame dirty when it lands. A width change re-wraps the
569
+ * held projection without another read. */
478
570
  pagePreviewBody(item, width) {
479
- let stamp;
571
+ const cached = this.preview;
572
+ if (cached !== undefined && cached.dir === item.dir) {
573
+ if (this.previewStale)
574
+ this.schedulePreview(item);
575
+ if (cached.width === width)
576
+ return cached.lines;
577
+ const lines = previewLines(cached.view, width);
578
+ this.preview = { ...cached, width, lines };
579
+ return lines;
580
+ }
581
+ this.schedulePreview(item);
582
+ // A failed read says so; an unfetched one is simply not there yet.
583
+ return this.previewFailed === item.dir ? [`${DIM}page could not be read${RESET}`] : [];
584
+ }
585
+ /** One in-flight preview read at a time. A revision that did not move keeps
586
+ * the held projection, so activity on an unrelated ticket costs no re-wrap. */
587
+ schedulePreview(item) {
588
+ if (this.previewFetching === item.dir)
589
+ return;
590
+ this.previewFetching = item.dir;
591
+ this.previewStale = false;
592
+ void cliClient().getHumanInboxPage(item.inboxTicketId).then((page) => {
593
+ if (this.closed || this.previewFetching !== item.dir)
594
+ return;
595
+ this.previewFetching = undefined;
596
+ this.previewFailed = undefined;
597
+ if (this.preview?.dir === item.dir && this.preview.revision === page.revision)
598
+ return;
599
+ const view = pageViewFromDTO(page);
600
+ const width = this.preview?.width ?? inboxLayout(this.cols, this.rows, this.screen).detailWidth;
601
+ this.preview = { dir: item.dir, revision: page.revision, view, width, lines: previewLines(view, width) };
602
+ this.markDirty();
603
+ }, () => {
604
+ if (this.closed || this.previewFetching !== item.dir)
605
+ return;
606
+ this.previewFetching = undefined;
607
+ this.previewFailed = item.dir;
608
+ // Never leave the prior page's body standing under this row's title.
609
+ if (this.preview?.dir === item.dir)
610
+ this.preview = undefined;
611
+ this.markDirty();
612
+ });
613
+ }
614
+ /** The request record is the settlement authority: a settled page publishes
615
+ * no file, so a claim would still succeed on its directory. An unreachable
616
+ * daemon reads as "not known to be settled" — the settle attempt that
617
+ * follows is the one that fails loudly. */
618
+ async requestOrNull(id) {
480
619
  try {
481
- stamp = String(statSync(pagePath(item.dir, item.dialect)).mtimeMs);
620
+ return await cliClient().getHumanRequest(id);
482
621
  }
483
622
  catch {
484
- stamp = 'missing';
623
+ return undefined;
485
624
  }
486
- const cached = this.preview;
487
- if (cached !== undefined && cached.dir === item.dir && cached.width === width && cached.stamp === stamp)
488
- return cached.lines;
489
- let lines = [];
490
- try {
491
- const ticket = readPageTicket(item.dir);
492
- const display = ticket.manifest.dialect === 'html'
493
- ? projectHtmlDocumentMarkdown(ticket.document)
494
- : projectPageDisplayMarkdown(ticket.document, ticket.manifest);
495
- lines = pageBodyLines(ticket.manifest, display, Math.min(Math.max(20, width - 4), 100), Math.max(20, width - 4));
496
- }
497
- catch { /* a half-written or removed ticket previews as its title alone */ }
498
- this.preview = { dir: item.dir, width, stamp, lines };
499
- return lines;
500
625
  }
501
626
  /** The read-only preview beside the list. It shows the page's own content,
502
627
  * because `s` settles the selected ticket from here without ever opening
@@ -523,7 +648,7 @@ export class InboxController {
523
648
  withStatus(lines) {
524
649
  const status = this.cancelConfirmation === undefined
525
650
  ? this.status
526
- : `cancel “${this.cancelConfirmation.title}”? y yes · n/Esc no`;
651
+ : `cancel “${this.cancelConfirmation.item.title}”? y yes · n/Esc no`;
527
652
  if (status === undefined || this.rows < 1)
528
653
  return lines;
529
654
  const next = [...lines];
@@ -536,50 +661,41 @@ export class InboxController {
536
661
  this.options.onDirty();
537
662
  }
538
663
  watchRoots() {
539
- const unsubscribe = watchInboxActivity(this.options.onInboxActivity ?? (() => this.invalidate()));
664
+ const unsubscribe = watchInboxActivity(this.options.onInboxActivity ?? (() => { void this.invalidate(); }));
540
665
  this.watchers.push({ close: unsubscribe });
541
666
  }
667
+ /** The request directory still carries the claim and progress files other
668
+ * inboxes write. Its page content lives in the daemon-selected revision,
669
+ * which `invalidate` re-reads over the API. */
542
670
  watchSelected(dir) {
543
671
  this.selectedWatcher?.close();
544
672
  try {
545
- this.selectedWatcher = watch(dir, (_event, file) => {
546
- if (file === 'page.json') {
547
- this.reloadSelectedPage();
548
- return;
549
- }
550
- this.invalidate();
551
- });
673
+ this.selectedWatcher = watch(dir, () => { void this.invalidate(); });
552
674
  }
553
675
  catch {
554
676
  this.selectedWatcher = undefined;
555
677
  }
556
678
  }
557
679
  }
558
- function canonicalResultStatus(result) {
559
- if (result === null)
560
- return 'ticket was already resolved';
561
- if (result.kind === 'review')
562
- return `ticket was already resolved: ${result.result.approved ? 'approved' : 'review submitted'}`;
563
- if (result.kind === 'page')
564
- return 'ticket was already resolved: page submitted';
565
- return 'ticket was already canceled';
680
+ /** The inbox the daemon lists IS the inbox: visibility, ordering and row
681
+ * content all come from the one projection. */
682
+ async function defaultScan() {
683
+ const { tickets } = await cliClient().listHumanInbox();
684
+ return tickets.map(inboxRowFromDTO).sort(byNewestFirst);
566
685
  }
567
- /** `readTicketResult` throws on a corrupt result file. In this long-lived TUI
568
- * process there is no top-level catch, so a corrupt file must surface as a
569
- * status line rather than crash the inbox. */
570
- function readTicketResultOrNull(dir) {
571
- try {
572
- return readTicketResult(dir);
573
- }
574
- catch {
575
- return null;
576
- }
686
+ function previewLines(view, width) {
687
+ const display = view.manifest.dialect === 'html'
688
+ ? projectHtmlDocumentMarkdown(view.document)
689
+ : projectPageDisplayMarkdown(view.document, view.manifest);
690
+ return pageBodyLines(view.manifest, display, Math.min(Math.max(20, width - 4), 100), Math.max(20, width - 4));
577
691
  }
578
- function safeCanonicalResultStatus(dir) {
579
- try {
580
- return canonicalResultStatus(readTicketResult(dir));
581
- }
582
- catch (error) {
583
- return `ticket result is unreadable: ${error instanceof Error ? error.message : String(error)}`;
584
- }
692
+ /** A request the daemon no longer reports on is not known to be settled. */
693
+ function isSettled(request) {
694
+ return request !== undefined && request.state !== 'pending';
695
+ }
696
+ /** Why a second settlement was refused, in the status line's own wording. */
697
+ function settledStatus(request) {
698
+ if (request?.page === undefined)
699
+ return 'ticket was already resolved';
700
+ return settledStatusOf(request.page);
585
701
  }
@@ -0,0 +1,28 @@
1
+ import type { InboxPageDTO, InboxTicketSummaryDTO } from '../../api/dto/inbox.js';
2
+ import type { PageManifest, PageResponses } from '../../core/human/page-schema.js';
3
+ import type { PageTicketSummary, ReviewTicketSummary } from '../../core/human/types.js';
4
+ /** A review row as the inbox actually uses it. The review's file and output
5
+ * path belong to the review document surface, which reads them from the review
6
+ * client — an inbox row never needs either. */
7
+ export type InboxReviewRow = Omit<ReviewTicketSummary, 'file' | 'output'>;
8
+ export type InboxRow = PageTicketSummary | InboxReviewRow;
9
+ /** Everything the page renderer needs, plus the revision token that decides
10
+ * whether an already-open page has to reload. */
11
+ export interface InboxPageView {
12
+ manifest: PageManifest;
13
+ /** The authored JSX/HTML source the renderer projects — never the compiled host module. */
14
+ document: string;
15
+ progress: PageResponses | null;
16
+ state: 'pending' | 'resolved' | 'canceled' | 'passive';
17
+ /** Opaque equality token for the SQL-selected revision. Compare, never parse. */
18
+ revision: string;
19
+ }
20
+ export declare function inboxRowFromDTO(dto: InboxTicketSummaryDTO): InboxRow;
21
+ export declare function pageViewFromDTO(dto: InboxPageDTO): InboxPageView;
22
+ /** The stored one-line answer digest, or undefined when the page was not
23
+ * answered. A canceled page carries no digest by construction. */
24
+ export declare function answerDigestOf(dto: InboxPageDTO): string | undefined;
25
+ /** What a surface says when a settled request refuses a second settlement. */
26
+ export declare function settledStatusOf(dto: InboxPageDTO): string;
27
+ /** The row sort every inbox surface shares: newest first, request id breaking ties. */
28
+ export declare function byNewestFirst(a: InboxRow, b: InboxRow): number;