@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
@@ -0,0 +1,217 @@
1
+ import { existsSync, mkdirSync, renameSync, writeFileSync } from 'node:fs';
2
+ import { randomBytes, randomUUID } from 'node:crypto';
3
+ import { join } from 'node:path';
4
+ import { cancelCronsOnWake } from '../canvas/crons.js';
5
+ import { withCanvasWrite } from '../canvas/db.js';
6
+ import { getRow } from '../canvas/canvas.js';
7
+ import { messagesDir } from '../canvas/paths.js';
8
+ import { syncDirectory, syncFile } from '../fs-utils.js';
9
+ import { settleDeadControllerWait, settleMatchingControllerWait } from '../canvas/waits.js';
10
+ import { generateOperationId, operationIdContext, validateOperationId } from '../events/operation-id.js';
11
+ import { clipBody } from './inbox.js';
12
+ function newId() {
13
+ let id;
14
+ do
15
+ id = randomBytes(16).toString('hex');
16
+ while (id === '00000000000000000000000000000000');
17
+ return id;
18
+ }
19
+ function stringify(value, name) {
20
+ const json = JSON.stringify(value);
21
+ if (json === undefined)
22
+ throw new TypeError(`${name} must be JSON-serializable`);
23
+ return json;
24
+ }
25
+ function effectiveTier(nodeId, urgency) {
26
+ if (urgency !== 'normal' && urgency !== 'deferred')
27
+ return urgency;
28
+ return getRow(nodeId)?.lifecycle === 'terminal' ? 'urgent' : urgency;
29
+ }
30
+ function writeMessageBody(nodeId, createdAt, body) {
31
+ const dir = messagesDir(nodeId);
32
+ if (!existsSync(dir))
33
+ mkdirSync(dir, { recursive: true });
34
+ const stamp = createdAt.replace(/[^0-9]/g, '');
35
+ const finalPath = join(dir, `${stamp}-${randomUUID().slice(0, 6)}.md`);
36
+ const temporaryPath = `${finalPath}.tmp`;
37
+ writeFileSync(temporaryPath, body, 'utf8');
38
+ syncFile(temporaryPath);
39
+ renameSync(temporaryPath, finalPath);
40
+ syncDirectory(dir);
41
+ return finalPath;
42
+ }
43
+ function inlineBody(bodyJson) {
44
+ if (typeof bodyJson !== 'object' || bodyJson === null)
45
+ return '';
46
+ const body = bodyJson['body'];
47
+ return typeof body === 'string' ? body.trim() : '';
48
+ }
49
+ function insertMessage(db, input) {
50
+ const messageId = input.messageId ?? newId();
51
+ const seq = Number(db.prepare('SELECT COALESCE(MAX(seq), 0) AS seq FROM messages').get().seq) + 1;
52
+ db.prepare(`INSERT INTO messages
53
+ (message_id, seq, operation_id, created_at, sender_node_id, sender_name, kind, label, body_ref, body_json, disposition)
54
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(messageId, seq, input.operationId, input.createdAt, input.senderNodeId, input.senderName, input.kind, input.label, input.bodyRef, input.bodyJson === undefined ? null : stringify(input.bodyJson, 'message body'), input.disposition);
55
+ return messageId;
56
+ }
57
+ function insertNodeDelivery(db, messageId, destinationId, urgency, wake, now, idempotencyKey) {
58
+ const deliveryId = newId();
59
+ db.prepare(`INSERT INTO deliveries
60
+ (delivery_id, message_id, destination_kind, destination_id, urgency, wake, state, idempotency_key, created_at, updated_at)
61
+ VALUES (?, ?, 'node_inbox', ?, ?, ?, 'pending', ?, ?, ?)`).run(deliveryId, messageId, destinationId, urgency, wake, idempotencyKey ?? null, now, now);
62
+ return deliveryId;
63
+ }
64
+ function finalizeNodeMail(input, destinationNodeId) {
65
+ const createdAt = new Date().toISOString();
66
+ const operationId = input.operationId === undefined
67
+ ? operationIdContext.current() ?? generateOperationId()
68
+ : validateOperationId(input.operationId);
69
+ let bodyRef = input.bodyRef ?? null;
70
+ let bodyJson = input.bodyJson;
71
+ if (bodyRef === null) {
72
+ const body = inlineBody(bodyJson);
73
+ const clipped = clipBody(body);
74
+ if (clipped.clipped) {
75
+ // The destination owns the referenced body. Publish the file before its reference enters the transaction.
76
+ bodyRef = writeMessageBody(destinationNodeId, createdAt, body);
77
+ bodyJson = { ...bodyJson, body: clipped.text };
78
+ }
79
+ }
80
+ return { createdAt, operationId, bodyRef, bodyJson };
81
+ }
82
+ /** Insert one immutable message and one node-inbox obligation into the caller's write transaction. */
83
+ export function enqueueNodeMail(destinationNodeId, input) {
84
+ const prepared = finalizeNodeMail(input, destinationNodeId);
85
+ return withCanvasWrite((db) => {
86
+ if (input.idempotencyKey !== undefined) {
87
+ const existing = db.prepare(`SELECT d.delivery_id, d.message_id, d.urgency, m.created_at
88
+ FROM deliveries d JOIN messages m ON m.message_id = d.message_id WHERE d.idempotency_key = ?`).get(input.idempotencyKey);
89
+ if (existing !== undefined)
90
+ return { messageId: existing.message_id, deliveryId: existing.delivery_id, urgency: existing.urgency, createdAt: existing.created_at };
91
+ }
92
+ const now = Date.now();
93
+ const urgency = effectiveTier(destinationNodeId, input.urgency);
94
+ const messageId = insertMessage(db, {
95
+ operationId: prepared.operationId,
96
+ createdAt: prepared.createdAt,
97
+ senderNodeId: input.senderNodeId ?? null,
98
+ senderName: input.senderName ?? null,
99
+ kind: input.kind,
100
+ label: input.label,
101
+ bodyRef: prepared.bodyRef,
102
+ bodyJson: prepared.bodyJson,
103
+ disposition: input.disposition ?? null,
104
+ });
105
+ const deliveryId = insertNodeDelivery(db, messageId, destinationNodeId, urgency, input.wake, now, input.idempotencyKey);
106
+ settleMatchingControllerWait(destinationNodeId, input.senderNodeId ?? null, now, db);
107
+ if (input.wake === 1 && urgency !== 'deferred')
108
+ cancelCronsOnWake(destinationNodeId);
109
+ return { messageId, deliveryId, urgency, createdAt: prepared.createdAt };
110
+ });
111
+ }
112
+ /** Fan out one immutable report message to the supplied subscriber snapshot. */
113
+ export function enqueueSubscriberMail(subscribers, input) {
114
+ if (subscribers.length === 0)
115
+ return [];
116
+ const prepared = finalizeNodeMail(input, subscribers[0].node_id);
117
+ return withCanvasWrite((db) => {
118
+ const now = Date.now();
119
+ const messageId = insertMessage(db, {
120
+ operationId: prepared.operationId,
121
+ createdAt: prepared.createdAt,
122
+ senderNodeId: input.senderNodeId ?? null,
123
+ senderName: input.senderName ?? null,
124
+ kind: input.kind,
125
+ label: input.label,
126
+ bodyRef: prepared.bodyRef,
127
+ bodyJson: prepared.bodyJson,
128
+ disposition: input.disposition ?? null,
129
+ });
130
+ return subscribers.map((subscriber) => {
131
+ const urgency = effectiveTier(subscriber.node_id, input.urgency);
132
+ const wake = subscriber.active ? 1 : 0;
133
+ const deliveryId = insertNodeDelivery(db, messageId, subscriber.node_id, urgency, wake, now);
134
+ settleMatchingControllerWait(subscriber.node_id, input.senderNodeId ?? null, now, db);
135
+ if (wake === 1 && urgency !== 'deferred')
136
+ cancelCronsOnWake(subscriber.node_id);
137
+ return { destinationNodeId: subscriber.node_id, messageId, deliveryId, urgency, createdAt: prepared.createdAt };
138
+ });
139
+ });
140
+ }
141
+ /** Insert one frozen external-action obligation. A duplicate idempotency key returns its original identity. */
142
+ export function enqueueExternalAction(input) {
143
+ return withCanvasWrite((db) => {
144
+ const existing = db.prepare('SELECT delivery_id, message_id FROM deliveries WHERE idempotency_key = ?').get(input.idempotencyKey);
145
+ if (existing !== undefined)
146
+ return { messageId: existing.message_id, deliveryId: existing.delivery_id };
147
+ const now = Date.now();
148
+ const messageId = insertMessage(db, {
149
+ operationId: operationIdContext.current() ?? generateOperationId(),
150
+ createdAt: new Date(now).toISOString(),
151
+ senderNodeId: null,
152
+ senderName: null,
153
+ kind: input.kind ?? 'completed',
154
+ label: input.label ?? input.destinationId,
155
+ bodyRef: null,
156
+ bodyJson: input.bodyJson,
157
+ disposition: null,
158
+ });
159
+ const deliveryId = newId();
160
+ const inserted = db.prepare(`INSERT INTO deliveries
161
+ (delivery_id, message_id, destination_kind, destination_id, urgency, wake, state, next_attempt_at,
162
+ adapter_json, idempotency_key, created_at, updated_at)
163
+ VALUES (?, ?, 'external_action', ?, 'normal', 0, 'pending', ?, ?, ?, ?, ?)
164
+ ON CONFLICT(idempotency_key) WHERE idempotency_key IS NOT NULL DO NOTHING`).run(deliveryId, messageId, input.destinationId, now, stringify(input.adapter, 'external action adapter'), input.idempotencyKey, now, now);
165
+ if (Number(inserted.changes) === 1)
166
+ return { messageId, deliveryId };
167
+ const winner = db.prepare('SELECT delivery_id, message_id FROM deliveries WHERE idempotency_key = ?').get(input.idempotencyKey);
168
+ if (winner === undefined)
169
+ throw new Error(`external action idempotency winner disappeared: ${input.idempotencyKey}`);
170
+ return { messageId: winner.message_id, deliveryId: winner.delivery_id };
171
+ });
172
+ }
173
+ /** Retract an obligation without fabricating a terminal receipt. */
174
+ export function cancelDelivery(deliveryId, now = Date.now()) {
175
+ return withCanvasWrite((db) => Number(db.prepare('UPDATE deliveries SET canceled_at = ?, updated_at = ? WHERE delivery_id = ? AND canceled_at IS NULL').run(now, now, deliveryId).changes) === 1);
176
+ }
177
+ export function cancelPendingNodeMail(nodeId, select = {}) {
178
+ return withCanvasWrite((db) => {
179
+ const clauses = ["d.destination_kind = 'node_inbox'", 'd.destination_id = ?', 'd.canceled_at IS NULL', "d.state IN ('pending', 'handed_off')"];
180
+ const params = [nodeId];
181
+ if (select.senderNodeId !== undefined) {
182
+ clauses.push('m.sender_node_id IS ?');
183
+ params.push(select.senderNodeId);
184
+ }
185
+ if (select.kind !== undefined) {
186
+ clauses.push('m.kind = ?');
187
+ params.push(select.kind);
188
+ }
189
+ const rows = db.prepare(`SELECT d.delivery_id FROM deliveries d JOIN messages m ON m.message_id = d.message_id WHERE ${clauses.join(' AND ')}`).all(...params);
190
+ if (rows.length === 0)
191
+ return [];
192
+ const now = Date.now();
193
+ db.prepare(`UPDATE deliveries SET canceled_at = ?, updated_at = ? WHERE delivery_id IN (${rows.map(() => '?').join(',')})`)
194
+ .run(now, now, ...rows.map((row) => row.delivery_id));
195
+ return rows.map((row) => row.delivery_id);
196
+ });
197
+ }
198
+ /** Enqueue the one notice only if the same live controller wait is still dead. */
199
+ export function enqueueControllerDeathNotice(nodeId, controllerId) {
200
+ return withCanvasWrite((db) => {
201
+ const now = Date.now();
202
+ const row = db.prepare(`SELECT target.status AS controller_status FROM waits w LEFT JOIN nodes target ON target.node_id = w.target_id
203
+ WHERE w.node_id = ? AND w.kind = 'controller' AND w.target_id = ? AND w.satisfied_at IS NULL`).get(nodeId, controllerId);
204
+ if (row === undefined || row.controller_status === 'active' || row.controller_status === 'idle')
205
+ return null;
206
+ const result = enqueueNodeMail(nodeId, {
207
+ kind: 'urgent',
208
+ label: 'declared controller is no longer live',
209
+ bodyJson: { body: `Controller ${controllerId} is no longer live; the declared message wait was cleared.` },
210
+ urgency: 'urgent',
211
+ wake: 1,
212
+ });
213
+ if (!settleDeadControllerWait(nodeId, controllerId, now, db))
214
+ throw new Error(`controller wait changed while notifying ${nodeId}`);
215
+ return result;
216
+ });
217
+ }
@@ -13,6 +13,10 @@ export interface AtomicWriteOptions {
13
13
  mode?: number;
14
14
  }
15
15
  export declare function atomicWriteJson(path: string, data: unknown, opts?: AtomicWriteOptions): void;
16
+ /** Force a file's written bytes to stable storage. */
17
+ export declare function syncFile(path: string): void;
18
+ /** Force a directory's entry changes to stable storage. */
19
+ export declare function syncDirectory(path: string): void;
16
20
  /** Replace one file's contents in a single rename, so a concurrent reader — the
17
21
  * tmux inbox watcher, or crtrd serving a ticket read — sees either the old
18
22
  * bytes or the new ones and never a truncated file mid-write. */
@@ -1,4 +1,4 @@
1
- import { existsSync, lstatSync, mkdirSync, readdirSync, readFileSync, rmSync, statSync, symlinkSync, writeFileSync, cpSync, readlinkSync, renameSync, chmodSync, realpathSync, } from 'node:fs';
1
+ import { existsSync, lstatSync, mkdirSync, readdirSync, readFileSync, rmSync, statSync, symlinkSync, writeFileSync, cpSync, readlinkSync, renameSync, chmodSync, realpathSync, closeSync, fsyncSync, openSync, } from 'node:fs';
2
2
  import { dirname, join, relative, sep } from 'node:path';
3
3
  import { homedir, platform } from 'node:os';
4
4
  /** `realpathSync`, tolerant of a path that doesn't exist or can't be resolved. */
@@ -38,6 +38,26 @@ export function writeJson(path, data) {
38
38
  export function atomicWriteJson(path, data, opts) {
39
39
  atomicWriteText(path, JSON.stringify(data, null, 2) + '\n', opts);
40
40
  }
41
+ /** Force a file's written bytes to stable storage. */
42
+ export function syncFile(path) {
43
+ const fd = openSync(path, 'r');
44
+ try {
45
+ fsyncSync(fd);
46
+ }
47
+ finally {
48
+ closeSync(fd);
49
+ }
50
+ }
51
+ /** Force a directory's entry changes to stable storage. */
52
+ export function syncDirectory(path) {
53
+ const fd = openSync(path, 'r');
54
+ try {
55
+ fsyncSync(fd);
56
+ }
57
+ finally {
58
+ closeSync(fd);
59
+ }
60
+ }
41
61
  /** Replace one file's contents in a single rename, so a concurrent reader — the
42
62
  * tmux inbox watcher, or crtrd serving a ticket read — sees either the old
43
63
  * bytes or the new ones and never a truncated file mid-write. */
@@ -49,5 +49,5 @@ export declare function answerCommentAnchorLabel(slot: PageSlot, anchor: LegacyC
49
49
  /** Derive the one shared answer model from the immutable page manifest and a response map. */
50
50
  /** `manifest` is unknown deliberately: this boundary validates persisted JSON before deriving. */
51
51
  export declare function describePageAnswer(manifest: unknown, responses: PageResponses, opts: DescribePageAnswerOptions): PageAnswer;
52
- /** The stable one-line digest stored in response.json and shown in history. */
52
+ /** The stable one-line digest stored in the terminal request result and shown in history. */
53
53
  export declare function buildAnswerDigest(slots: PageAnswerSlot[]): string;
@@ -100,7 +100,7 @@ function groupDigest(group) {
100
100
  return group.picked.map((item) => item.label).join(', ');
101
101
  return group.total > 0 ? `${group.picked.length} of ${group.total} ${group.group}s` : `${group.picked.length} ${group.group}${group.picked.length === 1 ? '' : 's'}`;
102
102
  }
103
- /** The stable one-line digest stored in response.json and shown in history. */
103
+ /** The stable one-line digest stored in the terminal request result and shown in history. */
104
104
  export function buildAnswerDigest(slots) {
105
105
  return slots.map((slot) => {
106
106
  if (slot.raw !== undefined)
@@ -14,7 +14,7 @@ export interface ClaimOptions {
14
14
  }
15
15
  export declare function readTicketClaim(dir: string): TicketClaim | null;
16
16
  export declare function isStaleClaim(claim: TicketClaim, now?: number, localHost?: string): boolean;
17
- /** Acquire a visible claim only for a still-pending ticket. Malformed crash artifacts are stale claims. */
17
+ /** Exclude another live presenter; the daemon owns whether the request can settle. */
18
18
  export declare function claimTicket(dir: string, opts?: ClaimOptions): TicketClaim | null;
19
19
  export declare function heartbeatClaim(dir: string, token: string, now?: Date): boolean;
20
20
  export declare function releaseClaimLocked(dir: string, token: string): boolean;
@@ -1,7 +1,7 @@
1
1
  import { existsSync, renameSync, unlinkSync, writeFileSync } from 'node:fs';
2
2
  import { hostname } from 'node:os';
3
3
  import { randomUUID } from 'node:crypto';
4
- import { claimPath, pageManifestPath, responsePath, reviewPath } from './convention.js';
4
+ import { claimPath } from './convention.js';
5
5
  import { readJsonOrNull } from '../fs-utils.js';
6
6
  import { withExclusiveDirectoryLock } from '../exclusive-lock.js';
7
7
  const REMOTE_STALE_MS = 30_000;
@@ -27,11 +27,9 @@ export function isStaleClaim(claim, now = Date.now(), localHost = hostname()) {
27
27
  const heartbeat = Date.parse(claim.heartbeatAt);
28
28
  return !Number.isFinite(heartbeat) || now - heartbeat > REMOTE_STALE_MS;
29
29
  }
30
- /** Acquire a visible claim only for a still-pending ticket. Malformed crash artifacts are stale claims. */
30
+ /** Exclude another live presenter; the daemon owns whether the request can settle. */
31
31
  export function claimTicket(dir, opts = {}) {
32
32
  return withExclusiveDirectoryLock(`${dir}/.ticket-lock`, () => {
33
- if (existsSync(responsePath(dir)) || (!existsSync(pageManifestPath(dir)) && !existsSync(reviewPath(dir))))
34
- return null;
35
33
  const path = claimPath(dir);
36
34
  const existing = readTicketClaim(dir);
37
35
  if (existing !== null && !isStaleClaim(existing, Date.now(), opts.host ?? hostname()))
@@ -10,7 +10,7 @@ const SLOT_RULES = `Component contract
10
10
  - When a page has two or more response-bearing components, every one needs a nonempty \`label\`.
11
11
  - Config objects and every documented nested object reject unknown fields.
12
12
  - A quoted JSX attribute does not interpret escape sequences, so \`body="one.\\n\\ntwo."\` reaches the reader as one run-on paragraph with a literal backslash-n in the middle of it. Write any markdown body that has a line or paragraph break as a braced string — \`body={"one.\\n\\ntwo."}\` — where the escape is real.
13
- - The page is rendered once at submit to build the ticket manifest, so every prop reaches it whatever its shape — mapped data, a helper component, a value from \`useState\`. Event handlers are dropped, because a manifest is JSON. The user's answer lands in \`response.json\` keyed by this \`id\`.`;
13
+ - The page is rendered once at submit to build the ticket manifest, so every prop reaches it whatever its shape — mapped data, a helper component, a value from \`useState\`. Event handlers are dropped, because a manifest is JSON. The user's answer is stored in the request's terminal result under this \`id\`.`;
14
14
  const COMMENT_SHAPE = `Comments are \`{id:string (nonempty), text:string, anchor:Anchor}\` and qualify the answer itself. \`Anchor\` is \`{kind:"option",optionId:string}\` or \`{kind:"row",rowId:string}\`; each component accepts only the anchors named next. The reader writes one against a specific option or row in whatever host they read in — in the terminal inbox, \`c\` on the selected one — so every option and row needs a label that reads on its own. Feedback on the page's own prose is a separate channel, offered only by hosts that render the page, and never part of a response.`;
15
15
  const DISPLAY_RULES = `Display-only: it is not recorded in the page manifest and contributes nothing to the response. Every display component also takes \`className\` for Tailwind utilities, and standard shadcn props otherwise.`;
16
16
  export const BUILTIN_COMPONENT_DOCS = [
@@ -18,11 +18,12 @@ import { reviveNode } from '../runtime/revive.js';
18
18
  import { materializeSessionBranchAt } from '../runtime/session-branch.js';
19
19
  import { initializeForkContextExposure } from '../substrate/injected-store.js';
20
20
  import { pagePath } from './convention.js';
21
+ import { selectedPageDir } from './tickets.js';
21
22
  export function feedbackCompanionBranchPath(dir) { return `${dir}/companion-branch.jsonl`; }
22
23
  /** Hex sha256 of the ticket's page document, recorded at first comment so the
23
24
  * settle-time completion can say whether the page changed during feedback. */
24
25
  export function pageDocumentSha256(dir) {
25
- return createHash('sha256').update(readFileSync(pagePath(dir, 'jsx'))).digest('hex');
26
+ return createHash('sha256').update(readFileSync(pagePath(selectedPageDir(dir), 'jsx'))).digest('hex');
26
27
  }
27
28
  /** Materialize the companion's fork branch, reusing only an already-valid one. */
28
29
  function materializeFeedbackBranch(dir, capture) {
@@ -65,7 +66,7 @@ export async function realizeFeedbackCompanion(args) {
65
66
  cwd: origin.cwd,
66
67
  profileId: origin.profile_id ?? null,
67
68
  });
68
- const node = spawnNode({
69
+ const node = await spawnNode({
69
70
  kind: origin.kind,
70
71
  mode: origin.mode,
71
72
  lifecycle: 'resident',
@@ -82,7 +83,7 @@ export async function realizeFeedbackCompanion(args) {
82
83
  review_id: args.ticketId,
83
84
  origin_node_id: args.senderNodeId,
84
85
  branch_file: branchFile,
85
- target_file: pagePath(args.dir, 'jsx'),
86
+ target_file: pagePath(selectedPageDir(args.dir), 'jsx'),
86
87
  },
87
88
  name: `Feedback · ${args.title}`,
88
89
  launch,
@@ -1,5 +1,6 @@
1
1
  import { z } from 'zod';
2
2
  export declare const PAGE_FEEDBACK_SCHEMA: "crtr.page-feedback/v1";
3
+ /** The path is a presentation projection, not the feedback authority. */
3
4
  export declare function pageFeedbackPath(dir: string): string;
4
5
  declare const pageFeedbackCommentSchema: z.ZodObject<{
5
6
  id: z.ZodString;
@@ -32,24 +33,19 @@ declare const pageFeedbackSchema: z.ZodObject<{
32
33
  }, z.core.$strict>;
33
34
  export type PageFeedbackComment = z.infer<typeof pageFeedbackCommentSchema>;
34
35
  export type PageFeedback = z.infer<typeof pageFeedbackSchema>;
35
- /** Read a ticket's feedback comments; a ticket without any has an empty record. */
36
+ /** Read page feedback from the request row. */
36
37
  export declare function readPageFeedback(dir: string): PageFeedback;
37
38
  export interface CreatePageFeedbackCommentInput {
38
39
  quote: string;
39
40
  context?: string;
40
41
  note: string;
41
- /** Bound as the ticket's companion when none is recorded yet. An already
42
- * recorded companion wins; the first writer's companion stays the target. */
43
42
  companionNodeId?: string;
44
- /** Recorded with the companion binding; ignored once one is recorded. */
45
43
  firstCommentedSha256?: string;
46
44
  }
47
45
  export interface PageFeedbackMutation {
48
46
  comment: PageFeedbackComment;
49
47
  feedback: PageFeedback;
50
48
  }
51
- /** Append one open feedback comment under the ticket lock. */
52
- export declare function createPageFeedbackComment(dir: string, input: CreatePageFeedbackCommentInput): PageFeedbackMutation;
53
49
  export declare class PageTicketTerminalError extends Error {
54
50
  constructor();
55
51
  }
@@ -59,7 +55,8 @@ export declare class UnknownPageFeedbackCommentError extends Error {
59
55
  export declare class PageFeedbackCommentAlreadyResolvedError extends Error {
60
56
  constructor(commentId: string);
61
57
  }
62
- /** Mark one open comment resolved under the ticket lock. Terminal: a resolved
63
- * comment never reopens; a renewed concern is a new comment. */
58
+ /** Append feedback in the request transaction; terminal rows reject it. */
59
+ export declare function createPageFeedbackComment(dir: string, input: CreatePageFeedbackCommentInput): PageFeedbackMutation;
60
+ /** Resolve one feedback comment in the request transaction. */
64
61
  export declare function resolvePageFeedbackComment(dir: string, commentId: string): PageFeedbackMutation;
65
62
  export {};
@@ -1,116 +1,88 @@
1
- // Page feedback comments — ticket-attached notes on highlighted page content.
2
- // Feedback is about the agent's WORK, never part of the page answer: it lives
3
- // in its own file beside the page, delivers immediately to the ticket's
4
- // companion conversation, and reaches the response the sender reads at settle
5
- // only as the unresolved-item addendum. A sent comment is immutable — it is
6
- // either open or resolved, and only the companion resolves it. The file name
7
- // is `feedback-comments.json` — `feedback.json` is the review subsystem's
8
- // output file and must not be shadowed.
9
- import { existsSync, readFileSync } from 'node:fs';
10
1
  import { randomUUID } from 'node:crypto';
11
2
  import { z } from 'zod';
12
- import { atomicWriteJson } from './convention.js';
13
- import { withTicketLock } from './claim.js';
14
- import { pageTicketState } from './tickets.js';
3
+ import { withCanvasWrite } from '../canvas/db.js';
4
+ import { getRequestByDirectory } from './requests.js';
15
5
  export const PAGE_FEEDBACK_SCHEMA = 'crtr.page-feedback/v1';
6
+ /** The path is a presentation projection, not the feedback authority. */
16
7
  export function pageFeedbackPath(dir) { return `${dir}/feedback-comments.json`; }
17
8
  const pageFeedbackCommentSchema = z.object({
18
- id: z.string().min(1),
19
- /** The highlighted text the note is about; for a chart, its title. */
20
- quote: z.string().min(1),
21
- /** Surrounding text that locates the quote on the page. */
22
- context: z.string().optional(),
23
- note: z.string().min(1),
24
- /** Open until the companion has dealt with it; resolution is terminal. */
25
- status: z.enum(['open', 'resolved']),
26
- createdAt: z.string().datetime({ offset: true }),
27
- resolvedAt: z.string().datetime({ offset: true }).optional(),
9
+ id: z.string().min(1), quote: z.string().min(1), context: z.string().optional(), note: z.string().min(1),
10
+ status: z.enum(['open', 'resolved']), createdAt: z.string().datetime({ offset: true }), resolvedAt: z.string().datetime({ offset: true }).optional(),
28
11
  }).strict();
29
12
  const pageFeedbackSchema = z.object({
30
- schema: z.literal(PAGE_FEEDBACK_SCHEMA),
31
- /** The ticket-bound companion conversation every comment delivers to;
32
- * recorded by the first comment and never replaced. */
33
- companionNodeId: z.string().min(1).optional(),
34
- /** Hex sha256 of the page document at first comment, for the settle-time
35
- * change notice to the sender. */
36
- firstCommentedSha256: z.string().min(1).optional(),
37
- comments: z.array(pageFeedbackCommentSchema),
13
+ schema: z.literal(PAGE_FEEDBACK_SCHEMA), companionNodeId: z.string().min(1).optional(),
14
+ firstCommentedSha256: z.string().min(1).optional(), comments: z.array(pageFeedbackCommentSchema),
38
15
  }).strict();
39
- function emptyFeedback() {
40
- return { schema: PAGE_FEEDBACK_SCHEMA, comments: [] };
16
+ function emptyFeedback() { return { schema: PAGE_FEEDBACK_SCHEMA, comments: [] }; }
17
+ function requestForDirectory(dir) {
18
+ const request = getRequestByDirectory(dir);
19
+ if (request === null || request.kind !== 'page')
20
+ throw new Error(`unknown page request directory: ${dir}`);
21
+ return request;
41
22
  }
42
- /** Read a ticket's feedback comments; a ticket without any has an empty record. */
43
- export function readPageFeedback(dir) {
44
- const path = pageFeedbackPath(dir);
45
- if (!existsSync(path))
46
- return emptyFeedback();
47
- let raw;
23
+ function feedback(request) {
24
+ if (request.feedback_json === null)
25
+ return request.companion_node_id === null ? emptyFeedback() : { ...emptyFeedback(), companionNodeId: request.companion_node_id };
48
26
  try {
49
- raw = JSON.parse(readFileSync(path, 'utf8'));
27
+ return pageFeedbackSchema.parse(JSON.parse(request.feedback_json));
50
28
  }
51
- catch {
52
- throw new Error(`page feedback is not valid JSON: ${path}`);
29
+ catch (error) {
30
+ throw new Error(`page feedback is malformed for ${request.request_id}`, { cause: error });
53
31
  }
54
- const parsed = pageFeedbackSchema.safeParse(raw);
55
- if (!parsed.success)
56
- throw new Error(`page feedback is malformed: ${path}`);
57
- return parsed.data;
58
- }
59
- /** Append one open feedback comment under the ticket lock. */
60
- export function createPageFeedbackComment(dir, input) {
61
- return withTicketLock(dir, () => {
62
- requirePendingTicket(dir);
63
- const feedback = readPageFeedback(dir);
64
- if (feedback.companionNodeId === undefined && input.companionNodeId !== undefined) {
65
- feedback.companionNodeId = input.companionNodeId;
66
- if (input.firstCommentedSha256 !== undefined)
67
- feedback.firstCommentedSha256 = input.firstCommentedSha256;
68
- }
69
- const comment = {
70
- id: randomUUID(),
71
- quote: input.quote,
72
- ...(input.context === undefined ? {} : { context: input.context }),
73
- note: input.note,
74
- status: 'open',
75
- createdAt: new Date().toISOString(),
76
- };
77
- feedback.comments.push(comment);
78
- atomicWriteJson(pageFeedbackPath(dir), feedback);
79
- return { comment, feedback };
80
- });
81
32
  }
33
+ /** Read page feedback from the request row. */
34
+ export function readPageFeedback(dir) { return feedback(requestForDirectory(dir)); }
82
35
  export class PageTicketTerminalError extends Error {
83
36
  constructor() { super('ticket already has a terminal outcome'); }
84
37
  }
85
- /** Settlement commits its terminal outcome under this same ticket lock, so an
86
- * in-lock pending check is the race-free gate: no feedback mutation may land
87
- * after the first ticket-ending action, however long the caller's own
88
- * pre-lock work (a companion fork await) took. */
89
- function requirePendingTicket(dir) {
90
- const state = pageTicketState(dir);
91
- if (state === 'resolved' || state === 'canceled')
92
- throw new PageTicketTerminalError();
93
- }
94
38
  export class UnknownPageFeedbackCommentError extends Error {
95
39
  constructor(commentId) { super(`no feedback comment ${commentId} on this ticket`); }
96
40
  }
97
41
  export class PageFeedbackCommentAlreadyResolvedError extends Error {
98
42
  constructor(commentId) { super(`feedback comment ${commentId} is already resolved`); }
99
43
  }
100
- /** Mark one open comment resolved under the ticket lock. Terminal: a resolved
101
- * comment never reopens; a renewed concern is a new comment. */
44
+ /** Append feedback in the request transaction; terminal rows reject it. */
45
+ export function createPageFeedbackComment(dir, input) {
46
+ const request = requestForDirectory(dir);
47
+ return withCanvasWrite((db) => {
48
+ const current = db.prepare('SELECT * FROM human_requests WHERE request_id = ?').get(request.request_id);
49
+ if (current === undefined)
50
+ throw new Error(`human request disappeared: ${request.request_id}`);
51
+ if (current['state'] !== 'open')
52
+ throw new PageTicketTerminalError();
53
+ const row = { ...request, feedback_json: current['feedback_json'], companion_node_id: current['companion_node_id'] };
54
+ const next = feedback(row);
55
+ if (next.companionNodeId === undefined && input.companionNodeId !== undefined) {
56
+ next.companionNodeId = input.companionNodeId;
57
+ if (input.firstCommentedSha256 !== undefined)
58
+ next.firstCommentedSha256 = input.firstCommentedSha256;
59
+ }
60
+ const comment = { id: randomUUID(), quote: input.quote, ...(input.context === undefined ? {} : { context: input.context }), note: input.note, status: 'open', createdAt: new Date().toISOString() };
61
+ next.comments.push(comment);
62
+ pageFeedbackSchema.parse(next);
63
+ db.prepare('UPDATE human_requests SET feedback_json = ?, companion_node_id = COALESCE(companion_node_id, ?), updated_at = ? WHERE request_id = ?').run(JSON.stringify(next), next.companionNodeId ?? null, new Date().toISOString(), request.request_id);
64
+ return { comment, feedback: next };
65
+ });
66
+ }
67
+ /** Resolve one feedback comment in the request transaction. */
102
68
  export function resolvePageFeedbackComment(dir, commentId) {
103
- return withTicketLock(dir, () => {
104
- requirePendingTicket(dir);
105
- const feedback = readPageFeedback(dir);
106
- const comment = feedback.comments.find((entry) => entry.id === commentId);
69
+ const request = requestForDirectory(dir);
70
+ return withCanvasWrite((db) => {
71
+ const current = db.prepare('SELECT * FROM human_requests WHERE request_id = ?').get(request.request_id);
72
+ if (current === undefined)
73
+ throw new Error(`human request disappeared: ${request.request_id}`);
74
+ if (current['state'] !== 'open')
75
+ throw new PageTicketTerminalError();
76
+ const row = { ...request, feedback_json: current['feedback_json'], companion_node_id: current['companion_node_id'] };
77
+ const next = feedback(row);
78
+ const comment = next.comments.find((entry) => entry.id === commentId);
107
79
  if (comment === undefined)
108
80
  throw new UnknownPageFeedbackCommentError(commentId);
109
81
  if (comment.status === 'resolved')
110
82
  throw new PageFeedbackCommentAlreadyResolvedError(commentId);
111
83
  comment.status = 'resolved';
112
84
  comment.resolvedAt = new Date().toISOString();
113
- atomicWriteJson(pageFeedbackPath(dir), feedback);
114
- return { comment, feedback };
85
+ db.prepare('UPDATE human_requests SET feedback_json = ?, updated_at = ? WHERE request_id = ?').run(JSON.stringify(next), new Date().toISOString(), request.request_id);
86
+ return { comment, feedback: next };
115
87
  });
116
88
  }
@@ -0,0 +1,30 @@
1
+ import type { ProductPageComponents } from '../../types.js';
2
+ import type { PageDelivery, PageManifest, PageSource } from './page-schema.js';
3
+ /** One validated page, ready for the daemon to publish. */
4
+ export interface PreparedPage {
5
+ /** The authored source, verbatim, as the request's `document`. */
6
+ document: string;
7
+ /** The compiled CommonJS module the Gateway evaluates for JSX pages. */
8
+ module?: string;
9
+ manifest: PageManifest;
10
+ }
11
+ export interface PreparePageSourceOptions {
12
+ /** `jsx` carries .tsx semantics; `html` is a complete document. */
13
+ dialect: 'jsx' | 'html';
14
+ /** The complete authored page text, inline. */
15
+ document: string;
16
+ source?: PageSource;
17
+ productKinds?: ProductPageComponents;
18
+ delivery?: PageDelivery;
19
+ }
20
+ export interface PreparePageOptions {
21
+ /** The authored page source. */
22
+ sourceFile: string;
23
+ source?: PageSource;
24
+ productKinds?: ProductPageComponents;
25
+ delivery?: PageDelivery;
26
+ }
27
+ /** Validate one authored page document into a transportable request. */
28
+ export declare function preparePageSource(opts: PreparePageSourceOptions): PreparedPage;
29
+ /** Validate one authored page source file without reading or writing canvas state. */
30
+ export declare function preparePage(opts: PreparePageOptions): PreparedPage;
@@ -0,0 +1,19 @@
1
+ import { existsSync, readFileSync } from 'node:fs';
2
+ import { compilePage, deriveHtmlPageManifest, derivePageManifestFromDocument, PageAuthoringError } from './page.js';
3
+ /** Validate one authored page document into a transportable request. */
4
+ export function preparePageSource(opts) {
5
+ const stamped = { ...(opts.source ?? {}), emittedAt: opts.source?.emittedAt ?? new Date().toISOString() };
6
+ const delivery = opts.delivery ?? { placement: 'inline', inbox: false, reply: false };
7
+ if (opts.dialect === 'html')
8
+ return { document: opts.document, manifest: deriveHtmlPageManifest(opts.document, stamped, delivery) };
9
+ return { document: opts.document, module: compilePage(opts.document), manifest: derivePageManifestFromDocument(opts.document, opts.productKinds ?? [], stamped, delivery) };
10
+ }
11
+ /** Validate one authored page source file without reading or writing canvas state. */
12
+ export function preparePage(opts) {
13
+ const html = /\.html?$/i.test(opts.sourceFile);
14
+ if (!html && !opts.sourceFile.endsWith('.tsx'))
15
+ throw new PageAuthoringError(`page source file must be .tsx, .html, or .htm: ${opts.sourceFile}`);
16
+ if (!existsSync(opts.sourceFile))
17
+ throw new PageAuthoringError(`page source file does not exist: ${opts.sourceFile}`);
18
+ return preparePageSource({ ...opts, dialect: html ? 'html' : 'jsx', document: readFileSync(opts.sourceFile, 'utf8') });
19
+ }