@north-light/crouter 0.3.233 → 0.3.234

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 (379) hide show
  1. package/dist/api/client.js +16 -15
  2. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/__tests__/integration/provider-rotation.test.ts +1 -1
  3. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/claude-plugin-commands.ts +14 -17
  4. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/crtr-commands/index.ts +7 -11
  5. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/frontmatter-rules/index.ts +8 -8
  6. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/memory-slash-commands.ts +10 -11
  7. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/pi-shell-runner.ts +2 -2
  8. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/provider-rotation.d.ts +3 -3
  9. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/provider-rotation.js +7 -7
  10. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/provider-rotation.ts +8 -8
  11. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/statusline.ts +6 -6
  12. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/sysprompt-window.ts +3 -3
  13. package/dist/builtin-pi-packages/pi-crtr-extensions/lib/subscription-state.ts +1 -1
  14. package/dist/clients/attach/chrome/ticket-panel.js +15 -2
  15. package/dist/clients/attach/overlays/graph.js +0 -3
  16. package/dist/clients/attach/overlays/mcp.js +1 -2
  17. package/dist/clients/attach/render/chat-view.js +0 -12
  18. package/dist/clients/attach/render/crtr-output.js +0 -2
  19. package/dist/clients/attach/render/tool-calls.js +0 -6
  20. package/dist/clients/attach/slash/dispatch.js +8 -11
  21. package/dist/clients/attach/viewer.js +532 -534
  22. package/dist/clients/conversation/projection.js +0 -2
  23. package/dist/clients/inbox/controller.js +22 -3
  24. package/dist/clients/inbox/review/companion-pane.js +0 -4
  25. package/dist/clients/inbox/review-adapter.js +0 -7
  26. package/dist/clients/inbox/tui/slots.d.ts +3 -3
  27. package/dist/clients/inbox/tui/types.d.ts +2 -2
  28. package/dist/commands/api-client.js +0 -8
  29. package/dist/commands/attention.js +0 -8
  30. package/dist/commands/canvas-browse.js +2 -1
  31. package/dist/commands/canvas-config.js +0 -10
  32. package/dist/commands/canvas-history.js +2 -1
  33. package/dist/commands/cron.js +5 -18
  34. package/dist/commands/dashboard.js +0 -2
  35. package/dist/commands/human/feedback.js +2 -1
  36. package/dist/commands/human/prompts.js +2 -5
  37. package/dist/commands/human/queue.js +2 -7
  38. package/dist/commands/human/request.js +0 -14
  39. package/dist/commands/human/review.js +2 -1
  40. package/dist/commands/memory/__tests__/command-selector-and-mutation-guards.test.js +4 -4
  41. package/dist/commands/memory/find.js +2 -2
  42. package/dist/commands/memory/lint.js +2 -1
  43. package/dist/commands/memory/read.d.ts +2 -7
  44. package/dist/commands/memory/read.js +5 -16
  45. package/dist/commands/memory/shared.js +4 -7
  46. package/dist/commands/memory/write.js +4 -3
  47. package/dist/commands/node/bash.js +2 -1
  48. package/dist/commands/node/create.js +2 -1
  49. package/dist/commands/node/inspect.js +0 -6
  50. package/dist/commands/node/lifecycle.js +4 -13
  51. package/dist/commands/node/message.js +3 -2
  52. package/dist/commands/node/subscription.js +2 -1
  53. package/dist/commands/node/wait.js +4 -3
  54. package/dist/commands/node-context.js +2 -1
  55. package/dist/commands/node-lifecycle-revive.js +0 -2
  56. package/dist/commands/node-worktree.js +2 -1
  57. package/dist/commands/pkg/browse/actions.d.ts +1 -1
  58. package/dist/commands/pkg/browse/actions.js +4 -4
  59. package/dist/commands/pkg/browse/catalog.d.ts +1 -1
  60. package/dist/commands/pkg/browse/catalog.js +15 -15
  61. package/dist/commands/pkg/browse/command-view.d.ts +1 -1
  62. package/dist/commands/pkg/browse/command-view.js +3 -3
  63. package/dist/commands/pkg/browse/detail-panel.d.ts +1 -1
  64. package/dist/commands/pkg/browse/detail-panel.js +8 -8
  65. package/dist/commands/pkg/browse/doc-view.d.ts +1 -1
  66. package/dist/commands/pkg/browse/doc-view.js +3 -3
  67. package/dist/commands/pkg/browse/list-panel.d.ts +1 -1
  68. package/dist/commands/pkg/browse/list-panel.js +3 -3
  69. package/dist/commands/pkg/browse/model.d.ts +3 -3
  70. package/dist/commands/pkg/browse/shell.d.ts +2 -2
  71. package/dist/commands/pkg/browse/shell.js +13 -13
  72. package/dist/commands/pkg/plugin-manage.js +2 -1
  73. package/dist/commands/pkg/shared.js +0 -2
  74. package/dist/commands/profile/delete.js +2 -1
  75. package/dist/commands/push.js +2 -3
  76. package/dist/commands/revive.js +0 -2
  77. package/dist/commands/surface/node/placement.js +2 -1
  78. package/dist/commands/surface-edit.js +2 -1
  79. package/dist/commands/sys/config.js +0 -4
  80. package/dist/commands/sys/daemon.js +0 -10
  81. package/dist/commands/sys/doctor.js +3 -2
  82. package/dist/commands/sys/panels/broker-limits-panel.d.ts +2 -2
  83. package/dist/commands/sys/panels/broker-limits-panel.js +4 -4
  84. package/dist/commands/sys/panels/dependencies-panel.d.ts +2 -2
  85. package/dist/commands/sys/panels/dependencies-panel.js +2 -2
  86. package/dist/commands/sys/panels/keybindings-panel.d.ts +1 -1
  87. package/dist/commands/sys/panels/keybindings-panel.js +5 -5
  88. package/dist/commands/sys/panels/models-panel.d.ts +4 -4
  89. package/dist/commands/sys/panels/models-panel.js +11 -10
  90. package/dist/commands/sys/panels/plugins-panel.d.ts +2 -2
  91. package/dist/commands/sys/panels/plugins-panel.js +2 -2
  92. package/dist/commands/sys/panels/prefs-panel.d.ts +2 -2
  93. package/dist/commands/sys/panels/prefs-panel.js +4 -4
  94. package/dist/commands/sys/panels/profiles-panel.d.ts +1 -1
  95. package/dist/commands/sys/panels/profiles-panel.js +14 -13
  96. package/dist/commands/sys/panels/provider-panel.d.ts +3 -3
  97. package/dist/commands/sys/panels/provider-panel.js +8 -8
  98. package/dist/commands/sys/settings-shell.d.ts +3 -3
  99. package/dist/commands/sys/settings-shell.js +15 -15
  100. package/dist/commands/sys/setup-settings-runner.d.ts +3 -3
  101. package/dist/commands/sys/setup-settings-runner.js +5 -5
  102. package/dist/commands/sys/setup-wizard.d.ts +4 -4
  103. package/dist/commands/sys/setup-wizard.js +8 -8
  104. package/dist/commands/sys/support/prepare.js +4 -4
  105. package/dist/commands/sys/support/submit.js +2 -2
  106. package/dist/commands/sys/sync-deps.js +0 -2
  107. package/dist/commands/sys/sync-project-guidance.js +2 -1
  108. package/dist/commands/sys/sysprompt.js +2 -1
  109. package/dist/core/__tests__/boot.test.js +0 -8
  110. package/dist/core/__tests__/broker-extension-canvas-db-boundary.test.js +23 -1
  111. package/dist/core/__tests__/canvas-db-boundary.test.d.ts +1 -0
  112. package/dist/core/__tests__/canvas-db-boundary.test.js +46 -0
  113. package/dist/core/__tests__/canvas.test.js +1 -1
  114. package/dist/core/__tests__/child-death-wake.test.js +0 -2
  115. package/dist/core/__tests__/cron-armed-context-failure.test.d.ts +1 -0
  116. package/dist/core/__tests__/cron-armed-context-failure.test.js +145 -0
  117. package/dist/core/__tests__/daemon-wedge.test.js +0 -4
  118. package/dist/core/__tests__/dead-node-policy-table.test.js +0 -4
  119. package/dist/core/__tests__/fixtures/fake-engine.js +4 -14
  120. package/dist/core/__tests__/fixtures/fake-pi-host.js +5 -5
  121. package/dist/core/__tests__/focuses.test.js +0 -6
  122. package/dist/core/__tests__/fs-utils.test.d.ts +1 -0
  123. package/dist/core/__tests__/fs-utils.test.js +50 -0
  124. package/dist/core/__tests__/helpers/harness.js +1 -1
  125. package/dist/core/__tests__/inline-memory-refs.test.js +0 -2
  126. package/dist/core/__tests__/integration/broker-fork-seam.test.js +0 -2
  127. package/dist/core/__tests__/integration/broker-sdk-wiring.test.js +3 -16
  128. package/dist/core/__tests__/integration/command-plugins.test.js +0 -24
  129. package/dist/core/__tests__/integration/deferred-no-wake.test.js +0 -6
  130. package/dist/core/__tests__/integration/flagship-lifecycle.test.js +0 -26
  131. package/dist/core/__tests__/integration/live-mutation-verbs.test.js +0 -4
  132. package/dist/core/__tests__/integration/live-mutation.test.js +1 -5
  133. package/dist/core/__tests__/integration/refresh-stall-recycle.test.js +1 -5
  134. package/dist/core/__tests__/integration/revive.test.js +0 -12
  135. package/dist/core/__tests__/integration/subscription-delivery.test.js +0 -6
  136. package/dist/core/__tests__/integration/tmux-surface.test.js +24 -8
  137. package/dist/core/__tests__/integration/worktree-reap.test.js +0 -6
  138. package/dist/core/__tests__/lifecycle.test.js +0 -18
  139. package/dist/core/__tests__/memory-resolver-precedence.test.js +1 -1
  140. package/dist/core/__tests__/migration.test.js +2 -2
  141. package/dist/core/__tests__/on-read-dedup-resume.test.js +3 -3
  142. package/dist/core/__tests__/pid-identity-match.test.js +1 -1
  143. package/dist/core/__tests__/plugin-kinds.test.js +1 -1
  144. package/dist/core/__tests__/plugin-page-components.test.js +1 -1
  145. package/dist/core/__tests__/preview-registry-sync.test.js +42 -1
  146. package/dist/core/__tests__/respawn-throttle.test.js +0 -4
  147. package/dist/core/__tests__/revive-all.test.js +5 -5
  148. package/dist/core/__tests__/revive-parked-fresh.test.js +62 -11
  149. package/dist/core/__tests__/seam/broker-attach-multiclient.test.js +0 -8
  150. package/dist/core/__tests__/seam/broker-attach-stream.test.js +0 -8
  151. package/dist/core/__tests__/seam/broker-crash-teardown.test.js +0 -2
  152. package/dist/core/__tests__/tmux-driver-boundary.test.d.ts +1 -0
  153. package/dist/core/__tests__/tmux-driver-boundary.test.js +50 -0
  154. package/dist/core/activity-signal.d.ts +7 -0
  155. package/dist/core/activity-signal.js +38 -0
  156. package/dist/core/auto-update.js +2 -1
  157. package/dist/core/bootstrap.js +2 -1
  158. package/dist/core/canvas/__tests__/remote-canvas-source.test.js +1 -1
  159. package/dist/core/canvas/__tests__/remote-transport.test.js +1 -1
  160. package/dist/core/canvas/__tests__/render-remote.test.js +1 -1
  161. package/dist/core/canvas/browse/app.js +3 -2
  162. package/dist/core/canvas/browse/model.d.ts +1 -1
  163. package/dist/core/canvas/browse/model.js +0 -4
  164. package/dist/core/canvas/canvas.d.ts +22 -0
  165. package/dist/core/canvas/canvas.js +50 -18
  166. package/dist/core/canvas/crons.js +0 -6
  167. package/dist/core/canvas/db.d.ts +14 -20
  168. package/dist/core/canvas/db.js +30 -1332
  169. package/dist/core/canvas/focuses.js +0 -4
  170. package/dist/core/canvas/history.js +2 -1
  171. package/dist/core/canvas/migrations.d.ts +20 -0
  172. package/dist/core/canvas/migrations.js +1339 -0
  173. package/dist/core/canvas/nav-model.js +1 -11
  174. package/dist/core/canvas/nav-render.js +0 -8
  175. package/dist/core/canvas/remote-canvas-source.js +0 -2
  176. package/dist/core/canvas/render-source.js +31 -13
  177. package/dist/core/command-manifests/manifest.js +0 -10
  178. package/dist/core/command-manifests/schema.js +0 -4
  179. package/dist/core/command-plugins/transport/http-invoke.js +0 -14
  180. package/dist/core/command.js +0 -4
  181. package/dist/core/config.js +2 -1
  182. package/dist/core/feed/inbox.d.ts +10 -0
  183. package/dist/core/feed/inbox.js +18 -12
  184. package/dist/core/fs-utils.d.ts +9 -2
  185. package/dist/core/fs-utils.js +7 -15
  186. package/dist/core/help.js +0 -8
  187. package/dist/core/host-exports/export.d.ts +2 -2
  188. package/dist/core/host-exports/export.js +9 -12
  189. package/dist/core/human/answer.d.ts +2 -2
  190. package/dist/core/human/convention.d.ts +2 -5
  191. package/dist/core/human/convention.js +3 -13
  192. package/dist/core/human/page-schema.d.ts +2 -2
  193. package/dist/core/human/page-schema.js +2 -2
  194. package/dist/core/human/root.js +6 -23
  195. package/dist/core/human/scan.js +24 -7
  196. package/dist/core/human/tickets.d.ts +4 -1
  197. package/dist/core/human/tickets.js +20 -6
  198. package/dist/core/io.js +0 -12
  199. package/dist/core/memory/identity.js +0 -4
  200. package/dist/core/memory/inline-ref-inventory.js +3 -2
  201. package/dist/core/memory/lint.js +3 -6
  202. package/dist/core/memory-resolver.d.ts +5 -0
  203. package/dist/core/memory-resolver.js +14 -12
  204. package/dist/core/pagination.js +0 -2
  205. package/dist/core/preview-registry.d.ts +14 -0
  206. package/dist/core/preview-registry.js +2 -4
  207. package/dist/core/preview-result-path.js +2 -1
  208. package/dist/core/profiles/manifest.d.ts +37 -5
  209. package/dist/core/profiles/manifest.js +44 -24
  210. package/dist/core/profiles/select.js +0 -4
  211. package/dist/core/review/signal.js +4 -23
  212. package/dist/core/review/ticket-filter.d.ts +3 -3
  213. package/dist/core/review/ticket-filter.js +4 -9
  214. package/dist/core/runtime/__tests__/engine-route.test.js +1 -1
  215. package/dist/core/runtime/__tests__/spawn-env.test.js +0 -8
  216. package/dist/core/runtime/bearings-render.d.ts +56 -0
  217. package/dist/core/runtime/bearings-render.js +241 -0
  218. package/dist/core/runtime/bearings.d.ts +2 -17
  219. package/dist/core/runtime/bearings.js +12 -198
  220. package/dist/core/runtime/broker/auth-reload.d.ts +39 -0
  221. package/dist/core/runtime/broker/auth-reload.js +149 -0
  222. package/dist/core/runtime/broker/engine-drive.d.ts +26 -0
  223. package/dist/core/runtime/broker/engine-drive.js +257 -0
  224. package/dist/core/runtime/broker/engine-routing.d.ts +143 -0
  225. package/dist/core/runtime/broker/engine-routing.js +211 -0
  226. package/dist/core/runtime/broker/event-projection.js +3 -2
  227. package/dist/core/runtime/broker/fault-retry.js +5 -4
  228. package/dist/core/runtime/broker/frame-client.d.ts +38 -0
  229. package/dist/core/runtime/broker/frame-client.js +129 -0
  230. package/dist/core/runtime/broker/frame-dispatch.d.ts +12 -189
  231. package/dist/core/runtime/broker/frame-dispatch.js +89 -1476
  232. package/dist/core/runtime/broker/frame-engine-control.d.ts +20 -0
  233. package/dist/core/runtime/broker/frame-engine-control.js +231 -0
  234. package/dist/core/runtime/broker/frame-memory-refs.d.ts +19 -0
  235. package/dist/core/runtime/broker/frame-memory-refs.js +41 -0
  236. package/dist/core/runtime/broker/frame-model-query.d.ts +13 -0
  237. package/dist/core/runtime/broker/frame-model-query.js +69 -0
  238. package/dist/core/runtime/broker/frame-read.d.ts +19 -0
  239. package/dist/core/runtime/broker/frame-read.js +116 -0
  240. package/dist/core/runtime/broker/frame-replies.d.ts +24 -0
  241. package/dist/core/runtime/broker/frame-replies.js +64 -0
  242. package/dist/core/runtime/broker/frame-session.d.ts +34 -0
  243. package/dist/core/runtime/broker/frame-session.js +203 -0
  244. package/dist/core/runtime/broker/turn-ignition.d.ts +42 -0
  245. package/dist/core/runtime/broker/turn-ignition.js +74 -0
  246. package/dist/core/runtime/broker-extension-render.js +9 -85
  247. package/dist/core/runtime/broker-protocol.js +0 -4
  248. package/dist/core/runtime/broker-sdk.js +2 -1
  249. package/dist/core/runtime/broker.d.ts +1 -1
  250. package/dist/core/runtime/broker.js +5 -23
  251. package/dist/core/runtime/fault.js +3 -2
  252. package/dist/core/runtime/headless-pi.js +3 -2
  253. package/dist/core/runtime/host.js +2 -3
  254. package/dist/core/runtime/kickoff.js +0 -4
  255. package/dist/core/runtime/launch-target.js +2 -1
  256. package/dist/core/runtime/launch.js +3 -6
  257. package/dist/core/runtime/lifecycle.js +17 -26
  258. package/dist/core/runtime/managed-provider-cooling.d.ts +1 -1
  259. package/dist/core/runtime/naming.js +0 -2
  260. package/dist/core/runtime/nodes.js +2 -1
  261. package/dist/core/runtime/persona.js +0 -6
  262. package/dist/core/runtime/placement-tmux.js +2 -5
  263. package/dist/core/runtime/placement.js +4 -11
  264. package/dist/core/runtime/recap.js +4 -3
  265. package/dist/core/runtime/reopen.js +4 -7
  266. package/dist/core/runtime/reset.js +0 -4
  267. package/dist/core/runtime/resume-root.js +0 -2
  268. package/dist/core/runtime/revive.js +17 -13
  269. package/dist/core/runtime/session-list-cache.js +2 -2
  270. package/dist/core/runtime/shell-expansion.js +0 -2
  271. package/dist/core/runtime/spawn.js +0 -2
  272. package/dist/core/runtime/stream-watchdog.d.ts +0 -1
  273. package/dist/core/runtime/stream-watchdog.js +13 -18
  274. package/dist/core/runtime/tmux-bindings.js +0 -2
  275. package/dist/core/runtime/tmux-driver.d.ts +12 -0
  276. package/dist/core/runtime/tmux-driver.js +18 -13
  277. package/dist/core/runtime/warm-pool.js +0 -4
  278. package/dist/core/scope.js +4 -3
  279. package/dist/core/spawn.js +2 -3
  280. package/dist/core/subscription-state.js +10 -17
  281. package/dist/core/substrate/__tests__/surface-match-pre-command.test.js +3 -3
  282. package/dist/core/substrate/render.js +0 -12
  283. package/dist/core/substrate/schema.js +0 -12
  284. package/dist/core/substrate/surface-match.js +0 -2
  285. package/dist/core/termrender/display.js +2 -1
  286. package/dist/core/tui/page-host.d.ts +3 -3
  287. package/dist/core/tui/page-host.js +4 -4
  288. package/dist/core/tui/panel.js +3 -3
  289. package/dist/core/wake.js +0 -12
  290. package/dist/core/worktree.js +0 -2
  291. package/dist/daemon/__tests__/reconciler-signature.test.d.ts +1 -0
  292. package/dist/daemon/__tests__/reconciler-signature.test.js +36 -0
  293. package/dist/daemon/api/__tests__/seam/api-server.test.js +2 -16
  294. package/dist/daemon/api/__tests__/seam/b10-attach-modelauth.test.js +3 -11
  295. package/dist/daemon/api/__tests__/seam/leaf-api-parity.test.js +0 -6
  296. package/dist/daemon/api/handlers/attach.js +2 -1
  297. package/dist/daemon/api/handlers/broker-ops.js +3 -6
  298. package/dist/daemon/api/handlers/canvas.js +12 -18
  299. package/dist/daemon/api/handlers/feedback-comments.js +0 -8
  300. package/dist/daemon/api/handlers/health.js +3 -2
  301. package/dist/daemon/api/handlers/human-requests.js +0 -10
  302. package/dist/daemon/api/handlers/human.js +1 -5
  303. package/dist/daemon/api/handlers/inbox.js +0 -18
  304. package/dist/daemon/api/handlers/memory.js +1 -1
  305. package/dist/daemon/api/handlers/messages.js +4 -8
  306. package/dist/daemon/api/handlers/nodes.js +10 -21
  307. package/dist/daemon/api/handlers/reports.js +1 -1
  308. package/dist/daemon/api/handlers/reviews.js +1 -6
  309. package/dist/daemon/api/map.js +0 -6
  310. package/dist/daemon/api/router.js +0 -4
  311. package/dist/daemon/api/server.js +2 -5
  312. package/dist/daemon/boot-migrations.d.ts +14 -0
  313. package/dist/daemon/boot-migrations.js +31 -0
  314. package/dist/daemon/cron/armed-context.d.ts +21 -0
  315. package/dist/daemon/cron/armed-context.js +100 -0
  316. package/dist/daemon/cron/capture.d.ts +20 -0
  317. package/dist/daemon/cron/capture.js +45 -0
  318. package/dist/daemon/cron/disposition.d.ts +19 -0
  319. package/dist/daemon/cron/disposition.js +52 -0
  320. package/dist/daemon/cron/escalation.d.ts +12 -0
  321. package/dist/daemon/cron/escalation.js +124 -0
  322. package/dist/daemon/cron/held.d.ts +4 -0
  323. package/dist/daemon/cron/held.js +20 -0
  324. package/dist/daemon/cron/lease-recovery.d.ts +3 -0
  325. package/dist/daemon/cron/lease-recovery.js +119 -0
  326. package/dist/daemon/cron/live-runs.d.ts +11 -0
  327. package/dist/daemon/cron/live-runs.js +23 -0
  328. package/dist/daemon/cron/passes.d.ts +11 -0
  329. package/dist/daemon/cron/passes.js +78 -0
  330. package/dist/daemon/cron/settle.d.ts +28 -0
  331. package/dist/daemon/cron/settle.js +37 -0
  332. package/dist/daemon/cron/sinks.d.ts +2 -0
  333. package/dist/daemon/cron/sinks.js +100 -0
  334. package/dist/daemon/cron-run.d.ts +5 -41
  335. package/dist/daemon/cron-run.js +193 -882
  336. package/dist/daemon/crtrd-cli.js +2 -1
  337. package/dist/daemon/crtrd.js +22 -22
  338. package/dist/daemon/fleet.js +0 -4
  339. package/dist/daemon/human/deliver-action.js +2 -1
  340. package/dist/daemon/human/sweep.js +15 -4
  341. package/dist/daemon/manage.d.ts +2 -2
  342. package/dist/daemon/manage.js +4 -13
  343. package/dist/daemon/pidfile.js +2 -2
  344. package/dist/daemon/reconcilers/broker-supervision.js +26 -29
  345. package/dist/daemon/reconcilers/dormant-inbox.d.ts +1 -1
  346. package/dist/daemon/reconcilers/dormant-inbox.js +2 -1
  347. package/dist/daemon/reconcilers/live-obligation.js +2 -2
  348. package/dist/daemon/reconcilers/pending-review-submit.d.ts +1 -1
  349. package/dist/daemon/reconcilers/pending-review-submit.js +2 -1
  350. package/dist/daemon/reconcilers/storage-maintenance.d.ts +2 -1
  351. package/dist/daemon/reconcilers/storage-maintenance.js +2 -1
  352. package/dist/daemon/review/companion.js +2 -2
  353. package/dist/daemon/review/sweep.js +8 -3
  354. package/dist/migrations/001-surfaces-frontmatter.js +0 -6
  355. package/dist/migrations/corpus.js +2 -5
  356. package/dist/migrations/registry.js +15 -2
  357. package/dist/pi-extensions/__tests__/canvas-stophook-agentend.test.js +0 -4
  358. package/dist/pi-extensions/broker-local.js +2 -1
  359. package/dist/pi-extensions/canvas-bash-valve.js +3 -17
  360. package/dist/pi-extensions/canvas-context-intro.js +2 -7
  361. package/dist/pi-extensions/canvas-doc-substrate.js +2 -3
  362. package/dist/pi-extensions/canvas-goal-capture.js +2 -1
  363. package/dist/pi-extensions/canvas-inbox-watcher.js +5 -12
  364. package/dist/pi-extensions/canvas-passive-context.js +2 -5
  365. package/dist/pi-extensions/canvas-preview-result.js +2 -1
  366. package/dist/pi-extensions/canvas-prompt-scrub.js +2 -1
  367. package/dist/pi-extensions/canvas-recap.js +3 -16
  368. package/dist/pi-extensions/canvas-review-boundary.js +2 -5
  369. package/dist/pi-extensions/canvas-stophook.js +2 -26
  370. package/dist/pi-extensions/canvas-structured-output.js +2 -1
  371. package/dist/pi-extensions/canvas-tool-guide.js +2 -1
  372. package/dist/shared/__tests__/env-boundary.test.d.ts +1 -0
  373. package/dist/shared/__tests__/env-boundary.test.js +93 -0
  374. package/dist/shared/env.d.ts +68 -0
  375. package/dist/shared/env.js +171 -0
  376. package/package.json +3 -1
  377. package/runtime.lock.json +2 -2
  378. package/dist/core/canvas/render.d.ts +0 -47
  379. package/dist/core/canvas/render.js +0 -245
