@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
@@ -0,0 +1,124 @@
1
+ // src/daemon/cron/escalation.ts — the cron failure path: pause the row and
2
+ // spawn a terminal node holding everything it needs to deal with the failure.
3
+ // Pausing IS the dedup — a paused row is never due, so nothing refires while
4
+ // the escalation node works.
5
+ import { getNode } from '../../core/canvas/canvas.js';
6
+ import { fullName } from '../../core/canvas/labels.js';
7
+ import { listCronRuns, setCronState } from '../../core/canvas/crons.js';
8
+ import { emitEvent } from '../../core/events/emit.js';
9
+ import { spawnChild } from '../../core/runtime/spawn.js';
10
+ import { cronWakeOrigin } from '../../core/runtime/bearings.js';
11
+ import { cadenceDisplay } from '../../core/wake.js';
12
+ import { resolveLiveProfileOrRoot } from './armed-context.js';
13
+ export function failureSummary(ctx, deliveryFailure) {
14
+ if (deliveryFailure !== null)
15
+ return deliveryFailure;
16
+ if (ctx.timedOut)
17
+ return `run timed out after ${ctx.runTimeoutS}s (killed; recorded exit -1)`;
18
+ if (ctx.processErrorMessage !== null)
19
+ return `run process error: ${ctx.processErrorMessage}`;
20
+ return `command exited ${ctx.exitCode ?? '(no exit observed)'}`;
21
+ }
22
+ /** The escalation node's kickoff: the cron's identity/config, this failure,
23
+ * the run-log ring, the creating-node reference, and a mandate that names its
24
+ * actual options (resume after verifying / cancel / ask the human). */
25
+ function buildEscalationKickoff(c, ctx, deliveryFailure) {
26
+ const creator = c.created_by != null ? getNode(c.created_by) : null;
27
+ const creatorLine = c.created_by == null
28
+ ? 'created by: (no creating node recorded)'
29
+ : creator !== null
30
+ ? `created by: node ${c.created_by} ("${fullName(creator)}") — still on the canvas; consider telling it what happened`
31
+ : `created by: node ${c.created_by} (now gone)`;
32
+ const schedule = c.recur != null ? `every ${cadenceDisplay(c.recur)}` : `one-shot at ${c.fire_at}`;
33
+ const priorRuns = listCronRuns(c.cron_id)
34
+ .slice(0, 10)
35
+ .map((r) => {
36
+ const exit = r.exit_code === null ? 'no exit' : `exit ${r.exit_code}`;
37
+ const head = (r.stderr_head ?? r.stdout_head ?? '').split('\n')[0]?.slice(0, 120) ?? '';
38
+ return `- ${r.started} — ${exit}, ${r.duration_ms}ms — ${r.delivered ?? ''}${head !== '' ? ` — ${head}` : ''}`;
39
+ });
40
+ return [
41
+ `A scheduled cron FAILED and has paused itself. You were spawned to deal with it.`,
42
+ '',
43
+ `- cron: "${c.name}" (${c.cron_id})`,
44
+ `- schedule: ${schedule}${c.tz != null ? ` (tz ${c.tz})` : ''}`,
45
+ `- context: cwd ${c.cwd}${c.profile != null ? `, profile ${c.profile}` : ''}`,
46
+ `- ${creatorLine}`,
47
+ `- this failure: ${failureSummary(ctx, deliveryFailure)}`,
48
+ '',
49
+ '## The command',
50
+ '',
51
+ '```bash',
52
+ c.command,
53
+ '```',
54
+ '',
55
+ '## This run',
56
+ '',
57
+ `- exit: ${ctx.timedOut ? '-1 (timeout)' : String(ctx.exitCode)}`,
58
+ `- stdout (head): ${ctx.stdoutHead === '' ? '(empty)' : ''}`,
59
+ ...(ctx.stdoutHead !== '' ? ['```', ctx.stdoutHead, '```'] : []),
60
+ `- stderr (head): ${ctx.stderrHead === '' ? '(empty)' : ''}`,
61
+ ...(ctx.stderrHead !== '' ? ['```', ctx.stderrHead, '```'] : []),
62
+ '',
63
+ '## Recent runs (most recent first, this failure not yet recorded)',
64
+ '',
65
+ ...(priorRuns.length > 0 ? priorRuns : ['(no prior runs recorded)']),
66
+ '',
67
+ '## Your mandate',
68
+ '',
69
+ `Diagnose why it failed and decide what to do:`,
70
+ `- Transient or fixable: fix the environment/command context, verify with \`crtr cron run ${c.cron_id}\` (fires now, out of band, shows you the result), then \`crtr cron resume ${c.cron_id}\` to re-arm it.`,
71
+ `- Obsolete: \`crtr cron cancel ${c.cron_id}\`.`,
72
+ `- The user's call (or the fix needs a change you cannot make): \`crtr human send\`.`,
73
+ '',
74
+ 'The cron stays paused until someone resumes it — nothing refires while you work.',
75
+ ].join('\n');
76
+ }
77
+ /** Pause the cron and spawn the escalation node in the cron's originating
78
+ * profile (root fallback). Returns the run-log `delivered` string. Never
79
+ * throws: an escalation spawn failure is recorded and logged, the pause
80
+ * stands (the row must not keep refiring a broken command). */
81
+ export async function escalateFailure(c, ctx, deliveryFailure) {
82
+ setCronState(c.cron_id, 'paused');
83
+ const creatorAlive = c.created_by != null && getNode(c.created_by) !== null;
84
+ try {
85
+ const res = await spawnChild({
86
+ kind: 'general',
87
+ cwd: c.cwd,
88
+ prompt: buildEscalationKickoff(c, ctx, deliveryFailure),
89
+ // The spec's word is TERMINAL: a node that deals with this failure and
90
+ // reaps. With the creator gone there is nobody to parent it, but
91
+ // parentless must not mean resident-and-forefront — see deliverSpawnSink.
92
+ ...(creatorAlive ? { parent: c.created_by } : { root: true, rootLifecycle: 'terminal' }),
93
+ readiness: 'launch',
94
+ profile: resolveLiveProfileOrRoot(c),
95
+ wakeOrigin: cronWakeOrigin(c, 'node_birth'),
96
+ });
97
+ emitEvent({
98
+ level: 'info',
99
+ event: 'cron.failure.escalated',
100
+ fields: { cron_id: c.cron_id, name: c.name, escalation_node: res.node.node_id },
101
+ });
102
+ return `failure: cron paused, escalation node ${res.node.node_id} spawned`;
103
+ }
104
+ catch (err) {
105
+ emitEvent({
106
+ level: 'error',
107
+ event: 'cron.escalation.spawn_failed',
108
+ error: err,
109
+ fields: { cron_id: c.cron_id, name: c.name },
110
+ });
111
+ return `failure: cron paused; escalation spawn failed: ${err.message}`;
112
+ }
113
+ }
114
+ /** The delivery-failure line an escalated crashed ONE-SHOT carries: its side
115
+ * effects may already have landed in full, so a human/agent decides whether to
116
+ * redo it rather than the daemon re-firing it. */
117
+ export function crashedOneShotSummary(why) {
118
+ return [
119
+ `${why}.`,
120
+ 'This is a ONE-SHOT, so it has been PAUSED rather than released: its side effects may have completed in full,',
121
+ 'and re-firing would enact them a second time. Determine whether the work actually landed, then either',
122
+ '`crtr cron run` it to redo it deliberately, or `crtr cron cancel` it.',
123
+ ].join(' ');
124
+ }
@@ -0,0 +1,4 @@
1
+ import { type Cron, type HeldSettle } from '../../core/canvas/crons.js';
2
+ export declare const EXIT_HELD = 75;
3
+ export declare const HELD_DELIVERED = "deferred (exit 75) \u2014 held for poke";
4
+ export declare function decideHeldSettle(c: Cron, startedAtIso: string, finishedAtMs: number): HeldSettle | undefined;
@@ -0,0 +1,20 @@
1
+ import { getCron } from '../../core/canvas/crons.js';
2
+ // Exit 75 parks an owed occurrence; the daemon honors the gate but does not implement it.
3
+ export const EXIT_HELD = 75;
4
+ export const HELD_DELIVERED = 'deferred (exit 75) — held for poke';
5
+ // A held one-shot without expiry is poke-only; leaving it past due would re-fire every tick.
6
+ const HELD_ONE_SHOT_FAR_FUTURE = '9999-12-31T23:59:59.999Z';
7
+ // Re-read `last_poke_at`: a poke can land while the gate check runs.
8
+ export function decideHeldSettle(c, startedAtIso, finishedAtMs) {
9
+ const fresh = getCron(c.cron_id);
10
+ if (fresh === null)
11
+ return undefined;
12
+ if (fresh.state === 'active' && fresh.last_poke_at !== null && startedAtIso < fresh.last_poke_at) {
13
+ // A mid-run poke cannot unpark a lease, so re-due an active row; paused rows must remain parked.
14
+ return { kind: 'redue', nowIso: new Date(finishedAtMs).toISOString() };
15
+ }
16
+ if (c.recur != null) {
17
+ return { kind: 'park' };
18
+ }
19
+ return { kind: 'park', fireAt: fresh.expires_at ?? HELD_ONE_SHOT_FAR_FUTURE };
20
+ }
@@ -0,0 +1,3 @@
1
+ import type { RunDueCronOptions } from './passes.js';
2
+ /** Recover leases from prior daemon instances. Verify launch identity, not pid liveness: a reboot can recycle the pid, and killing its new owner would signal a stranger. */
3
+ export declare function recoverStaleCronLeases(now: number, options?: RunDueCronOptions): void;
@@ -0,0 +1,119 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { setTimeout as delay } from 'node:timers/promises';
3
+ import { CRON_DEATH_POLL_MS, CRON_KILL_GRACE_MS, EMPTY_STDOUT_SHA256 } from './capture.js';
4
+ import { cronDaemonInstanceId } from './live-runs.js';
5
+ import { crashedOneShotSummary, escalateFailure } from './escalation.js';
6
+ import { settleRun } from './settle.js';
7
+ import { killLeasedCronRun, runningCrons, setCronState, verifyLeasedCronRun, } from '../../core/canvas/crons.js';
8
+ import { emitEvent } from '../../core/events/emit.js';
9
+ /** Recover leases from prior daemon instances. Verify launch identity, not pid liveness: a reboot can recycle the pid, and killing its new owner would signal a stranger. */
10
+ export function recoverStaleCronLeases(now, options = {}) {
11
+ for (const c of runningCrons()) {
12
+ if (c.run_lease_owner === cronDaemonInstanceId)
13
+ continue;
14
+ if (!verifyLeasedCronRun(c)) {
15
+ settleCrashedRun(c, now, options);
16
+ continue;
17
+ }
18
+ const startedAtMs = c.run_started_at != null ? new Date(c.run_started_at).getTime() : now;
19
+ if (now - startedAtMs < c.run_timeout_s * 1000)
20
+ continue;
21
+ if (!killLeasedCronRun(c)) {
22
+ settleCrashedRun(c, now, options);
23
+ continue;
24
+ }
25
+ const ctx = {
26
+ runId: randomUUID(),
27
+ startedAtIso: c.run_started_at ?? new Date(now).toISOString(),
28
+ exitCode: -1,
29
+ timedOut: true,
30
+ runTimeoutS: c.run_timeout_s,
31
+ processErrorMessage: null,
32
+ stdoutHead: '',
33
+ stdoutSha256: EMPTY_STDOUT_SHA256,
34
+ stderrHead: `run timed out after ${c.run_timeout_s}s (recovered stale lease from a prior daemon instance)`,
35
+ };
36
+ const settlement = (async () => {
37
+ await awaitLeasedRunDeath(c);
38
+ await settleRun(c, {
39
+ outOfBand: false,
40
+ outcome: { kind: 'disposed', ctx },
41
+ record: {
42
+ runId: ctx.runId,
43
+ startedAtIso: ctx.startedAtIso,
44
+ finishedAtMs: now,
45
+ durationMs: now - startedAtMs,
46
+ exitCode: -1,
47
+ stdoutHead: null,
48
+ stderrHead: ctx.stderrHead,
49
+ },
50
+ lease: { kind: 'clear' },
51
+ });
52
+ })();
53
+ options.register?.(settlement);
54
+ void settlement.catch((err) => {
55
+ emitEvent({ level: 'error', event: 'cron.stale_lease.settle_failed', error: err, fields: { cron_id: c.cron_id } });
56
+ });
57
+ }
58
+ }
59
+ // Wait after SIGTERM: a signal-trapping command can still produce side effects.
60
+ async function awaitLeasedRunDeath(c) {
61
+ const deadline = Date.now() + CRON_KILL_GRACE_MS;
62
+ while (Date.now() < deadline) {
63
+ await delay(CRON_DEATH_POLL_MS);
64
+ if (!verifyLeasedCronRun(c))
65
+ return; // gone
66
+ }
67
+ killLeasedCronRun(c, 'SIGKILL');
68
+ }
69
+ /** Record an unobserved crashed run. Pause one-shots rather than re-fire them: their side effects may already have landed. */
70
+ function settleCrashedRun(c, now, options) {
71
+ const oneShot = c.recur == null;
72
+ const startedAtMs = c.run_started_at != null ? new Date(c.run_started_at).getTime() : now;
73
+ const why = 'the daemon life that owned this run ended before it settled — the process is gone and its outcome was never observed';
74
+ const ctx = {
75
+ runId: randomUUID(),
76
+ startedAtIso: c.run_started_at ?? new Date(now).toISOString(),
77
+ exitCode: null,
78
+ timedOut: false,
79
+ runTimeoutS: c.run_timeout_s,
80
+ processErrorMessage: why,
81
+ stdoutHead: '',
82
+ stdoutSha256: EMPTY_STDOUT_SHA256,
83
+ stderrHead: `run outcome unobserved: ${why}`,
84
+ };
85
+ const settlement = (async () => {
86
+ let delivered = `crashed: ${why}${oneShot ? '' : ' — the next occurrence is unaffected'}`;
87
+ if (oneShot) {
88
+ // Pause before escalation so a throw cannot re-fire the past-due one-shot.
89
+ setCronState(c.cron_id, 'paused');
90
+ delivered =
91
+ c.on_output === 'silent'
92
+ ? `crashed: ${why} — one-shot paused, not re-fired (silent: not escalated)`
93
+ : `crashed one-shot: ${await escalateFailure(c, ctx, crashedOneShotSummary(why))}`;
94
+ }
95
+ await settleRun(c, {
96
+ outOfBand: false,
97
+ outcome: { kind: 'stated', delivered, paused: oneShot },
98
+ record: {
99
+ runId: ctx.runId,
100
+ startedAtIso: ctx.startedAtIso,
101
+ finishedAtMs: now,
102
+ durationMs: Math.max(0, now - startedAtMs),
103
+ exitCode: null,
104
+ stdoutHead: null,
105
+ stderrHead: ctx.stderrHead,
106
+ },
107
+ lease: 'release',
108
+ });
109
+ emitEvent({
110
+ level: 'warn',
111
+ event: 'cron.run.unobserved',
112
+ fields: { cron_id: c.cron_id, name: c.name, one_shot: oneShot },
113
+ });
114
+ })();
115
+ options.register?.(settlement);
116
+ void settlement.catch((err) => {
117
+ emitEvent({ level: 'error', event: 'cron.crashed_run.settle_failed', error: err, fields: { cron_id: c.cron_id } });
118
+ });
119
+ }
@@ -0,0 +1,11 @@
1
+ export declare const cronDaemonInstanceId: `${string}-${string}-${string}-${string}-${string}`;
2
+ export interface LiveRun {
3
+ runId: string;
4
+ replaced: boolean;
5
+ selfCanceled: boolean;
6
+ kill(): void;
7
+ }
8
+ export declare function liveRunFor(cronId: string): LiveRun | undefined;
9
+ export declare function registerLiveRun(cronId: string, run: LiveRun): void;
10
+ export declare function clearLiveRun(cronId: string, runId: string): void;
11
+ export declare function markSelfCanceledCronRun(cronId: string, runId: string): boolean;
@@ -0,0 +1,23 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ export const cronDaemonInstanceId = randomUUID();
3
+ const liveRuns = new Map();
4
+ export function liveRunFor(cronId) {
5
+ return liveRuns.get(cronId);
6
+ }
7
+ export function registerLiveRun(cronId, run) {
8
+ liveRuns.set(cronId, run);
9
+ }
10
+ // An earlier close handler must not evict a later run.
11
+ export function clearLiveRun(cronId, runId) {
12
+ const live = liveRuns.get(cronId);
13
+ if (live !== undefined && live.runId === runId)
14
+ liveRuns.delete(cronId);
15
+ }
16
+ // Only that exact process may finish after its row is removed.
17
+ export function markSelfCanceledCronRun(cronId, runId) {
18
+ const live = liveRuns.get(cronId);
19
+ if (live === undefined || live.runId !== runId)
20
+ return false;
21
+ live.selfCanceled = true;
22
+ return true;
23
+ }
@@ -0,0 +1,11 @@
1
+ import { type Cron } from '../../core/canvas/crons.js';
2
+ export interface RunDueCronOptions {
3
+ /** Registers settlement work for graceful-teardown draining. */
4
+ register?: (work: Promise<void>) => void;
5
+ /** Node-launching runs need capacity; absent means all runs are admitted. */
6
+ hasBrokerCapacity?: () => boolean;
7
+ }
8
+ export declare function cronLaunchesNode(c: Cron): boolean;
9
+ export declare function expireDueCrons(nowIso: string): void;
10
+ export declare function applyOverlapPolicies(now: number, nowIso: string): void;
11
+ export declare function emitCapacityDeferral(now: number, deferred: number): void;
@@ -0,0 +1,78 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { cronDaemonInstanceId, liveRunFor } from './live-runs.js';
3
+ import { advanceCron, cancelCron, dueOverlapCrons, expiredIdleCrons, listCronRuns, recordCronRun, } from '../../core/canvas/crons.js';
4
+ import { emitEvent } from '../../core/events/emit.js';
5
+ import { nextSlotAfter } from '../../core/wake.js';
6
+ import { parseSink } from '../cron-sink.js';
7
+ /** Lead of a `cron_runs.delivered` string written by the overlap=skip pass.
8
+ * Matched (not just written) — see `latestRunIsSkip`. */
9
+ const SKIP_MARKER = 'skipped (overlap=skip)';
10
+ // A false negative is an ungated run; a false positive delays one poll at capacity.
11
+ const NODE_LAUNCHING_COMMAND = /\bcrtr\b[^\n]*?\bnode\s+lifecycle\s+revive\b|\bcrtr\b[^\n]*?\bnode\s+new\b|\bcrtr\b[^\n]*?\bcanvas\s+revive\b|\bcrtr\b[^\n]*?\bnode\s+message\s+send\b[^\n]*?\s--fresh\b/;
12
+ export function cronLaunchesNode(c) {
13
+ if (parseSink(c.sink)?.kind === 'spawn')
14
+ return true;
15
+ return NODE_LAUNCHING_COMMAND.test(c.command);
16
+ }
17
+ export function expireDueCrons(nowIso) {
18
+ for (const c of expiredIdleCrons(nowIso)) {
19
+ cancelCron(c.cron_id);
20
+ emitEvent({
21
+ level: 'info',
22
+ event: 'cron.expired',
23
+ fields: { cron_id: c.cron_id, name: c.name, expires_at: c.expires_at },
24
+ });
25
+ }
26
+ }
27
+ function latestRunIsSkip(cronId) {
28
+ return listCronRuns(cronId)[0]?.delivered?.startsWith(SKIP_MARKER) === true;
29
+ }
30
+ export function applyOverlapPolicies(now, nowIso) {
31
+ for (const c of dueOverlapCrons(nowIso)) {
32
+ if (c.overlap === 'queue')
33
+ continue;
34
+ if (c.overlap === 'skip') {
35
+ let nextFire;
36
+ try {
37
+ nextFire = nextSlotAfter(c.recur, new Date(now));
38
+ }
39
+ catch {
40
+ continue; // paused + escalated by the run path when it next fires
41
+ }
42
+ advanceCron(c.cron_id, nextFire);
43
+ // One marker per streak preserves real runs in the 20-entry ring.
44
+ if (!latestRunIsSkip(c.cron_id)) {
45
+ recordCronRun({
46
+ run_id: randomUUID(),
47
+ cron_id: c.cron_id,
48
+ started: nowIso,
49
+ finished: nowIso,
50
+ duration_ms: 0,
51
+ exit_code: null,
52
+ stdout_head: null,
53
+ stderr_head: null,
54
+ delivered: `${SKIP_MARKER}: fire landed while the previous run was in flight (consecutive skips are not re-recorded until a run settles)`,
55
+ });
56
+ }
57
+ continue;
58
+ }
59
+ // replace
60
+ const live = liveRunFor(c.cron_id);
61
+ if (c.run_lease_owner === cronDaemonInstanceId && live !== undefined && !live.replaced) {
62
+ live.replaced = true;
63
+ live.kill();
64
+ }
65
+ }
66
+ }
67
+ const CAPACITY_DEFER_LOG_THROTTLE_MS = 30_000;
68
+ let lastCapacityDeferLogAt = Number.NEGATIVE_INFINITY;
69
+ export function emitCapacityDeferral(now, deferred) {
70
+ if (now - lastCapacityDeferLogAt < CAPACITY_DEFER_LOG_THROTTLE_MS)
71
+ return;
72
+ lastCapacityDeferLogAt = now;
73
+ emitEvent({
74
+ level: 'warn',
75
+ event: 'cron.capacity.run_deferred',
76
+ fields: { deferred_runs: deferred },
77
+ });
78
+ }
@@ -0,0 +1,28 @@
1
+ import { type Cron, type CronRunRecord, type HeldSettle } from '../../core/canvas/crons.js';
2
+ import { type RunSettleContext } from './disposition.js';
3
+ export type LeaseSettlement = 'none' | 'release' | HeldSettle;
4
+ export type SettleOutcome = {
5
+ kind: 'disposed';
6
+ ctx: RunSettleContext;
7
+ selfCanceled?: boolean;
8
+ } | {
9
+ kind: 'stated';
10
+ delivered: string;
11
+ paused?: boolean;
12
+ };
13
+ export interface SettleRecord {
14
+ runId: string;
15
+ startedAtIso: string;
16
+ finishedAtMs: number;
17
+ durationMs: number;
18
+ exitCode: number | null;
19
+ stdoutHead: string | null;
20
+ stderrHead: string | null;
21
+ }
22
+ export interface SettleRunInput {
23
+ outOfBand: boolean;
24
+ outcome: SettleOutcome;
25
+ record: SettleRecord;
26
+ lease: LeaseSettlement;
27
+ }
28
+ export declare function settleRun(c: Cron, input: SettleRunInput): Promise<CronRunRecord>;
@@ -0,0 +1,37 @@
1
+ import { consumeCron, recordCronRun, releaseCronRunLease, } from '../../core/canvas/crons.js';
2
+ import { disposeSettledRun } from './disposition.js';
3
+ function occurrenceStaysOwed(lease) {
4
+ return typeof lease === 'object' && (lease.kind === 'park' || lease.kind === 'redue');
5
+ }
6
+ export async function settleRun(c, input) {
7
+ const { outOfBand, outcome, record, lease } = input;
8
+ let delivered;
9
+ let paused;
10
+ if (outcome.kind === 'disposed') {
11
+ const disposition = await disposeSettledRun(c, outcome.ctx, outOfBand, outcome.selfCanceled ?? false);
12
+ delivered = disposition.delivered;
13
+ paused = disposition.paused;
14
+ }
15
+ else {
16
+ delivered = outcome.delivered;
17
+ paused = outcome.paused ?? false;
18
+ }
19
+ const written = {
20
+ run_id: record.runId,
21
+ cron_id: c.cron_id,
22
+ started: record.startedAtIso,
23
+ finished: new Date(record.finishedAtMs).toISOString(),
24
+ duration_ms: record.durationMs,
25
+ exit_code: record.exitCode,
26
+ stdout_head: record.stdoutHead,
27
+ stderr_head: record.stderrHead,
28
+ delivered,
29
+ };
30
+ recordCronRun(written);
31
+ if (lease !== 'none')
32
+ releaseCronRunLease(c.cron_id, lease === 'release' ? undefined : lease);
33
+ // Retain out-of-band, paused, and held rows; their occurrence is not spent.
34
+ if (!outOfBand && c.recur == null && !paused && !occurrenceStaysOwed(lease))
35
+ consumeCron(c.cron_id);
36
+ return written;
37
+ }
@@ -0,0 +1,2 @@
1
+ import type { Cron } from '../../core/canvas/crons.js';
2
+ export declare function deliverToSink(c: Cron, stdout: string): Promise<string>;
@@ -0,0 +1,100 @@
1
+ import { randomBytes } from 'node:crypto';
2
+ import { rmSync } from 'node:fs';
3
+ import { synthesizedPageDocument, writeSynthesizedPage } from '../../core/human/page-synth.js';
4
+ import { ticketDir } from '../../core/human/root.js';
5
+ import { preparePage, publishPage } from '../../core/human/tickets.js';
6
+ import { resolvePageComponents } from '../../core/config.js';
7
+ import { getNode } from '../../core/canvas/canvas.js';
8
+ import { appendInbox } from '../../core/feed/inbox.js';
9
+ import { hasNoNaturalCycle } from '../../core/runtime/revive-all.js';
10
+ import { isParked } from '../../core/runtime/lifecycle.js';
11
+ import { reviveNode } from '../../core/runtime/revive.js';
12
+ import { isBrokerLive } from '../../core/runtime/model-swap.js';
13
+ import { spawnChild } from '../../core/runtime/spawn.js';
14
+ import { cronWakeOrigin } from '../../core/runtime/bearings.js';
15
+ import { parseSink } from '../cron-sink.js';
16
+ import { resolveLiveProfile, resolveLiveProfileName } from './armed-context.js';
17
+ function deliverNodeSink(c, target, stdout) {
18
+ const meta = getNode(target);
19
+ if (meta === null)
20
+ throw new Error(`node sink ${target} is gone`);
21
+ // A cron is an explicit next cycle for a clock-parked node, not other done nodes.
22
+ if (hasNoNaturalCycle(meta) && !isParked(meta)) {
23
+ throw new Error(`node sink ${target} has no natural cycle ahead (done or finalized)`);
24
+ }
25
+ const from = c.created_by != null && getNode(c.created_by) !== null ? c.created_by : null;
26
+ appendInbox(target, {
27
+ from,
28
+ tier: c.tier,
29
+ kind: 'message',
30
+ label: `⏰ cron ${c.name}`,
31
+ data: { body: stdout },
32
+ });
33
+ // The durable append succeeds even if its best-effort wake fails.
34
+ if (c.tier !== 'deferred' && !isBrokerLive(meta)) {
35
+ try {
36
+ reviveNode(target, { resume: true });
37
+ }
38
+ catch {
39
+ /* best-effort wake — the entry is durably appended regardless */
40
+ }
41
+ }
42
+ }
43
+ async function deliverSpawnSink(c, kind, stdout) {
44
+ const creatorAlive = c.created_by != null && getNode(c.created_by) !== null;
45
+ const prompt = [
46
+ `Scheduled cron "${c.name}" (${c.cron_id}) fired and delivered its output to you as your kickoff.`,
47
+ '',
48
+ '## Command output',
49
+ '',
50
+ stdout,
51
+ ].join('\n');
52
+ const res = await spawnChild({
53
+ kind,
54
+ cwd: c.cwd,
55
+ prompt,
56
+ // Creatorless sinks are terminal roots so each run reaps without stealing focus.
57
+ ...(creatorAlive ? { parent: c.created_by } : { root: true, rootLifecycle: 'terminal' }),
58
+ readiness: 'launch',
59
+ profile: resolveLiveProfile(c),
60
+ wakeOrigin: cronWakeOrigin(c, 'node_birth'),
61
+ });
62
+ return res.node.node_id;
63
+ }
64
+ function deliverHumanSink(c, stdout) {
65
+ const synthesized = writeSynthesizedPage(synthesizedPageDocument(`⏰ cron ${c.name}`, `Scheduled cron "${c.name}" delivered output for your attention.`, stdout, false));
66
+ // Validate verbatim stdout before minting a ticket to avoid a half-built page.
67
+ let prepared;
68
+ try {
69
+ const profileName = resolveLiveProfileName(c);
70
+ prepared = preparePage({
71
+ sourceFile: synthesized.path,
72
+ source: {
73
+ sessionName: `cron ${c.name} (${c.cron_id})`,
74
+ ...(profileName === undefined ? {} : { profileName }),
75
+ },
76
+ productKinds: resolvePageComponents(),
77
+ delivery: { placement: 'inline', inbox: true, reply: false },
78
+ });
79
+ }
80
+ finally {
81
+ rmSync(synthesized.dir, { recursive: true, force: true });
82
+ }
83
+ const id = `page-${randomBytes(4).toString('hex')}`;
84
+ publishPage(ticketDir(id), prepared);
85
+ }
86
+ export async function deliverToSink(c, stdout) {
87
+ const sink = parseSink(c.sink);
88
+ if (sink === null)
89
+ return 'no sink armed — nothing delivered';
90
+ if (sink.kind === 'node') {
91
+ deliverNodeSink(c, sink.node, stdout);
92
+ return `delivered: node ${sink.node}`;
93
+ }
94
+ if (sink.kind === 'spawn') {
95
+ const nodeId = await deliverSpawnSink(c, sink.spawn, stdout);
96
+ return `delivered: spawned ${nodeId}`;
97
+ }
98
+ deliverHumanSink(c, stdout);
99
+ return 'delivered: human send --inbox';
100
+ }
@@ -1,45 +1,9 @@
1
+ import { type RunDueCronOptions } from './cron/passes.js';
1
2
  import { type Cron, type CronRunRecord } from '../core/canvas/crons.js';
2
- /** Output head caps for the run log (spec: "capped, e.g. 4KB"). */
3
- export declare const CRON_OUTPUT_HEAD_BYTES: number;
4
- /** One id per daemon process instance — distinguishes THIS process's own
5
- * in-flight run leases from a stale 'running' row a prior (crashed/restarted)
6
- * daemon left behind. */
7
- export declare const cronDaemonInstanceId: `${string}-${string}-${string}-${string}-${string}`;
8
- export interface RunDueCronOptions {
9
- /** Invoked synchronously the instant each settlement promise is created, so
10
- * the caller can register it for a graceful-teardown drain. */
11
- register?: (work: Promise<void>) => void;
12
- /** Broker-capacity admission for a run that will LAUNCH OR REVIVE a node
13
- * (`cronLaunchesNode`). Returns false when the live fleet is at the
14
- * automatic-revive cap; the daemon wires in its own `hasBrokerCapacity`
15
- * (crtrd.ts), which owns the fleet count and the throttled capacity event.
16
- * Absent → every run is admitted (out-of-band/test callers own no fleet). */
17
- hasBrokerCapacity?: () => boolean;
18
- }
19
- /** Will this cron's run consume broker capacity? True when its armed sink
20
- * births a node, or its command invokes a node-launching crtr verb. A cron
21
- * that merely runs a shell command or sends a message reads false and is
22
- * never gated. */
23
- export declare function cronLaunchesNode(c: Cron): boolean;
24
- /** Mark a cancellation as originating from the live cron run it names. Only
25
- * that exact process may finish after its row is removed, so an external cancel
26
- * retains its normal process-group kill behavior. */
27
- export declare function markSelfCanceledCronRun(cronId: string, runId: string): boolean;
28
- /** Recover stale 'running' rows left by a PRIOR daemon instance: a pid that is
29
- * not VERIFIABLY still the process this lease launched resets straight to
30
- * idle; a confirmed-live process group past its own run_timeout_s is killed
31
- * and settled as a timeout run through the SAME failure path a live timeout
32
- * takes (pause + escalation unless silent). Rows this SAME instance owns are
33
- * genuinely in flight and left alone.
34
- *
35
- * The verification is `verifyLeasedCronRun`, not `isPidAlive`, and it is
36
- * FAIL-CLOSED: this row was written by a daemon life that has since ended, so
37
- * after a host reboot its `run_pid` names a slot in a dead kernel's pid space
38
- * that the OS has probably recycled, and the timeout comparison below is
39
- * trivially satisfied by any downtime. Liveness alone would therefore SIGTERM
40
- * a stranger's process group. Only a matching launch-time identity authorizes
41
- * the signal; everything else releases the lease. */
42
- export declare function recoverStaleCronLeases(now: number, options?: RunDueCronOptions): void;
3
+ export { CRON_OUTPUT_HEAD_BYTES } from './cron/capture.js';
4
+ export { cronDaemonInstanceId, markSelfCanceledCronRun } from './cron/live-runs.js';
5
+ export { cronLaunchesNode, type RunDueCronOptions } from './cron/passes.js';
6
+ export { recoverStaleCronLeases } from './cron/lease-recovery.js';
43
7
  /** Kick off ONE cron run and return its settlement promise, resolving with
44
8
  * the settled run record (null = the idle→running lease was lost, or the row
45
9
  * was quarantined — nothing was recorded by THIS call). The subprocess