@@ -6,12 +6,15 @@
6
6
  // map.ts carries no projector — it is frozen under concurrency).
7
7
  //
8
8
  // `{id}` is the target node.
9
+ import { envProfileId } from '../../../shared/env.js';
9
10
  import { readdirSync } from 'node:fs';
10
11
  import { basename } from 'node:path';
11
- import { appendSituationalContext, closeNode, getNode, listNodes, nowIso, reviveNode, } from '../../../index.js';
12
- import { formatSituationalProse } from '../../../core/runtime/situational-context.js';
12
+ import { appendSituationalContext, formatSituationalProse } from '../../../core/runtime/situational-context.js';
13
+ import { closeNode } from '../../../core/runtime/close.js';
14
+ import { reviveNode } from '../../../core/runtime/revive.js';
15
+ import { nowIso } from '../../../core/fs-utils.js';
13
16
  import { assertLaunchModelRegistered, forkNode, spawnChild } from '../../../core/runtime/spawn.js';
14
- import { childrenOf, subscribersOf, subscriptionsOf, setMessageWait, updateNode } from '../../../core/canvas/canvas.js';
17
+ import { childrenOf, getNode, listNodes, subscribersOf, subscriptionsOf, setMessageWait, updateNode } from '../../../core/canvas/canvas.js';
15
18
  import { subtreeIds } from '../../../core/canvas/nav-model.js';
16
19
  import { ticketCountsForNodes } from '../../../core/canvas/attention.js';
17
20
  import { contextDir, reportsDir } from '../../../core/canvas/paths.js';
@@ -40,9 +43,7 @@ import { cronWakeOrigin } from '../../../core/runtime/bearings.js';
40
43
  import { notFound, usage } from '../../../core/errors.js';
41
44
  import { toCloseResultDTO, toNodeDetailDTO, toNodeSummaryDTO, toReviveResultDTO } from '../map.js';
42
45
  import { ApiError } from '../../../api/index.js';
43
- // ---------------------------------------------------------------------------
44
46
  // Shared helpers
45
- // ---------------------------------------------------------------------------
46
47
  function requireMeta(id) {
47
48
  const meta = getNode(id);
48
49
  if (meta === null)
@@ -76,9 +77,7 @@ function countFiles(id) {
76
77
  }
77
78
  return n;
78
79
  }
79
- // ---------------------------------------------------------------------------
80
80
  // POST /v1/nodes — create immediately (201)
81
- // ---------------------------------------------------------------------------
82
81
  /** Validate + narrow the create body (spec §6.2). */
83
82
  function parseCreateBody(body) {
84
83
  if (typeof body !== 'object' || body === null) {
@@ -192,7 +191,7 @@ export async function handleCreate(ctx, deps = {}) {
192
191
  // buildRecipe already defaults `prompt` to ''. The CLI keeps every no_kickoff
193
192
  // guards (root-only / no-prompt / no-orchestrator).
194
193
  const parent = req.parent ?? undefined;
195
- // --- Immediate spawn (201). A managed child needs a parent; a root does not. ---
194
+ // Immediate spawn (201). A managed child needs a parent; a root does not.
196
195
  if (parent == null && req.root !== true) {
197
196
  throw usage('a node create requires a parent (or root: true for an independent root)');
198
197
  }
@@ -233,9 +232,7 @@ export async function handleCreate(ctx, deps = {}) {
233
232
  writeOutputSchema(result.node.node_id, 'terminal', schema);
234
233
  return { status: 201, body: detailOf(result.node.node_id) };
235
234
  }
236
- // ---------------------------------------------------------------------------
237
235
  // GET /v1/nodes — list (filters) ; GET /v1/nodes/{id} — detail
238
- // ---------------------------------------------------------------------------
239
236
  function singleQueryValue(ctx, name) {
240
237
  const values = ctx.query.getAll(name);
241
238
  if (values.length > 1)
@@ -343,9 +340,7 @@ function handleDetail(ctx) {
343
340
  requireMeta(id);
344
341
  return { status: 200, body: detailOf(id) };
345
342
  }
346
- // ---------------------------------------------------------------------------
347
343
  // Read endpoints (snapshot / transcript / context / artifacts)
348
- // ---------------------------------------------------------------------------
349
344
  async function handleSnapshot(ctx) {
350
345
  const id = ctx.params['id'];
351
346
  requireMeta(id);
@@ -458,9 +453,7 @@ function handleArtifacts(ctx) {
458
453
  const body = { node_id: id, artifacts };
459
454
  return { status: 200, body };
460
455
  }
461
- // ---------------------------------------------------------------------------
462
456
  // Lifecycle verbs
463
- // ---------------------------------------------------------------------------
464
457
  async function handleFork(ctx) {
465
458
  const id = ctx.params['id'];
466
459
  requireMeta(id);
@@ -601,9 +594,7 @@ function handleReviveAll() {
601
594
  const body = { revived: [...result.revived], failed: [...result.failed] };
602
595
  return { status: 200, body };
603
596
  }
604
- // ---------------------------------------------------------------------------
605
597
  // PATCH /v1/nodes/{id}/config — model / situational-context (spec §6.2)
606
- // ---------------------------------------------------------------------------
607
598
  export async function handleConfig(ctx, deps = {}) {
608
599
  const id = ctx.params['id'];
609
600
  let meta = requireMeta(id);
@@ -646,7 +637,7 @@ export async function handleConfig(ctx, deps = {}) {
646
637
  // Re-read so the launch-spec rebuild below sees the fresh model pin.
647
638
  meta = requireMeta(id);
648
639
  }
649
- // --- kind / mode / lifecycle: reshape via canonical primitive (CLI parity). ---
640
+ // kind / mode / lifecycle: reshape via canonical primitive (CLI parity).
650
641
  if (patch.kind !== undefined || patch.mode !== undefined || patch.lifecycle !== undefined) {
651
642
  if (patch.kind !== undefined)
652
643
  await assertKindServer(patch.kind);
@@ -671,7 +662,7 @@ export async function handleConfig(ctx, deps = {}) {
671
662
  ...(patch.lifecycle !== undefined ? { lifecycle: newLifecycle } : {}),
672
663
  }, beforePersist);
673
664
  }
674
- // --- name: rename the row and, when it has a live viewer window, that window. ---
665
+ // name: rename the row and, when it has a live viewer window, that window.
675
666
  if (patch.name !== undefined) {
676
667
  meta = updateNode(id, { name: patch.name });
677
668
  if (meta.window)
@@ -685,15 +676,13 @@ export async function handleConfig(ctx, deps = {}) {
685
676
  * ambient configuration; prospective reads validate their resolved target. The
686
677
  * read is subprocess-isolated — crtrd must never resolve config on its own loop. */
687
678
  async function assertKindServer(kind) {
688
- const kinds = Object.keys((await readMergedLaunchConfigAsync(process.cwd(), process.env['CRTR_PROFILE_ID'] || null)).kinds);
679
+ const kinds = Object.keys((await readMergedLaunchConfigAsync(process.cwd(), envProfileId() || null)).kinds);
689
680
  if (!kinds.includes(kind)) {
690
681
  throw usage(`unknown kind: ${kind}`, { received: kind, next: `Valid kinds: ${kinds.sort().join(', ')}.` });
691
682
  }
692
683
  }
693
- // ---------------------------------------------------------------------------
694
684
  // Route table (revive-all registered before `:id` collisions are impossible —
695
685
  // different methods — but ordered defensively).
696
- // ---------------------------------------------------------------------------
697
686
  export const nodeRoutes = [
698
687
  { method: 'POST', pattern: '/v1/nodes', handler: handleCreate },
699
688
  { method: 'GET', pattern: '/v1/nodes', handler: handleList },
@@ -6,7 +6,7 @@
6
6
  // `{id}` is the REPORTING node (client-side = CRTR_NODE_ID).
7
7
  import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs';
8
8
  import { join } from 'node:path';
9
- import { getNode } from '../../../index.js';
9
+ import { getNode } from '../../../core/canvas/canvas.js';
10
10
  import { push, FinalizationError } from '../../../core/feed/feed.js';
11
11
  import { autoDropCleanManagedWorktree, hasOpenManagedWorktree, missingWorktreeRecoveryHint } from '../../../core/worktree.js';
12
12
  import { reportsDir } from '../../../core/canvas/paths.js';
@@ -1,10 +1,8 @@
1
1
  // Canonical daemon-owned review lifecycle routes. Handlers validate the full
2
2
  // wire request, delegate lifecycle work to review services, and project only
3
3
  // committed daemon state.
4
- import { existsSync } from 'node:fs';
5
- import { ApiError } from '../../../api/index.js';
6
4
  import { isSafeNodeId, reviewResultPath } from '../../../core/canvas/paths.js';
7
- import { responsePath, reviewPath } from '../../../core/human/convention.js';
5
+ import { responsePath } from '../../../core/human/convention.js';
8
6
  import { ticketDir } from '../../../core/human/root.js';
9
7
  import { createReview } from '../../../core/review/birth.js';
10
8
  import { readAnchorBase, getReviewByBridge, listReviews, requireVisibleReview } from '../../../core/review/store.js';
@@ -168,9 +166,6 @@ function handleByBridge(ctx) {
168
166
  const review = getReviewByBridge(bridgeNodeId);
169
167
  if (review !== null)
170
168
  return { status: 200, body: toReviewResponseDTO(review) };
171
- if (existsSync(reviewPath(ticketDir(bridgeNodeId)))) {
172
- throw new ApiError(409, 'review_predates_system', 'this review predates canonical review records; cancel it and re-file the review', { bridge_node_id: bridgeNodeId });
173
- }
174
169
  throw new ReviewOperationError('unknown_review', 'review was not found', { received: bridgeNodeId });
175
170
  }
176
171
  async function handleSubmit(ctx) {
@@ -20,9 +20,7 @@ import { ExitCode } from '../../types.js';
20
20
  import { sinkDisplay } from '../cron-sink.js';
21
21
  import { responsePath } from '../../core/human/convention.js';
22
22
  import { ticketDir } from '../../core/human/root.js';
23
- // ---------------------------------------------------------------------------
24
23
  // Node projections
25
- // ---------------------------------------------------------------------------
26
24
  /** Project the queryable row (what `listNodes` yields) into a summary DTO. */
27
25
  export function toNodeSummaryDTO(row) {
28
26
  return {
@@ -144,9 +142,7 @@ export function toNodeDetailDTO(meta, edges) {
144
142
  dto.worktree = toNodeWorktreeDTO(meta.managed_worktree);
145
143
  return dto;
146
144
  }
147
- // ---------------------------------------------------------------------------
148
145
  // Action-result projections
149
- // ---------------------------------------------------------------------------
150
146
  /** Project a `reviveNode` result. `revived` is true only when a fresh launch
151
147
  * actually happened — the double-revive no-op (already-alive) returns without a
152
148
  * `launch` handle, so its absence is the "nothing relaunched" signal. `status`
@@ -398,9 +394,7 @@ export function toFeedbackResultDTO(result) {
398
394
  }
399
395
  const _exitIntentParity = true;
400
396
  void _exitIntentParity;
401
- // ---------------------------------------------------------------------------
402
397
  // Error → HTTP status mapping (spec §8)
403
- // ---------------------------------------------------------------------------
404
398
  /** The only internal review-error to public HTTP projection. Every internal
405
399
  * review failure is listed explicitly so handlers never need to invent a status
406
400
  * or public code. */
@@ -102,9 +102,7 @@ export class Router {
102
102
  }
103
103
  }
104
104
  }
105
- // ---------------------------------------------------------------------------
106
105
  // Matching
107
- // ---------------------------------------------------------------------------
108
106
  /** Split a path/pattern into non-empty segments (leading/trailing '/' ignored). */
109
107
  function splitPath(p) {
110
108
  return p.split('/').filter((s) => s.length > 0);
@@ -127,9 +125,7 @@ function matchSegments(pattern, actual) {
127
125
  }
128
126
  return params;
129
127
  }
130
- // ---------------------------------------------------------------------------
131
128
  // Body + response I/O
132
- // ---------------------------------------------------------------------------
133
129
  class BodyTooLargeError extends Error {
134
130
  }
135
131
  /** Read and JSON-parse the request body. Returns `undefined` for an empty body.
@@ -16,6 +16,7 @@ import { createServer } from 'node:http';
16
16
  import { chmodSync, existsSync, unlinkSync } from 'node:fs';
17
17
  import { apiSocketPath } from '../../core/canvas/paths.js';
18
18
  import { emitEvent } from '../../core/events/emit.js';
19
+ import { envCrtrdTcp } from '../../shared/env.js';
19
20
  import { Router } from './router.js';
20
21
  import { handleAttachUpgrade } from './bridge.js';
21
22
  import { attachRoutes } from './handlers/attach.js';
@@ -204,10 +205,8 @@ export function createApiServer(opts = {}) {
204
205
  emitEvent({ event: 'api.server.failed', level: 'error', error: err });
205
206
  });
206
207
  };
207
- // -------------------------------------------------------------------------
208
208
  // Unix socket — ALWAYS bound. Unlink any stale socket first (mirror
209
209
  // broker.ts's listener setup), listen, then chmod 0600 once the file exists.
210
- // -------------------------------------------------------------------------
211
210
  const socketServer = createServer(onRequest);
212
211
  socketServer.on('upgrade', onUpgrade);
213
212
  bindErrorHandler(socketServer);
@@ -230,11 +229,9 @@ export function createApiServer(opts = {}) {
230
229
  });
231
230
  }
232
231
  });
233
- // -------------------------------------------------------------------------
234
232
  // TCP — opt-in only. A SECOND http.Server sharing the same handlers.
235
- // -------------------------------------------------------------------------
236
233
  let tcpServer;
237
- const tcpSpec = opts.tcp ?? process.env['CRTRD_TCP'];
234
+ const tcpSpec = opts.tcp ?? envCrtrdTcp();
238
235
  if (tcpSpec !== undefined && tcpSpec !== '') {
239
236
  const parsed = parseTcp(tcpSpec);
240
237
  if (parsed === null) {
@@ -0,0 +1,14 @@
1
+ export interface BootMigrationResult {
2
+ changed: boolean;
3
+ summary: Record<string, unknown>;
4
+ }
5
+ export interface BootMigration {
6
+ readonly id: string;
7
+ readonly description: string;
8
+ /** Runs unconditionally at every daemon boot. Must be safe to re-run every
9
+ * time (idempotent via its own convergence, not a version gate) and must
10
+ * isolate its own per-item failures — a thrown error here is NOT caught by
11
+ * the boot sequence and would abort daemon startup. */
12
+ apply(): BootMigrationResult;
13
+ }
14
+ export declare const BOOT_MIGRATIONS: readonly BootMigration[];
@@ -0,0 +1,31 @@
1
+ // The ordered boot-migration list — the THIRD of this repo's three migration
2
+ // homes, and the one for filesystem repairs that must run unconditionally at
3
+ // every daemon boot, before supervision can revive anything. No version gate
4
+ // (there is no single database to hold a `user_version`, and no `crtr sys
5
+ // migrate` invocation to drive it): each entry converges on its own, the same
6
+ // way `migrateLegacyInboxIdentities` already does — a re-run over
7
+ // already-migrated state rewrites nothing.
8
+ //
9
+ // The other two homes:
10
+ // - `../core/canvas/migrations.ts` — canvas.db's versioned schema chain,
11
+ // gated by `PRAGMA user_version`, applied once per database inside
12
+ // `migrate()`.
13
+ // - `../migrations/registry.ts` — the `STATE_MIGRATIONS` registry for
14
+ // on-disk memory documents, profile manifests, and the corpus lane,
15
+ // driven by `crtr sys migrate`.
16
+ //
17
+ // Authoring contract: one entry per boot-time filesystem repair, appended
18
+ // here in order. `crtrd.ts` iterates this list once at boot and emits one
19
+ // `daemon.boot_migration.applied` event per entry that reports a change —
20
+ // add a step here instead of a bespoke call in the boot sequence.
21
+ import { migrateLegacyInboxIdentities } from '../core/feed/inbox-migrate.js';
22
+ export const BOOT_MIGRATIONS = [
23
+ {
24
+ id: 'legacy-inbox-identities',
25
+ description: 'normalize pre-cutover inbox entry/operation ids (UUID-spelled or missing) and cursors (ISO-timestamp boundaries) into canonical 32-lowercase-hex identities the strict inbox reader requires.',
26
+ apply: () => {
27
+ const summary = migrateLegacyInboxIdentities();
28
+ return { changed: summary.nodes > 0 || summary.failures > 0, summary: { ...summary } };
29
+ },
30
+ },
31
+ ];
@@ -0,0 +1,21 @@
1
+ import type { Cron } from '../../core/canvas/crons.js';
2
+ /** The cron's profile, re-validated now and returned in canonical id form.
3
+ *
4
+ * A DELETED profile is the spec's root fallback: null. An UNREADABLE one is
5
+ * not — the profile is still there, so running as root would execute the
6
+ * command under an identity nobody armed, with the profile's own env store and
7
+ * purview silently replaced. That throws, and the run fails loudly once
8
+ * instead of running wrong on every tick. */
9
+ export declare function resolveLiveProfile(c: Cron): string | null;
10
+ export declare function resolveLiveProfileOrRoot(c: Cron): string | null;
11
+ export declare function resolveLiveProfileName(c: Cron): string | undefined;
12
+ /** A stored context cannot be rebuilt; the run fails rather than running under a context nobody armed. */
13
+ export declare class ArmedContextError extends Error {
14
+ constructor(message: string);
15
+ }
16
+ export interface ArmedRunContext {
17
+ profileId: string | null;
18
+ env: NodeJS.ProcessEnv;
19
+ }
20
+ /** Rebuild the armed context. Corrupt snapshots and unreadable profiles fail rather than silently changing its identity or environment. */
21
+ export declare function buildArmedRunContext(c: Cron, runId: string): ArmedRunContext;
@@ -0,0 +1,100 @@
1
+ import { envHomeOverride } from '../../shared/env.js';
2
+ import { loadProfileManifest, lookupProfile } from '../../core/profiles/manifest.js';
3
+ import { readProfileEnvVars } from '../../core/profiles/env-store.js';
4
+ import { ADMITTED_HOST_PATH_ENV, buildOperationalEnvBase } from '../../core/runtime/spawn-env.js';
5
+ /** The cron's profile, re-validated now and returned in canonical id form.
6
+ *
7
+ * A DELETED profile is the spec's root fallback: null. An UNREADABLE one is
8
+ * not — the profile is still there, so running as root would execute the
9
+ * command under an identity nobody armed, with the profile's own env store and
10
+ * purview silently replaced. That throws, and the run fails loudly once
11
+ * instead of running wrong on every tick. */
12
+ export function resolveLiveProfile(c) {
13
+ if (c.profile == null)
14
+ return null;
15
+ let lookup;
16
+ try {
17
+ lookup = lookupProfile(c.profile);
18
+ }
19
+ catch {
20
+ return null; // an ambiguous NAME resolves no better than a deleted profile
21
+ }
22
+ if (lookup.kind === 'resolved')
23
+ return lookup.entry.profileId;
24
+ if (lookup.kind === 'absent')
25
+ return null;
26
+ throw new ArmedContextError(`the armed profile "${c.profile}" is still on this host but its manifest could not be read (${lookup.manifestPath}) — running as root would silently give this cron a different identity, env store, and purview. Repair the manifest, or re-arm the cron under a profile that resolves.`);
27
+ }
28
+ // The escalation node must still report an unreadable manifest, so its own spawn falls back to root.
29
+ export function resolveLiveProfileOrRoot(c) {
30
+ try {
31
+ return resolveLiveProfile(c);
32
+ }
33
+ catch {
34
+ return null;
35
+ }
36
+ }
37
+ export function resolveLiveProfileName(c) {
38
+ if (c.profile == null)
39
+ return undefined;
40
+ try {
41
+ return loadProfileManifest(c.profile).manifest.name;
42
+ }
43
+ catch {
44
+ return undefined;
45
+ }
46
+ }
47
+ /** A stored context cannot be rebuilt; the run fails rather than running under a context nobody armed. */
48
+ export class ArmedContextError extends Error {
49
+ constructor(message) {
50
+ super(message);
51
+ this.name = 'ArmedContextError';
52
+ }
53
+ }
54
+ // Invalid snapshots are corruption: dropping `--env` would run under an environment nobody armed, so fail instead.
55
+ function parseArmedEnv(envJson) {
56
+ if (envJson == null)
57
+ return {};
58
+ let parsed;
59
+ try {
60
+ parsed = JSON.parse(envJson);
61
+ }
62
+ catch (err) {
63
+ throw new ArmedContextError(`the armed env snapshot is not valid JSON (${err.message}) — this cron's stored env_json is corrupt, so the run would silently drop every armed variable. Re-arm the cron with a valid --env.`);
64
+ }
65
+ if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {
66
+ throw new ArmedContextError("the armed env snapshot is not a JSON object — this cron's stored env_json is corrupt. Re-arm the cron with a valid --env.");
67
+ }
68
+ const extraEnv = {};
69
+ for (const [k, v] of Object.entries(parsed)) {
70
+ if (typeof v !== 'string') {
71
+ throw new ArmedContextError(`the armed env snapshot's value for "${k}" is not a string — this cron's stored env_json is corrupt, so the run would silently drop that variable. Re-arm the cron with a valid --env.`);
72
+ }
73
+ extraEnv[k] = v;
74
+ }
75
+ return extraEnv;
76
+ }
77
+ /** Rebuild the armed context. Corrupt snapshots and unreadable profiles fail rather than silently changing its identity or environment. */
78
+ export function buildArmedRunContext(c, runId) {
79
+ const extraEnv = parseArmedEnv(c.env_json);
80
+ const profileId = resolveLiveProfile(c);
81
+ const operationalEnv = buildOperationalEnvBase({ targetCwd: c.cwd, targetProfileId: profileId });
82
+ const env = {
83
+ ...operationalEnv,
84
+ ...readProfileEnvVars(profileId),
85
+ ...extraEnv,
86
+ ...(profileId !== null ? { CRTR_PROFILE_ID: profileId } : {}),
87
+ CRTR_CRON_ID: c.cron_id,
88
+ CRTR_CRON_NAME: c.name,
89
+ CRTR_RUN_ID: runId,
90
+ };
91
+ // Preserve the daemon identity: otherwise child `crtr` resolves the default canvas.
92
+ const canvasHomeOverride = envHomeOverride();
93
+ if (canvasHomeOverride !== undefined && canvasHomeOverride !== '') {
94
+ env['CRTR_HOME'] = canvasHomeOverride;
95
+ }
96
+ if (c.anchor_node != null)
97
+ env['CRTR_ANCHOR'] = c.anchor_node;
98
+ env[ADMITTED_HOST_PATH_ENV] = operationalEnv[ADMITTED_HOST_PATH_ENV];
99
+ return { profileId, env };
100
+ }
@@ -0,0 +1,20 @@
1
+ /** Output head caps for the run log (spec: "capped, e.g. 4KB"). */
2
+ export declare const CRON_OUTPUT_HEAD_BYTES: number;
3
+ /** Bound between the run-timeout SIGTERM and a forced SIGKILL escalation, so
4
+ * a signal-trapping command cannot stall teardown's detached drain. */
5
+ export declare const CRON_KILL_GRACE_MS = 2000;
6
+ /** Poll interval while waiting for a SIGTERMed stale-lease run to actually die
7
+ * (each probe is a `ps` call, so this is deliberately coarse). */
8
+ export declare const CRON_DEATH_POLL_MS = 250;
9
+ /** sha256 of empty stdout — the change-detection hash a run that produced no
10
+ * output carries (a failed spawn, a recovered stale lease). */
11
+ export declare const EMPTY_STDOUT_SHA256: string;
12
+ /** A byte-capped capture of one output stream's head. Bytes are buffered and
13
+ * decoded exactly once, at the end — decoding per chunk both mis-measured the
14
+ * cap (UTF-16 code units, not bytes) and corrupted any multi-byte sequence
15
+ * that straddled a chunk boundary. Truncation is MARKED, never silent: a
16
+ * clipped body reads as complete output to whoever receives the delivery. */
17
+ export declare function newHeadCapture(): {
18
+ take(chunk: Buffer): void;
19
+ decode(): string;
20
+ };
@@ -0,0 +1,45 @@
1
+ // src/daemon/cron/capture.ts — output capture for a cron run's child process,
2
+ // plus the timing/hash constants the capture and kill mechanics share.
3
+ import { createHash } from 'node:crypto';
4
+ /** Output head caps for the run log (spec: "capped, e.g. 4KB"). */
5
+ export const CRON_OUTPUT_HEAD_BYTES = 4 * 1024;
6
+ /** Bound between the run-timeout SIGTERM and a forced SIGKILL escalation, so
7
+ * a signal-trapping command cannot stall teardown's detached drain. */
8
+ export const CRON_KILL_GRACE_MS = 2000;
9
+ /** Poll interval while waiting for a SIGTERMed stale-lease run to actually die
10
+ * (each probe is a `ps` call, so this is deliberately coarse). */
11
+ export const CRON_DEATH_POLL_MS = 250;
12
+ /** sha256 of empty stdout — the change-detection hash a run that produced no
13
+ * output carries (a failed spawn, a recovered stale lease). */
14
+ export const EMPTY_STDOUT_SHA256 = createHash('sha256').update('').digest('hex');
15
+ /** A byte-capped capture of one output stream's head. Bytes are buffered and
16
+ * decoded exactly once, at the end — decoding per chunk both mis-measured the
17
+ * cap (UTF-16 code units, not bytes) and corrupted any multi-byte sequence
18
+ * that straddled a chunk boundary. Truncation is MARKED, never silent: a
19
+ * clipped body reads as complete output to whoever receives the delivery. */
20
+ export function newHeadCapture() {
21
+ const chunks = [];
22
+ let bytes = 0;
23
+ let truncated = false;
24
+ return {
25
+ take(chunk) {
26
+ const room = CRON_OUTPUT_HEAD_BYTES - bytes;
27
+ if (room <= 0) {
28
+ truncated = true;
29
+ return;
30
+ }
31
+ if (chunk.length > room) {
32
+ chunks.push(chunk.subarray(0, room));
33
+ bytes = CRON_OUTPUT_HEAD_BYTES;
34
+ truncated = true;
35
+ return;
36
+ }
37
+ chunks.push(chunk);
38
+ bytes += chunk.length;
39
+ },
40
+ decode() {
41
+ const text = Buffer.concat(chunks).toString('utf8');
42
+ return truncated ? `${text}\n[… output truncated at ${CRON_OUTPUT_HEAD_BYTES} bytes]` : text;
43
+ },
44
+ };
45
+ }
@@ -0,0 +1,19 @@
1
+ import { type Cron } from '../../core/canvas/crons.js';
2
+ export interface RunSettleContext {
3
+ runId: string;
4
+ startedAtIso: string;
5
+ exitCode: number | null;
6
+ timedOut: boolean;
7
+ runTimeoutS: number;
8
+ processErrorMessage: string | null;
9
+ stdoutHead: string;
10
+ /** Hash the full output: a capped head can hide a change. */
11
+ stdoutSha256: string;
12
+ stderrHead: string;
13
+ }
14
+ export interface Disposition {
15
+ delivered: string;
16
+ /** A paused row is retained so `cron resume` can re-arm it. */
17
+ paused: boolean;
18
+ }
19
+ export declare function disposeSettledRun(c: Cron, ctx: RunSettleContext, outOfBand: boolean, selfCanceled?: boolean): Promise<Disposition>;
@@ -0,0 +1,52 @@
1
+ import { getCron, setCronLastOutputHash } from '../../core/canvas/crons.js';
2
+ import { escalateFailure, failureSummary } from './escalation.js';
3
+ import { deliverToSink } from './sinks.js';
4
+ export async function disposeSettledRun(c, ctx, outOfBand, selfCanceled = false) {
5
+ const prefix = outOfBand ? 'out-of-band: ' : '';
6
+ // A self-cancel still delivers its already-produced output; an external cancellation does not.
7
+ const fresh = getCron(c.cron_id);
8
+ if (fresh === null && !selfCanceled)
9
+ return { delivered: `${prefix}cron canceled mid-run — nothing delivered`, paused: false };
10
+ const effective = fresh ?? c;
11
+ const failed = ctx.timedOut || ctx.processErrorMessage !== null || ctx.exitCode !== 0;
12
+ // `cron run` compares but never rewrites the scheduled baseline.
13
+ let changed = true;
14
+ if (effective.on_output === 'on-change') {
15
+ changed = effective.last_output_hash !== ctx.stdoutSha256;
16
+ if (!outOfBand && fresh !== null)
17
+ setCronLastOutputHash(c.cron_id, ctx.stdoutSha256);
18
+ }
19
+ if (failed) {
20
+ if (effective.on_output === 'silent')
21
+ return { delivered: `${prefix}silent`, paused: false };
22
+ if (outOfBand)
23
+ return { delivered: `out-of-band: ${failureSummary(ctx, null)} — not escalated`, paused: false };
24
+ return { delivered: await escalateFailure(effective, ctx, null), paused: true };
25
+ }
26
+ switch (effective.on_output) {
27
+ case 'silent':
28
+ return { delivered: `${prefix}silent`, paused: false };
29
+ case 'on-failure':
30
+ return { delivered: `${prefix}ok — nothing to deliver (on-failure)`, paused: false };
31
+ case 'on-change':
32
+ if (!changed)
33
+ return { delivered: `${prefix}unchanged — not delivered (on-change)`, paused: false };
34
+ return deliverOrEscalate(effective, ctx, outOfBand, prefix);
35
+ case 'always':
36
+ return deliverOrEscalate(effective, ctx, outOfBand, prefix);
37
+ }
38
+ }
39
+ async function deliverOrEscalate(c, ctx, outOfBand, prefix) {
40
+ if (ctx.stdoutHead.trim() === '') {
41
+ return { delivered: `${prefix}empty stdout — nothing delivered`, paused: false };
42
+ }
43
+ try {
44
+ return { delivered: `${prefix}${await deliverToSink(c, ctx.stdoutHead)}`, paused: false };
45
+ }
46
+ catch (err) {
47
+ const why = `delivery failed: ${err.message}`;
48
+ if (outOfBand)
49
+ return { delivered: `out-of-band: ${why} — not escalated`, paused: false };
50
+ return { delivered: await escalateFailure(c, ctx, why), paused: true };
51
+ }
52
+ }
@@ -0,0 +1,12 @@
1
+ import { type Cron } from '../../core/canvas/crons.js';
2
+ import type { RunSettleContext } from './disposition.js';
3
+ export declare function failureSummary(ctx: RunSettleContext, deliveryFailure: string | null): string;
4
+ /** Pause the cron and spawn the escalation node in the cron's originating
5
+ * profile (root fallback). Returns the run-log `delivered` string. Never
6
+ * throws: an escalation spawn failure is recorded and logged, the pause
7
+ * stands (the row must not keep refiring a broken command). */
8
+ export declare function escalateFailure(c: Cron, ctx: RunSettleContext, deliveryFailure: string | null): Promise<string>;
9
+ /** The delivery-failure line an escalated crashed ONE-SHOT carries: its side
10
+ * effects may already have landed in full, so a human/agent decides whether to
11
+ * redo it rather than the daemon re-firing it. */
12
+ export declare function crashedOneShotSummary(why: string): string;