@north-light/crouter 0.3.233 → 0.3.235

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 (411) hide show
  1. package/dist/api/client.js +16 -15
  2. package/dist/api/dto/nodes.d.ts +11 -0
  3. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/__tests__/integration/provider-rotation.test.ts +1 -1
  4. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/claude-plugin-commands.ts +14 -17
  5. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/crtr-commands/index.ts +7 -11
  6. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/frontmatter-rules/index.ts +8 -8
  7. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/memory-slash-commands.ts +10 -11
  8. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/pi-shell-runner.ts +2 -2
  9. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/provider-rotation.d.ts +3 -3
  10. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/provider-rotation.js +7 -7
  11. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/provider-rotation.ts +8 -8
  12. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/statusline.ts +6 -6
  13. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/sysprompt-window.ts +3 -3
  14. package/dist/builtin-pi-packages/pi-crtr-extensions/lib/subscription-state.ts +1 -1
  15. package/dist/clients/attach/chrome/ticket-panel.js +15 -2
  16. package/dist/clients/attach/overlays/graph.js +0 -3
  17. package/dist/clients/attach/overlays/mcp.js +1 -2
  18. package/dist/clients/attach/render/chat-view.js +0 -12
  19. package/dist/clients/attach/render/crtr-output.js +0 -2
  20. package/dist/clients/attach/render/tool-calls.js +0 -6
  21. package/dist/clients/attach/slash/dispatch.js +8 -11
  22. package/dist/clients/attach/viewer.js +535 -537
  23. package/dist/clients/conversation/projection.js +0 -2
  24. package/dist/clients/inbox/controller.js +22 -3
  25. package/dist/clients/inbox/review/companion-pane.js +0 -4
  26. package/dist/clients/inbox/review-adapter.js +0 -7
  27. package/dist/clients/inbox/tui/slots.d.ts +3 -3
  28. package/dist/clients/inbox/tui/types.d.ts +2 -2
  29. package/dist/commands/api-client.js +0 -8
  30. package/dist/commands/attention.js +1 -9
  31. package/dist/commands/canvas-browse.js +2 -1
  32. package/dist/commands/canvas-config.js +0 -10
  33. package/dist/commands/canvas-history.js +2 -1
  34. package/dist/commands/cron.js +10 -23
  35. package/dist/commands/dashboard.js +0 -2
  36. package/dist/commands/human/feedback.js +2 -1
  37. package/dist/commands/human/prompts.js +2 -5
  38. package/dist/commands/human/queue.js +2 -7
  39. package/dist/commands/human/request.js +0 -14
  40. package/dist/commands/human/review.js +2 -1
  41. package/dist/commands/human.js +1 -1
  42. package/dist/commands/memory/__tests__/command-selector-and-mutation-guards.test.js +4 -4
  43. package/dist/commands/memory/__tests__/memory-list-and-lint-ignore.test.d.ts +1 -0
  44. package/dist/commands/memory/__tests__/memory-list-and-lint-ignore.test.js +96 -0
  45. package/dist/commands/memory/delete.js +1 -1
  46. package/dist/commands/memory/edit.js +6 -3
  47. package/dist/commands/memory/find.js +2 -2
  48. package/dist/commands/memory/lint.js +2 -1
  49. package/dist/commands/memory/list.d.ts +4 -0
  50. package/dist/commands/memory/list.js +20 -14
  51. package/dist/commands/memory/move.js +1 -1
  52. package/dist/commands/memory/read.d.ts +2 -7
  53. package/dist/commands/memory/read.js +5 -16
  54. package/dist/commands/memory/shared.d.ts +1 -0
  55. package/dist/commands/memory/shared.js +17 -8
  56. package/dist/commands/memory/write.js +8 -4
  57. package/dist/commands/node/bash.js +2 -1
  58. package/dist/commands/node/create.js +2 -1
  59. package/dist/commands/node/inspect.js +11 -10
  60. package/dist/commands/node/lifecycle.js +5 -13
  61. package/dist/commands/node/message.js +11 -6
  62. package/dist/commands/node/subscription.js +7 -2
  63. package/dist/commands/node/wait.js +9 -4
  64. package/dist/commands/node-context.js +2 -1
  65. package/dist/commands/node-lifecycle-revive.js +0 -2
  66. package/dist/commands/node-worktree.js +2 -1
  67. package/dist/commands/pkg/browse/actions.d.ts +1 -1
  68. package/dist/commands/pkg/browse/actions.js +4 -4
  69. package/dist/commands/pkg/browse/catalog.d.ts +1 -1
  70. package/dist/commands/pkg/browse/catalog.js +15 -15
  71. package/dist/commands/pkg/browse/command-view.d.ts +1 -1
  72. package/dist/commands/pkg/browse/command-view.js +3 -3
  73. package/dist/commands/pkg/browse/detail-panel.d.ts +1 -1
  74. package/dist/commands/pkg/browse/detail-panel.js +8 -8
  75. package/dist/commands/pkg/browse/doc-view.d.ts +1 -1
  76. package/dist/commands/pkg/browse/doc-view.js +3 -3
  77. package/dist/commands/pkg/browse/list-panel.d.ts +1 -1
  78. package/dist/commands/pkg/browse/list-panel.js +3 -3
  79. package/dist/commands/pkg/browse/model.d.ts +3 -3
  80. package/dist/commands/pkg/browse/shell.d.ts +2 -2
  81. package/dist/commands/pkg/browse/shell.js +13 -13
  82. package/dist/commands/pkg/plugin-manage.js +3 -2
  83. package/dist/commands/pkg/shared.js +0 -2
  84. package/dist/commands/pkg.js +1 -0
  85. package/dist/commands/profile/delete.js +2 -1
  86. package/dist/commands/profile/rename.js +1 -1
  87. package/dist/commands/push.js +2 -3
  88. package/dist/commands/revive.js +0 -2
  89. package/dist/commands/surface/node/placement.js +2 -1
  90. package/dist/commands/surface-edit.js +2 -1
  91. package/dist/commands/sys/config.js +1 -4
  92. package/dist/commands/sys/daemon.js +0 -10
  93. package/dist/commands/sys/doctor.js +3 -2
  94. package/dist/commands/sys/panels/broker-limits-panel.d.ts +2 -2
  95. package/dist/commands/sys/panels/broker-limits-panel.js +4 -4
  96. package/dist/commands/sys/panels/dependencies-panel.d.ts +2 -2
  97. package/dist/commands/sys/panels/dependencies-panel.js +2 -2
  98. package/dist/commands/sys/panels/keybindings-panel.d.ts +1 -1
  99. package/dist/commands/sys/panels/keybindings-panel.js +5 -5
  100. package/dist/commands/sys/panels/models-panel.d.ts +4 -4
  101. package/dist/commands/sys/panels/models-panel.js +11 -10
  102. package/dist/commands/sys/panels/plugins-panel.d.ts +2 -2
  103. package/dist/commands/sys/panels/plugins-panel.js +2 -2
  104. package/dist/commands/sys/panels/prefs-panel.d.ts +2 -2
  105. package/dist/commands/sys/panels/prefs-panel.js +4 -4
  106. package/dist/commands/sys/panels/profiles-panel.d.ts +1 -1
  107. package/dist/commands/sys/panels/profiles-panel.js +14 -13
  108. package/dist/commands/sys/panels/provider-panel.d.ts +3 -3
  109. package/dist/commands/sys/panels/provider-panel.js +8 -8
  110. package/dist/commands/sys/settings-shell.d.ts +3 -3
  111. package/dist/commands/sys/settings-shell.js +15 -15
  112. package/dist/commands/sys/setup-settings-runner.d.ts +3 -3
  113. package/dist/commands/sys/setup-settings-runner.js +5 -5
  114. package/dist/commands/sys/setup-wizard.d.ts +4 -4
  115. package/dist/commands/sys/setup-wizard.js +8 -8
  116. package/dist/commands/sys/support/prepare.js +4 -4
  117. package/dist/commands/sys/support/submit.js +2 -2
  118. package/dist/commands/sys/sync-deps.js +0 -2
  119. package/dist/commands/sys/sync-project-guidance.js +2 -1
  120. package/dist/commands/sys/sysprompt.js +2 -1
  121. package/dist/commands/sys.js +1 -0
  122. package/dist/core/__tests__/boot.test.js +0 -8
  123. package/dist/core/__tests__/broker-extension-canvas-db-boundary.test.js +23 -1
  124. package/dist/core/__tests__/canvas-db-boundary.test.d.ts +1 -0
  125. package/dist/core/__tests__/canvas-db-boundary.test.js +46 -0
  126. package/dist/core/__tests__/canvas.test.js +1 -1
  127. package/dist/core/__tests__/child-death-wake.test.js +0 -2
  128. package/dist/core/__tests__/cli-help-contract.test.d.ts +1 -0
  129. package/dist/core/__tests__/cli-help-contract.test.js +150 -0
  130. package/dist/core/__tests__/cron-armed-context-failure.test.d.ts +1 -0
  131. package/dist/core/__tests__/cron-armed-context-failure.test.js +145 -0
  132. package/dist/core/__tests__/daemon-wedge.test.js +0 -4
  133. package/dist/core/__tests__/dead-node-policy-table.test.js +0 -4
  134. package/dist/core/__tests__/fixtures/fake-engine.js +4 -14
  135. package/dist/core/__tests__/fixtures/fake-pi-host.js +5 -5
  136. package/dist/core/__tests__/focuses.test.js +0 -6
  137. package/dist/core/__tests__/fs-utils.test.d.ts +1 -0
  138. package/dist/core/__tests__/fs-utils.test.js +50 -0
  139. package/dist/core/__tests__/helpers/harness.js +1 -1
  140. package/dist/core/__tests__/inline-memory-refs.test.js +0 -2
  141. package/dist/core/__tests__/integration/broker-fork-seam.test.js +0 -2
  142. package/dist/core/__tests__/integration/broker-sdk-wiring.test.js +3 -16
  143. package/dist/core/__tests__/integration/command-plugins.test.js +21 -24
  144. package/dist/core/__tests__/integration/deferred-no-wake.test.js +0 -6
  145. package/dist/core/__tests__/integration/flagship-lifecycle.test.js +0 -26
  146. package/dist/core/__tests__/integration/live-mutation-verbs.test.js +0 -4
  147. package/dist/core/__tests__/integration/live-mutation.test.js +1 -5
  148. package/dist/core/__tests__/integration/refresh-stall-recycle.test.js +1 -5
  149. package/dist/core/__tests__/integration/revive.test.js +0 -12
  150. package/dist/core/__tests__/integration/subscription-delivery.test.js +0 -6
  151. package/dist/core/__tests__/integration/tmux-surface.test.js +24 -8
  152. package/dist/core/__tests__/integration/worktree-reap.test.js +0 -6
  153. package/dist/core/__tests__/lifecycle.test.js +0 -18
  154. package/dist/core/__tests__/memory-resolver-precedence.test.js +1 -1
  155. package/dist/core/__tests__/migration.test.js +2 -2
  156. package/dist/core/__tests__/on-read-dedup-resume.test.js +3 -3
  157. package/dist/core/__tests__/pid-identity-match.test.js +1 -1
  158. package/dist/core/__tests__/plugin-kinds.test.js +1 -1
  159. package/dist/core/__tests__/plugin-page-components.test.js +1 -1
  160. package/dist/core/__tests__/preview-registry-sync.test.js +42 -1
  161. package/dist/core/__tests__/respawn-throttle.test.js +0 -4
  162. package/dist/core/__tests__/revive-all.test.js +5 -5
  163. package/dist/core/__tests__/revive-parked-fresh.test.js +62 -11
  164. package/dist/core/__tests__/seam/broker-attach-multiclient.test.js +0 -8
  165. package/dist/core/__tests__/seam/broker-attach-stream.test.js +0 -8
  166. package/dist/core/__tests__/seam/broker-crash-teardown.test.js +0 -2
  167. package/dist/core/__tests__/tmux-driver-boundary.test.d.ts +1 -0
  168. package/dist/core/__tests__/tmux-driver-boundary.test.js +50 -0
  169. package/dist/core/__tests__/watchdog-abort-arms-retry.test.js +11 -6
  170. package/dist/core/activity-signal.d.ts +7 -0
  171. package/dist/core/activity-signal.js +38 -0
  172. package/dist/core/auto-update.js +2 -1
  173. package/dist/core/bootstrap.js +2 -1
  174. package/dist/core/canvas/__tests__/remote-canvas-source.test.js +1 -1
  175. package/dist/core/canvas/__tests__/remote-transport.test.js +1 -1
  176. package/dist/core/canvas/__tests__/render-remote.test.js +1 -1
  177. package/dist/core/canvas/browse/app.js +3 -2
  178. package/dist/core/canvas/browse/model.d.ts +1 -1
  179. package/dist/core/canvas/browse/model.js +0 -4
  180. package/dist/core/canvas/canvas.d.ts +22 -0
  181. package/dist/core/canvas/canvas.js +61 -19
  182. package/dist/core/canvas/crons.js +0 -6
  183. package/dist/core/canvas/db.d.ts +14 -20
  184. package/dist/core/canvas/db.js +30 -1332
  185. package/dist/core/canvas/focuses.js +0 -4
  186. package/dist/core/canvas/history.js +2 -1
  187. package/dist/core/canvas/meta-file.js +18 -27
  188. package/dist/core/canvas/migrations.d.ts +20 -0
  189. package/dist/core/canvas/migrations.js +1339 -0
  190. package/dist/core/canvas/nav-model.js +1 -11
  191. package/dist/core/canvas/nav-render.js +0 -8
  192. package/dist/core/canvas/remote-canvas-source.d.ts +8 -0
  193. package/dist/core/canvas/remote-canvas-source.js +9 -3
  194. package/dist/core/canvas/render-source.js +31 -13
  195. package/dist/core/command-manifests/manifest.js +0 -10
  196. package/dist/core/command-manifests/registry.d.ts +2 -0
  197. package/dist/core/command-manifests/schema.js +0 -4
  198. package/dist/core/command-plugins/compose.js +4 -2
  199. package/dist/core/command-plugins/discovery.d.ts +1 -0
  200. package/dist/core/command-plugins/discovery.js +1 -1
  201. package/dist/core/command-plugins/transport/http-invoke.js +0 -14
  202. package/dist/core/command.js +2 -6
  203. package/dist/core/config.js +2 -1
  204. package/dist/core/fault-classifier.js +8 -0
  205. package/dist/core/feed/inbox.d.ts +10 -0
  206. package/dist/core/feed/inbox.js +18 -12
  207. package/dist/core/fs-utils.d.ts +9 -2
  208. package/dist/core/fs-utils.js +7 -15
  209. package/dist/core/help.js +0 -8
  210. package/dist/core/host-exports/export.d.ts +2 -2
  211. package/dist/core/host-exports/export.js +9 -12
  212. package/dist/core/human/answer.d.ts +2 -2
  213. package/dist/core/human/convention.d.ts +2 -5
  214. package/dist/core/human/convention.js +3 -13
  215. package/dist/core/human/page-schema.d.ts +2 -2
  216. package/dist/core/human/page-schema.js +2 -2
  217. package/dist/core/human/root.js +6 -23
  218. package/dist/core/human/scan.js +24 -7
  219. package/dist/core/human/tickets.d.ts +4 -1
  220. package/dist/core/human/tickets.js +20 -6
  221. package/dist/core/io.js +0 -12
  222. package/dist/core/memory/identity.js +0 -4
  223. package/dist/core/memory/inline-ref-inventory.js +3 -2
  224. package/dist/core/memory/lint.js +3 -6
  225. package/dist/core/memory-resolver.d.ts +5 -0
  226. package/dist/core/memory-resolver.js +14 -12
  227. package/dist/core/pagination.d.ts +2 -0
  228. package/dist/core/pagination.js +2 -3
  229. package/dist/core/preview-registry.d.ts +14 -0
  230. package/dist/core/preview-registry.js +2 -4
  231. package/dist/core/preview-result-path.js +2 -1
  232. package/dist/core/profiles/manifest.d.ts +37 -5
  233. package/dist/core/profiles/manifest.js +44 -24
  234. package/dist/core/profiles/select.js +0 -4
  235. package/dist/core/review/signal.js +4 -23
  236. package/dist/core/review/ticket-filter.d.ts +3 -3
  237. package/dist/core/review/ticket-filter.js +4 -9
  238. package/dist/core/runtime/__tests__/engine-route.test.js +1 -1
  239. package/dist/core/runtime/__tests__/spawn-env.test.js +0 -8
  240. package/dist/core/runtime/bearings-render.d.ts +56 -0
  241. package/dist/core/runtime/bearings-render.js +241 -0
  242. package/dist/core/runtime/bearings.d.ts +2 -17
  243. package/dist/core/runtime/bearings.js +12 -198
  244. package/dist/core/runtime/broker/auth-reload.d.ts +39 -0
  245. package/dist/core/runtime/broker/auth-reload.js +149 -0
  246. package/dist/core/runtime/broker/engine-drive.d.ts +26 -0
  247. package/dist/core/runtime/broker/engine-drive.js +257 -0
  248. package/dist/core/runtime/broker/engine-routing.d.ts +143 -0
  249. package/dist/core/runtime/broker/engine-routing.js +211 -0
  250. package/dist/core/runtime/broker/event-projection.js +3 -2
  251. package/dist/core/runtime/broker/fault-retry.js +11 -4
  252. package/dist/core/runtime/broker/frame-client.d.ts +38 -0
  253. package/dist/core/runtime/broker/frame-client.js +129 -0
  254. package/dist/core/runtime/broker/frame-dispatch.d.ts +12 -189
  255. package/dist/core/runtime/broker/frame-dispatch.js +89 -1476
  256. package/dist/core/runtime/broker/frame-engine-control.d.ts +20 -0
  257. package/dist/core/runtime/broker/frame-engine-control.js +231 -0
  258. package/dist/core/runtime/broker/frame-memory-refs.d.ts +19 -0
  259. package/dist/core/runtime/broker/frame-memory-refs.js +41 -0
  260. package/dist/core/runtime/broker/frame-model-query.d.ts +13 -0
  261. package/dist/core/runtime/broker/frame-model-query.js +69 -0
  262. package/dist/core/runtime/broker/frame-read.d.ts +19 -0
  263. package/dist/core/runtime/broker/frame-read.js +116 -0
  264. package/dist/core/runtime/broker/frame-replies.d.ts +24 -0
  265. package/dist/core/runtime/broker/frame-replies.js +64 -0
  266. package/dist/core/runtime/broker/frame-session.d.ts +34 -0
  267. package/dist/core/runtime/broker/frame-session.js +203 -0
  268. package/dist/core/runtime/broker/rebind.js +50 -0
  269. package/dist/core/runtime/broker/turn-ignition.d.ts +42 -0
  270. package/dist/core/runtime/broker/turn-ignition.js +74 -0
  271. package/dist/core/runtime/broker-extension-render.js +9 -85
  272. package/dist/core/runtime/broker-protocol.js +0 -4
  273. package/dist/core/runtime/broker-sdk.js +2 -1
  274. package/dist/core/runtime/broker.d.ts +1 -1
  275. package/dist/core/runtime/broker.js +5 -23
  276. package/dist/core/runtime/fault.js +3 -2
  277. package/dist/core/runtime/headless-pi.js +3 -2
  278. package/dist/core/runtime/host.js +2 -3
  279. package/dist/core/runtime/kickoff.js +0 -4
  280. package/dist/core/runtime/launch-target.js +2 -1
  281. package/dist/core/runtime/launch.d.ts +2 -2
  282. package/dist/core/runtime/launch.js +20 -10
  283. package/dist/core/runtime/lifecycle.js +17 -26
  284. package/dist/core/runtime/managed-provider-cooling.d.ts +1 -1
  285. package/dist/core/runtime/naming.js +0 -2
  286. package/dist/core/runtime/nodes.js +2 -1
  287. package/dist/core/runtime/persona.js +0 -6
  288. package/dist/core/runtime/placement-tmux.js +2 -5
  289. package/dist/core/runtime/placement.js +4 -11
  290. package/dist/core/runtime/recap.js +4 -3
  291. package/dist/core/runtime/reopen.js +4 -7
  292. package/dist/core/runtime/reset.js +0 -4
  293. package/dist/core/runtime/resume-root.js +0 -2
  294. package/dist/core/runtime/revive.js +17 -13
  295. package/dist/core/runtime/session-list-cache.js +2 -2
  296. package/dist/core/runtime/shell-expansion.js +0 -2
  297. package/dist/core/runtime/spawn.js +0 -2
  298. package/dist/core/runtime/stop-guard.js +28 -16
  299. package/dist/core/runtime/stream-watchdog.d.ts +0 -1
  300. package/dist/core/runtime/stream-watchdog.js +13 -18
  301. package/dist/core/runtime/tmux-bindings.js +0 -2
  302. package/dist/core/runtime/tmux-driver.d.ts +12 -0
  303. package/dist/core/runtime/tmux-driver.js +18 -13
  304. package/dist/core/runtime/warm-pool.js +0 -4
  305. package/dist/core/scope.js +4 -3
  306. package/dist/core/spawn.js +2 -3
  307. package/dist/core/subscription-state.js +10 -17
  308. package/dist/core/substrate/__tests__/surface-match-pre-command.test.js +3 -3
  309. package/dist/core/substrate/frontmatter-validation.d.ts +1 -0
  310. package/dist/core/substrate/frontmatter-validation.js +1 -1
  311. package/dist/core/substrate/render.js +0 -12
  312. package/dist/core/substrate/schema.js +0 -12
  313. package/dist/core/substrate/surface-match.js +0 -2
  314. package/dist/core/termrender/display.js +2 -1
  315. package/dist/core/tui/page-host.d.ts +3 -3
  316. package/dist/core/tui/page-host.js +4 -4
  317. package/dist/core/tui/panel.js +3 -3
  318. package/dist/core/wake.js +0 -12
  319. package/dist/core/worktree.js +0 -2
  320. package/dist/daemon/__tests__/reconciler-signature.test.d.ts +1 -0
  321. package/dist/daemon/__tests__/reconciler-signature.test.js +36 -0
  322. package/dist/daemon/api/__tests__/error-seam.test.js +27 -0
  323. package/dist/daemon/api/__tests__/seam/api-server.test.js +2 -16
  324. package/dist/daemon/api/__tests__/seam/b10-attach-modelauth.test.js +3 -11
  325. package/dist/daemon/api/__tests__/seam/leaf-api-parity.test.js +0 -6
  326. package/dist/daemon/api/handlers/attach.js +2 -1
  327. package/dist/daemon/api/handlers/broker-ops.js +3 -6
  328. package/dist/daemon/api/handlers/canvas.js +12 -18
  329. package/dist/daemon/api/handlers/feedback-comments.js +0 -8
  330. package/dist/daemon/api/handlers/health.js +3 -2
  331. package/dist/daemon/api/handlers/human-requests.js +0 -10
  332. package/dist/daemon/api/handlers/human.js +1 -5
  333. package/dist/daemon/api/handlers/inbox.js +0 -18
  334. package/dist/daemon/api/handlers/memory.js +1 -1
  335. package/dist/daemon/api/handlers/messages.js +16 -20
  336. package/dist/daemon/api/handlers/nodes.js +23 -22
  337. package/dist/daemon/api/handlers/reports.js +1 -1
  338. package/dist/daemon/api/handlers/reviews.js +1 -6
  339. package/dist/daemon/api/handlers/worktree.js +6 -22
  340. package/dist/daemon/api/map.d.ts +4 -2
  341. package/dist/daemon/api/map.js +22 -8
  342. package/dist/daemon/api/router.js +0 -4
  343. package/dist/daemon/api/server.js +2 -5
  344. package/dist/daemon/boot-migrations.d.ts +14 -0
  345. package/dist/daemon/boot-migrations.js +31 -0
  346. package/dist/daemon/cron/armed-context.d.ts +21 -0
  347. package/dist/daemon/cron/armed-context.js +100 -0
  348. package/dist/daemon/cron/capture.d.ts +20 -0
  349. package/dist/daemon/cron/capture.js +45 -0
  350. package/dist/daemon/cron/disposition.d.ts +19 -0
  351. package/dist/daemon/cron/disposition.js +52 -0
  352. package/dist/daemon/cron/escalation.d.ts +12 -0
  353. package/dist/daemon/cron/escalation.js +124 -0
  354. package/dist/daemon/cron/held.d.ts +4 -0
  355. package/dist/daemon/cron/held.js +20 -0
  356. package/dist/daemon/cron/lease-recovery.d.ts +3 -0
  357. package/dist/daemon/cron/lease-recovery.js +119 -0
  358. package/dist/daemon/cron/live-runs.d.ts +11 -0
  359. package/dist/daemon/cron/live-runs.js +23 -0
  360. package/dist/daemon/cron/passes.d.ts +11 -0
  361. package/dist/daemon/cron/passes.js +78 -0
  362. package/dist/daemon/cron/settle.d.ts +28 -0
  363. package/dist/daemon/cron/settle.js +37 -0
  364. package/dist/daemon/cron/sinks.d.ts +2 -0
  365. package/dist/daemon/cron/sinks.js +100 -0
  366. package/dist/daemon/cron-run.d.ts +5 -41
  367. package/dist/daemon/cron-run.js +193 -882
  368. package/dist/daemon/crtrd-cli.js +2 -1
  369. package/dist/daemon/crtrd.js +22 -22
  370. package/dist/daemon/fleet.js +0 -4
  371. package/dist/daemon/human/deliver-action.js +2 -1
  372. package/dist/daemon/human/sweep.js +15 -4
  373. package/dist/daemon/manage.d.ts +2 -2
  374. package/dist/daemon/manage.js +4 -13
  375. package/dist/daemon/pidfile.js +2 -2
  376. package/dist/daemon/reconcilers/broker-supervision.js +27 -30
  377. package/dist/daemon/reconcilers/dormant-inbox.d.ts +1 -1
  378. package/dist/daemon/reconcilers/dormant-inbox.js +2 -1
  379. package/dist/daemon/reconcilers/live-obligation.js +2 -2
  380. package/dist/daemon/reconcilers/pending-review-submit.d.ts +1 -1
  381. package/dist/daemon/reconcilers/pending-review-submit.js +2 -1
  382. package/dist/daemon/reconcilers/storage-maintenance.d.ts +2 -1
  383. package/dist/daemon/reconcilers/storage-maintenance.js +2 -1
  384. package/dist/daemon/review/companion.js +2 -2
  385. package/dist/daemon/review/sweep.js +8 -3
  386. package/dist/migrations/001-surfaces-frontmatter.js +0 -6
  387. package/dist/migrations/corpus.js +2 -5
  388. package/dist/migrations/registry.js +15 -2
  389. package/dist/pi-extensions/__tests__/canvas-stophook-agentend.test.js +0 -4
  390. package/dist/pi-extensions/broker-local.js +2 -1
  391. package/dist/pi-extensions/canvas-bash-valve.js +7 -20
  392. package/dist/pi-extensions/canvas-context-intro.js +2 -7
  393. package/dist/pi-extensions/canvas-doc-substrate.js +2 -3
  394. package/dist/pi-extensions/canvas-goal-capture.js +2 -1
  395. package/dist/pi-extensions/canvas-inbox-watcher.js +5 -12
  396. package/dist/pi-extensions/canvas-passive-context.js +2 -5
  397. package/dist/pi-extensions/canvas-preview-result.js +2 -1
  398. package/dist/pi-extensions/canvas-prompt-scrub.js +2 -1
  399. package/dist/pi-extensions/canvas-recap.js +3 -16
  400. package/dist/pi-extensions/canvas-review-boundary.js +2 -5
  401. package/dist/pi-extensions/canvas-stophook.js +2 -26
  402. package/dist/pi-extensions/canvas-structured-output.js +2 -1
  403. package/dist/pi-extensions/canvas-tool-guide.js +2 -1
  404. package/dist/shared/__tests__/env-boundary.test.d.ts +1 -0
  405. package/dist/shared/__tests__/env-boundary.test.js +93 -0
  406. package/dist/shared/env.d.ts +68 -0
  407. package/dist/shared/env.js +171 -0
  408. package/package.json +3 -1
  409. package/runtime.lock.json +2 -2
  410. package/dist/core/canvas/render.d.ts +0 -47
  411. package/dist/core/canvas/render.js +0 -245
@@ -16,6 +16,7 @@
16
16
  // + model registry are real SDK construction helpers (NOT part of the swappable
17
17
  // engine seam — the fake engine receives the real, extension-loaded loader so
18
18
  // the real canvas-stophook fires session_start).
19
+ import { envModelIntent, envNodeCwd, envProfileId } from '../../shared/env.js';
19
20
  import { createServer } from 'node:net';
20
21
  import { existsSync, readFileSync, unlinkSync } from 'node:fs';
21
22
  import { join } from 'node:path';
@@ -54,7 +55,7 @@ import { FrameOverflowError, normalizeSnapshotTail, } from './broker-protocol.js
54
55
  /** A review companion may only navigate or fork within a branch whose ancestry
55
56
  * has crossed its durable transcript boundary. The broker sends this typed local
56
57
  * failure as a dedicated error frame without invoking pi's tree mutation. */
57
- export { ReviewBoundaryNavigationError, REVIEW_BOUNDARY_NAVIGATION_CODE } from './broker/frame-dispatch.js';
58
+ export { ReviewBoundaryNavigationError, REVIEW_BOUNDARY_NAVIGATION_CODE } from './broker/frame-replies.js';
58
59
  /**
59
60
  * pi 0.82 made `ModelRuntime` the canonical model/auth surface and demoted
60
61
  * `ModelRegistry` to the synchronous facade over it (`find`/`getAll`/
@@ -83,9 +84,7 @@ function formatExactModelSpec(model, thinkingLevel) {
83
84
  const base = formatModelSpec(model, undefined);
84
85
  return base === null ? null : `${base}:${thinkingLevel === undefined || thinkingLevel === '' ? 'off' : thinkingLevel}`;
85
86
  }
86
- // ---------------------------------------------------------------------------
87
87
  // Tunables (backpressure / dialog anti-deadlock)
88
- // ---------------------------------------------------------------------------
89
88
  /** Broker-side default dialog timeout (anti-deadlock). When an extension
90
89
  * dialog is forwarded to every writable client, the broker ALWAYS arms a timeout
91
90
  * (this default, or a shorter per-dialog `opts.timeout` if the extension passed
@@ -93,14 +92,12 @@ function formatExactModelSpec(model, thinkingLevel) {
93
92
  * — can never hang the agent turn forever. On fire it resolves to the SAFE default
94
93
  * (deny for confirm; cancel/undefined for select/input/editor). */
95
94
  const DEFAULT_DIALOG_TIMEOUT_MS = 120_000; // 120 s
96
- // ---------------------------------------------------------------------------
97
95
  // M3 (scout mq5thyli): the active engine session, exposed so broker-cli's FATAL
98
96
  // handlers (uncaughtException / unhandledRejection / the runBroker reject) can
99
97
  // dispose it before process.exit. The bash tool spawns children `detached` (own
100
98
  // pgid); only session.dispose() (→ abortBash / agent.abort → killProcessTree)
101
99
  // reaps them. The graceful exit path already disposes; without this the crash
102
100
  // path would `process.exit(1)` and ORPHAN every in-flight bash subprocess.
103
- // ---------------------------------------------------------------------------
104
101
  let activeSession = null;
105
102
  /** Dispose the live engine session if one exists (idempotent). Called by
106
103
  * broker-cli's fatal handlers before exit so a crash never orphans detached
@@ -117,9 +114,7 @@ export function disposeActiveSession() {
117
114
  }
118
115
  }
119
116
  }
120
- // ---------------------------------------------------------------------------
121
117
  // runBroker — the broker process body (plan T4 steps 1–6)
122
- // ---------------------------------------------------------------------------
123
118
  export async function runBroker(nodeId, startupAt = process.hrtime.bigint(), startupTs = new Date().toISOString()) {
124
119
  // A clean-abort certificate belongs only to the dead broker that wrote it.
125
120
  // Fleet classification consumes it as proof that the dead broker had a turn
@@ -275,9 +270,7 @@ export async function runBroker(nodeId, startupAt = process.hrtime.bigint(), sta
275
270
  // N3: the node's display name is re-applied by the rebind lane below so it
276
271
  // survives a session replacement (new_session/switch_session/fork), not only at
277
272
  // boot.
278
- // -------------------------------------------------------------------------
279
273
  // Socket fan-out, dialogs, and transcript tool groups
280
- // -------------------------------------------------------------------------
281
274
  const sockPath = viewSocketPath(nodeId);
282
275
  const registry = new BrokerClientRegistry(nodeId);
283
276
  const pendingDialogs = new Map();
@@ -445,10 +438,8 @@ export async function runBroker(nodeId, startupAt = process.hrtime.bigint(), sta
445
438
  const reWelcomeAll = () => {
446
439
  registry.forEachHelloed(sendWelcome);
447
440
  };
448
- // -------------------------------------------------------------------------
449
441
  // The single exit helper: dispose the engine, close + unlink the
450
442
  // socket, exit 0. Idempotent — every trigger converges here.
451
- // -------------------------------------------------------------------------
452
443
  const completeExit = (status) => {
453
444
  if (disposed)
454
445
  return;
@@ -509,7 +500,6 @@ export async function runBroker(nodeId, startupAt = process.hrtime.bigint(), sta
509
500
  }
510
501
  completeExit(status);
511
502
  };
512
- // -------------------------------------------------------------------------
513
503
  // Extension-dialog routing (C2). makeBrokerUiContext owns the dialogPromise;
514
504
  // here we just give it the three broker-side hooks it needs: the live set of
515
505
  // writable clients (empty = nobody can answer → noOp fallback), how to forward a
@@ -517,7 +507,6 @@ export async function runBroker(nodeId, startupAt = process.hrtime.bigint(), sta
517
507
  // first settles through. The zero-writable path NEVER hangs and NEVER waits on a
518
508
  // per-dialog timeout (design §5.4's timeout premise is false — see
519
509
  // makeBrokerUiContext).
520
- // -------------------------------------------------------------------------
521
510
  uiContext = makeBrokerUiContext({
522
511
  writable: () => registry.writableClients(),
523
512
  forward: (client, request) => registry.sendFrame(client, request),
@@ -542,9 +531,7 @@ export async function runBroker(nodeId, startupAt = process.hrtime.bigint(), sta
542
531
  });
543
532
  notifyTurnAccepted = frameDispatch.notifyTurnAccepted;
544
533
  resetFrameState = frameDispatch.resetForSession;
545
- // -------------------------------------------------------------------------
546
534
  // 4 (plan): the socket listener — unlink any stale socket first.
547
- // -------------------------------------------------------------------------
548
535
  try {
549
536
  if (existsSync(sockPath))
550
537
  unlinkSync(sockPath);
@@ -671,7 +658,6 @@ export async function runBroker(nodeId, startupAt = process.hrtime.bigint(), sta
671
658
  process.on('SIGTERM', () => { void abortAndExit('SIGTERM'); });
672
659
  process.on('SIGINT', () => { void abortAndExit('SIGINT'); });
673
660
  }
674
- // ---------------------------------------------------------------------------
675
661
  // snapshotMessages — the catch-up snapshot's ordered message history.
676
662
  //
677
663
  // The broker is the SOLE writer of the node's session `.jsonl`, so the session
@@ -722,7 +708,6 @@ export function snapshotMessages(session, boundaryReviewId) {
722
708
  }
723
709
  return messages === undefined ? undefined : visibleMessages(messages, { boundaryReviewId });
724
710
  }
725
- // ---------------------------------------------------------------------------
726
711
  // resolveLaunchModel — the SOLE launch model-selection policy, module-scoped
727
712
  // so both buildForManager (initial boot, inside buildBrokerSession below) and
728
713
  // the reload_auth re-drive (inside runBroker, a DIFFERENT function scope) can
@@ -731,15 +716,14 @@ export function snapshotMessages(session, boundaryReviewId) {
731
716
  // viable at all (tier 3) — this is NOT a fault; the caller decides what to do
732
717
  // with it (construct a model-less session at boot, or leave a running session
733
718
  // alone on a failed re-drive attempt).
734
- // ---------------------------------------------------------------------------
735
719
  async function resolveLaunchModel(services, cfg) {
736
- const routeRequest = modelRequestFromConfig(cfg.model, process.env['CRTR_MODEL_INTENT'], cfg.modelExact === true);
720
+ const routeRequest = modelRequestFromConfig(cfg.model, envModelIntent(), cfg.modelExact === true);
737
721
  if (routeRequest === undefined)
738
722
  return undefined;
739
723
  const registry = registryOf(services);
740
724
  const routeOptions = {
741
- cwd: process.env['CRTR_NODE_CWD'] ?? cfg.cwd,
742
- profileId: process.env['CRTR_PROFILE_ID'],
725
+ cwd: envNodeCwd() ?? cfg.cwd,
726
+ profileId: envProfileId(),
743
727
  };
744
728
  const unknown = unregisteredConcreteModel(routeRequest, registry, routeOptions);
745
729
  if (unknown !== undefined) {
@@ -812,11 +796,9 @@ function modelLessRegistry(real) {
812
796
  });
813
797
  return { runtime, reveal: () => { masked = false; } };
814
798
  }
815
- // ---------------------------------------------------------------------------
816
799
  // buildBrokerSession (plan T4 steps 2–4) — turn the launch recipe into a live
817
800
  // engine session via the pi SDK SERVICES path. Exported so the C3/C4 real-SDK
818
801
  // regression tests can drive the EXACT production wiring (not the mock).
819
- // ---------------------------------------------------------------------------
820
802
  export async function buildBrokerSession(engine, cfg) {
821
803
  // 2. Build cwd-bound runtime services via the SERVICES path (C3) — NOT plain
822
804
  // createAgentSession. createAgentSessionServices builds + reloads the
@@ -1,3 +1,4 @@
1
+ import { envNodeId } from '../../shared/env.js';
1
2
  import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
2
3
  import { join } from 'node:path';
3
4
  import { jobDir } from '../canvas/paths.js';
@@ -140,7 +141,7 @@ export function recordFault(nodeId, input) {
140
141
  updateMarker(nodeId, fault);
141
142
  }
142
143
  export function recordFaultForCurrentNode(input) {
143
- const nodeId = process.env['CRTR_NODE_ID'];
144
+ const nodeId = envNodeId();
144
145
  if (nodeId === undefined || nodeId.trim() === '')
145
146
  return false;
146
147
  if (!existsSync(jobDir(nodeId)))
@@ -186,7 +187,7 @@ export function clearFault(nodeId, opts) {
186
187
  }
187
188
  }
188
189
  export function clearFaultForCurrentNode(opts) {
189
- const nodeId = process.env['CRTR_NODE_ID'];
190
+ const nodeId = envNodeId();
190
191
  if (nodeId === undefined || nodeId.trim() === '')
191
192
  return false;
192
193
  if (!existsSync(jobDir(nodeId)))
@@ -3,6 +3,7 @@
3
3
  // Each call uses crouter's credential pool and route policy, an in-memory session,
4
4
  // and only the inline provider-rotation extension. No conversation resources or
5
5
  // canvas lifecycle hooks enter the helper session.
6
+ import { envNodeCwd, envProfileId } from '../../shared/env.js';
6
7
  import { join } from 'node:path';
7
8
  import { PoolCredentialStore } from '../pool-credential-store.js';
8
9
  import { modelRequestFromConfig, selectInitialModelRoute } from '../model-routes.js';
@@ -55,8 +56,8 @@ async function runHeadless(request, fallback, collect) {
55
56
  ]);
56
57
  if (timedOut)
57
58
  return fallback;
58
- const nodeCwd = process.env['CRTR_NODE_CWD'] ?? process.cwd();
59
- const profileId = process.env['CRTR_PROFILE_ID'];
59
+ const nodeCwd = envNodeCwd() ?? process.cwd();
60
+ const profileId = envProfileId();
60
61
  const agentDir = sdk.getAgentDir();
61
62
  const modelRuntime = await sdk.ModelRuntime.create({
62
63
  credentials: new PoolCredentialStore(join(agentDir, 'auth.json')),
@@ -25,12 +25,11 @@ import { assertMandatoryCanvasExtensions } from './canvas-extensions.js';
25
25
  import { boundFleet } from './fleet.js';
26
26
  import { emitEvent } from '../events/emit.js';
27
27
  import { bindBrokerEventSource, eventSource } from '../events/source.js';
28
- // ---------------------------------------------------------------------------
28
+ import { envBrokerEngine } from '../../shared/env.js';
29
29
  // HeadlessBrokerHost (design §3.3): a detached broker PROCESS, no tmux pane. It
30
30
  // boots the dedicated broker-cli entry directly (NEVER src/cli.ts), supervises
31
31
  // via the broker pid recorded as pi_pid, and tears down over the node's unix
32
32
  // socket (graceful `shutdown` frame, SIGTERM fallback).
33
- // ---------------------------------------------------------------------------
34
33
  /** Resolve the broker entry beside this module. A broker must run from the
35
34
  * same generation as the crtrd launching it, so module-relative resolution
36
35
  * preserves that invariant. */
@@ -48,7 +47,7 @@ const DEFAULT_BROKER_ENGINE = '@earendil-works/pi-coding-agent';
48
47
  * why: the default-deny allowlist in spawn-env.ts strips `CRTR_*` from
49
48
  * ambient env, so the child can no longer re-derive this itself). */
50
49
  function resolveEngineSpec(inv) {
51
- return inv.env['CRTR_BROKER_ENGINE'] ?? process.env['CRTR_BROKER_ENGINE'] ?? DEFAULT_BROKER_ENGINE;
50
+ return inv.env['CRTR_BROKER_ENGINE'] ?? envBrokerEngine();
52
51
  }
53
52
  function isPlainLaunchSessionPath(token) {
54
53
  return token.includes('/') || token.endsWith('.jsonl');
@@ -30,10 +30,8 @@ import { formatCard } from '../../shared/generated-context.js';
30
30
  /** The runtime-restart continuation exactly as delivered. `revive.ts` injects
31
31
  * it as the prompt of a strict resume that follows a cleanly aborted turn. */
32
32
  export const RUNTIME_RESTART_CONTINUATION = formatCard('restart-continuation', {}, 'continue');
33
- // ---------------------------------------------------------------------------
34
33
  // Companion context files: the goal (the spawning mandate) and the one-shot
35
34
  // yield message (a note from the prior self to the revived self).
36
- // ---------------------------------------------------------------------------
37
35
  /** The goal file — the prompt/task a node was spawned with, persisted at birth
38
36
  * so a fresh revive can re-read its mandate. */
39
37
  export function goalPath(nodeId) {
@@ -230,9 +228,7 @@ function backgroundJobsBlock(nodeId) {
230
228
  lines.push('', 'Read output so far: `tail -n 50 <log>` (or `crtr node bash list --tail 20`). Abandon one: `crtr node bash kill <job-id>`.');
231
229
  return `<background-jobs>\n${lines.join('\n')}\n</background-jobs>`;
232
230
  }
233
- // ---------------------------------------------------------------------------
234
231
  // buildReviveKickoff — assemble the full fresh-revive first message.
235
- // ---------------------------------------------------------------------------
236
232
  /** Assemble the auto-injected first message for a FRESH revive of `meta` from its
237
233
  * already-drained `bearings` (see drainBearings) plus pure on-disk reads of the
238
234
  * node's goal, roadmap, and context dir, framed so the revived node can rebuild
@@ -1,3 +1,4 @@
1
+ import { envProfileId } from '../../shared/env.js';
1
2
  import { resolve } from 'node:path';
2
3
  import { getNode } from '../canvas/canvas.js';
3
4
  import { usage } from '../errors.js';
@@ -10,7 +11,7 @@ export function resolveCreateKind(kind, profile) {
10
11
  /** Validate a kind against the same cwd/profile configuration used to build its launch.
11
12
  * Resolution is subprocess-isolated: crtrd is single-threaded and a config read can
12
13
  * block indefinitely on a network filesystem. */
13
- export async function assertLaunchKind(kind, cwd = process.cwd(), profileId = process.env['CRTR_PROFILE_ID'] || null) {
14
+ export async function assertLaunchKind(kind, cwd = process.cwd(), profileId = envProfileId() || null) {
14
15
  const kinds = Object.keys((await readMergedLaunchConfigAsync(cwd, profileId)).kinds);
15
16
  if (!kinds.includes(kind)) {
16
17
  throw usage(`unknown kind: ${kind}`, { received: kind, next: `Valid kinds: ${kinds.sort().join(', ')}.` });
@@ -15,8 +15,8 @@ export type { PortableModelSelection } from './model-selection.js';
15
15
  * stays unpinned. */
16
16
  export declare function isProviderPinnedModelToken(model: string): boolean;
17
17
  /** True when `model` is a PORTABLE ladder token — a bare strength/alias
18
- * (`ultra`, `normal`, `opus`, …) or a `anthropic|openai / strength` pair —
19
- * i.e. a form `normalizeModel` resolves through the ladders rather than
18
+ * (`ultra`, `normal`, `opus`, …), an `anthropic|openai / strength` pair, or
19
+ * an anthropic family alias — i.e. a form `normalizeModel` resolves through the ladders rather than
20
20
  * passing through as a concrete id. Portable tokens are safe to PERSIST as
21
21
  * defaults (`persistDefaultKindModel`): they re-resolve against whatever
22
22
  * ladder the running build ships, so they never freeze a stale concrete
@@ -8,6 +8,7 @@
8
8
  // fresh cycle as a new branch rather than starting a new file.
9
9
  // The spec is rewritten on every polymorph (base→orchestrator) so a node
10
10
  // always comes back as its *current* self.
11
+ import { envProfileId } from '../../shared/env.js';
11
12
  import { mkdirSync, writeFileSync } from 'node:fs';
12
13
  import { join } from 'node:path';
13
14
  import { readMergedLaunchConfig } from '../config.js';
@@ -25,7 +26,6 @@ import { modelLadders, defaultProvider, STRENGTH_ALIASES, MODEL_PROVIDER_KEYS, M
25
26
  // file's openDb-reaching graph. Re-exported here so existing importers of these
26
27
  // launch.ts symbols keep working.
27
28
  export { OPENAI_PROVIDER_ENV, configuredLadderProviders, inferPortableModelSelection } from './model-selection.js';
28
- // ---------------------------------------------------------------------------
29
29
  // Two-axis model resolution: (provider × strength) → concrete `model:thinking`.
30
30
  //
31
31
  // A persona declares ONE `model:` field valued `provider/strength` (e.g.
@@ -36,7 +36,6 @@ export { OPENAI_PROVIDER_ENV, configuredLadderProviders, inferPortableModelSelec
36
36
  // pi accepts the `:thinking` suffix on `--model` (off,minimal,low,medium,high,
37
37
  // xhigh — confirmed via `pi --help`), so the resolved spec is exactly the
38
38
  // string buildPiArgv passes to `--model`; no extra launch plumbing.
39
- // ---------------------------------------------------------------------------
40
39
  /** Model-ladder resolution goes through `readMergedLaunchConfig` — the SAME
41
40
  * project-stack > profile > user > builtin precedence `resolveKindConfig`
42
41
  * already uses for `kinds` — so a project/profile override of `modelLadders`
@@ -86,10 +85,14 @@ function findLadderCell(normalized, ladders, providers) {
86
85
  * strong/medium/light, ALWAYS anthropic (ignores the env/config default).
87
86
  * 4. Anything else → pass through unchanged. */
88
87
  function modelTokenStrength(model) {
89
- const rest = model.includes('/') ? model.slice(model.indexOf('/') + 1) : model;
88
+ const slash = model.indexOf('/');
89
+ const provider = slash === -1 ? undefined : model.slice(0, slash);
90
+ const rest = slash === -1 ? model : model.slice(slash + 1);
90
91
  const strength = STRENGTH_ALIASES[rest];
91
92
  if (strength !== undefined)
92
93
  return strength;
94
+ if (provider !== undefined && provider !== 'anthropic')
95
+ return null;
93
96
  if (rest === 'opus')
94
97
  return 'strong';
95
98
  if (rest === 'sonnet')
@@ -134,8 +137,8 @@ export function isProviderPinnedModelToken(model) {
134
137
  return model.includes('/') && !isPortableModelToken(model);
135
138
  }
136
139
  /** True when `model` is a PORTABLE ladder token — a bare strength/alias
137
- * (`ultra`, `normal`, `opus`, …) or a `anthropic|openai / strength` pair —
138
- * i.e. a form `normalizeModel` resolves through the ladders rather than
140
+ * (`ultra`, `normal`, `opus`, …), an `anthropic|openai / strength` pair, or
141
+ * an anthropic family alias — i.e. a form `normalizeModel` resolves through the ladders rather than
139
142
  * passing through as a concrete id. Portable tokens are safe to PERSIST as
140
143
  * defaults (`persistDefaultKindModel`): they re-resolve against whatever
141
144
  * ladder the running build ships, so they never freeze a stale concrete
@@ -145,7 +148,8 @@ export function isPortableModelToken(model) {
145
148
  const slash = model.indexOf('/');
146
149
  const provider = model.slice(0, slash);
147
150
  const rest = model.slice(slash + 1);
148
- return (provider === 'anthropic' || provider === 'openai') && STRENGTH_ALIASES[rest] !== undefined;
151
+ return (provider === 'anthropic' || provider === 'openai') && (STRENGTH_ALIASES[rest] !== undefined
152
+ || (provider === 'anthropic' && (rest === 'opus' || rest === 'sonnet' || rest === 'haiku')));
149
153
  }
150
154
  return STRENGTH_ALIASES[model] !== undefined || model === 'opus' || model === 'sonnet' || model === 'haiku';
151
155
  }
@@ -169,6 +173,14 @@ export function normalizeModel(model, ladders = modelLadders()) {
169
173
  if (strength !== undefined && (provider === 'anthropic' || provider === 'openai')) {
170
174
  return ladderFor(provider, ladders)[strength];
171
175
  }
176
+ if (provider === 'anthropic') {
177
+ if (rest === 'opus')
178
+ return ladders.anthropic.strong;
179
+ if (rest === 'sonnet')
180
+ return ladders.anthropic.medium;
181
+ if (rest === 'haiku')
182
+ return ladders.anthropic.light;
183
+ }
172
184
  // Concrete `provider/id` (optionally `...:thinking`) — pass through.
173
185
  return model;
174
186
  }
@@ -258,7 +270,7 @@ function buildLaunchSpecFromMerged(kind, mode, opts, merged) {
258
270
  }
259
271
  /** Build a launch recipe for callers that are not on crtrd's event loop. */
260
272
  export function buildLaunchSpec(kind, mode, opts) {
261
- return buildLaunchSpecFromMerged(kind, mode, opts, readMergedLaunchConfig(opts.cwd ?? process.cwd(), opts.profileId !== undefined ? opts.profileId : (process.env['CRTR_PROFILE_ID'] || null)));
273
+ return buildLaunchSpecFromMerged(kind, mode, opts, readMergedLaunchConfig(opts.cwd ?? process.cwd(), opts.profileId !== undefined ? opts.profileId : (envProfileId() || null)));
262
274
  }
263
275
  /**
264
276
  * Resolve launch config in a bounded subprocess before building a recipe.
@@ -268,7 +280,7 @@ export function buildLaunchSpec(kind, mode, opts) {
268
280
  */
269
281
  export async function buildLaunchSpecAsync(kind, mode, opts) {
270
282
  const cwd = opts.cwd ?? process.cwd();
271
- const profileId = opts.profileId !== undefined ? opts.profileId : (process.env['CRTR_PROFILE_ID'] || null);
283
+ const profileId = opts.profileId !== undefined ? opts.profileId : (envProfileId() || null);
272
284
  const merged = await readMergedLaunchConfigAsync(cwd, profileId);
273
285
  return buildLaunchSpecFromMerged(kind, mode, opts, merged);
274
286
  }
@@ -308,9 +320,7 @@ export function readMergedLaunchConfigAsync(cwd, profileId) {
308
320
  });
309
321
  });
310
322
  }
311
- // ---------------------------------------------------------------------------
312
323
  // Build the pi argv to launch / revive a node
313
- // ---------------------------------------------------------------------------
314
324
  /** Persist a node's (possibly large) system prompt to a file in its node dir and
315
325
  * return the absolute path, so callers can pass a short path to pi instead of
316
326
  * the inline text. Returns null if the write fails — the caller then falls back
@@ -25,11 +25,13 @@
25
25
  // `cancel`.
26
26
  //
27
27
  // Layering note: lifecycle.ts is runtime, but it is the canvas write surface's
28
- // `transition` verb (the only writer of status+intent), so it owns its atomic
29
- // row UPDATE directly via openDb — the one sanctioned exception to "only
30
- // canvas.ts touches the db" (see src/core/canvas/.crouter/memory/INDEX.md),
31
- // exactly as db.ts's backfill is the sanctioned exception for a data migration.
32
- import { openDb, getNode, cancelCronsOnWake } from '../canvas/index.js';
28
+ // `transition` verb (the only writer of status+intent), so it drives the
29
+ // atomic status+intent write through canvas.ts's `transitionStatusIntent`
30
+ // (its own decision logic — the legality table below, and the
31
+ // unknown-node/illegal-move/already-finalized-cancel branching — stays here;
32
+ // only the raw conditional UPDATE lives in canvas.ts, keeping every `.prepare(`
33
+ // inside `core/canvas/`).
34
+ import { getNode, cancelCronsOnWake, transitionStatusIntent } from '../canvas/index.js';
33
35
  const ANY = '*';
34
36
  /** The supervised statuses — a live node the daemon watches. */
35
37
  const LIVE = ['active', 'idle'];
@@ -71,28 +73,17 @@ export function transition(nodeId, event) {
71
73
  const spec = TRANSITIONS[event];
72
74
  const writeStatus = Object.prototype.hasOwnProperty.call(spec, 'status');
73
75
  const writeIntent = Object.prototype.hasOwnProperty.call(spec, 'intent');
74
- const assignments = [];
75
- const values = [];
76
- if (writeStatus) {
77
- assignments.push('status = ?');
78
- values.push(spec.status ?? null);
79
- }
80
- if (writeIntent) {
81
- assignments.push('intent = ?');
82
- values.push(spec.intent ?? null);
83
- }
84
- const predicates = ['node_id = ?'];
85
- values.push(nodeId);
86
- if (spec.from !== ANY) {
87
- predicates.push(`status IN (${spec.from.map(() => '?').join(', ')})`);
88
- values.push(...spec.from);
89
- }
76
+ const fields = {};
77
+ if (writeStatus)
78
+ fields.status = spec.status;
79
+ if (writeIntent)
80
+ fields.intent = spec.intent ?? null;
90
81
  // A committed canonical result is protected from a concurrent close.
91
- if (event === 'cancel')
92
- predicates.push('final_report IS NULL');
93
- const db = openDb();
94
- const result = db.prepare(`UPDATE nodes SET ${assignments.join(', ')} WHERE ${predicates.join(' AND ')}`).run(...values);
95
- if (result.changes === 0) {
82
+ const changes = transitionStatusIntent(nodeId, fields, {
83
+ fromStatuses: spec.from === ANY ? undefined : spec.from,
84
+ requireNoFinalReport: event === 'cancel',
85
+ });
86
+ if (changes === 0) {
96
87
  const cur = getNode(nodeId);
97
88
  if (cur === null)
98
89
  throw new Error(`transition: unknown node ${nodeId}`);
@@ -1,4 +1,4 @@
1
- import type { AssistantMessageDiagnostic } from "@earendil-works/pi-ai";
1
+ import type { AssistantMessageDiagnostic } from '@earendil-works/pi-ai';
2
2
  /** Diagnostic `type` tag carrying the cooling deadline; also the wire contract the
3
3
  * stophook matches on. */
4
4
  export declare const MANAGED_PROVIDER_COOLING_DIAGNOSTIC = "crtr:managed-provider-cooling";
@@ -217,10 +217,8 @@ export async function headlessName(body, sourceCap = PROMPT_CAP) {
217
217
  export function headlessNameFromConversation(conversation) {
218
218
  return headlessName(conversation, 30_000);
219
219
  }
220
- // ---------------------------------------------------------------------------
221
220
  // Issue titles — turn a free-text todo/idea/bug note into a short, human-readable
222
221
  // GitHub-issue title (sentence case, NOT kebab). Used by `crtr canvas issue new`.
223
- // ---------------------------------------------------------------------------
224
222
  const TITLE_SYSTEM_PROMPT = 'You write GitHub issue titles. Given a free-text note (a todo, idea, or bug ' +
225
223
  'report), reply with ONLY a concise issue title: sentence case, 4-10 words, no ' +
226
224
  'trailing period, no quotes, no prefix like "Bug:" or "Idea:", no markdown. ' +
@@ -12,6 +12,7 @@
12
12
  // learns when the work finishes — this seeds the subscription
13
13
  // graph to mirror the spawn structure. A `spawned_by` audit edge
14
14
  // is also recorded.
15
+ import { envHomeOverride } from '../../shared/env.js';
15
16
  import { randomBytes } from 'node:crypto';
16
17
  import { mkdirSync } from 'node:fs';
17
18
  import { createNode, getNode, getRow, subscribe, recordSpawn, contextDir, } from '../canvas/index.js';
@@ -200,7 +201,7 @@ export function nodeEnv(meta) {
200
201
  if (meta.parent)
201
202
  env['CRTR_PARENT_NODE_ID'] = meta.parent;
202
203
  // Propagate an explicit canvas home so children share the same canvas.
203
- const home = process.env['CRTR_HOME'];
204
+ const home = envHomeOverride();
204
205
  if (home !== undefined && home !== '')
205
206
  env['CRTR_HOME'] = home;
206
207
  // Propagate the subtree-root node id. The broker uses it to group a subtree;
@@ -4,9 +4,7 @@
4
4
  import { getNode, updateNode } from '../canvas/index.js';
5
5
  import { readRoadmap, roadmapPath } from './roadmap.js';
6
6
  import { orchestratorContextNote } from './bearings.js';
7
- // ---------------------------------------------------------------------------
8
7
  // Base → orchestrator guidance
9
- // ---------------------------------------------------------------------------
10
8
  /** Fresh-context framing for a base → orchestrator change. */
11
9
  function orchestrationGuidance(nodeId, kind) {
12
10
  const roadmap = readRoadmap(nodeId) ?? '(no roadmap yet)';
@@ -18,9 +16,7 @@ function orchestrationGuidance(nodeId, kind) {
18
16
  parts.push('', `Your roadmap scaffold (\`${rmPath}\`) — author it now: state the goal, exit criteria, and the phase skeleton, using your orchestration protocol. Current contents:`, '', roadmap, '', orchestratorContextNote(nodeId), '', 'Your three long-term memory scopes (user-global, project, node-local) already surface in your bearings at boot. What promotion changes is USE, not access: you now persist across refresh cycles, so node-local carries what THIS goal needs between your refreshes, and durable facts you commit to user-global or project outlive this node. Use `crtr memory write` to save a document; `crtr memory list` to browse; `crtr memory find` to search (see "Your long-term memory" above for the full substrate flow).', '', 'Then delegate each phase with `crtr node new --kind <kind>`. When your context fills, run `crtr node yield` to refresh against this roadmap.');
19
17
  return parts.join('\n');
20
18
  }
21
- // ---------------------------------------------------------------------------
22
19
  // Orchestrator → base guidance
23
- // ---------------------------------------------------------------------------
24
20
  /** orchestrator → base (demote): hands-on again, finish in-window. */
25
21
  function baseModeGuidance() {
26
22
  return ('You are HANDS-ON again — base mode. Do the work yourself in THIS window and finish it here; ' +
@@ -36,9 +32,7 @@ export function transitionGuidance(nodeId, from, to) {
36
32
  const node = getNode(nodeId);
37
33
  return orchestrationGuidance(nodeId, node?.kind ?? 'general');
38
34
  }
39
- // ---------------------------------------------------------------------------
40
35
  // Detector + ack commit.
41
- // ---------------------------------------------------------------------------
42
36
  /** Compare the live persona with the last acknowledged context delivery.
43
37
  * `guidance` contains only fresh-context framing; current snapshots supply its
44
38
  * documents. An absent or pre-kind acknowledgment cannot fabricate drift. */
@@ -15,19 +15,16 @@
15
15
  // §5.1 import-lint: placement-tmux.ts is an allowed direct importer of
16
16
  // tmux-driver.ts — it is the sanctioned pure-tmux seam, re-exporting the driver
17
17
  // verbs other modules need so they never import the driver directly.
18
+ import { envHomeOverride } from '../../shared/env.js';
18
19
  import { paneLocation, ensureSession, openNodeWindow, splitWindow, selectLayout, } from './tmux-driver.js';
19
20
  import { BROKER_VIEW_SOCKET_WAIT_MS } from './view-socket.js';
20
21
  // Re-export the pure tmux driver verbs the CLI/viewer consumers need without
21
22
  // exposing the canvas focuses table or sending command modules to the raw driver.
22
23
  export { paneExists, paneLocation, paneOfNode, paneCurrentPath, paneSize, paneRunning, ensureSession, openNodeWindow, splitWindow, closePane, currentTmux, switchClient, selectWindow, selectLayout, selectPane, getPaneOption, setPaneOption, breakPane, inTmux, piCommand, shellQuote, renameWindow, displayPopup, windowOfPane, respawnPaneSync, respawnPaneInBackground, respawnPaneInPlace, paneStartCommand, panePid, windowAlive, listLivePanes, VIEWER_STAMP_OPTION, stampViewerPaneAsync, liveViewerPanes, sendAttachControl, displayClientMessage, } from './tmux-driver.js';
23
24
  export { installTmuxBindings } from './tmux-bindings.js';
24
- // ---------------------------------------------------------------------------
25
25
  // view.sock readiness — the one shared broker-cold-start primitive
26
- // ---------------------------------------------------------------------------
27
26
  export { BROKER_VIEW_SOCKET_WAIT_MS, waitForBrokerViewSocket } from './view-socket.js';
28
- // ---------------------------------------------------------------------------
29
27
  // Viewer pane opener (tmux half, NO focus-row write)
30
- // ---------------------------------------------------------------------------
31
28
  /** Env for a `crtr surface attach` VIEWER pane (the focus split, the spawn viewer
32
29
  * window, AND recycle's re-attach respawn): propagate CRTR_HOME so the viewer
33
30
  * resolves the SAME canvas home — and thus the right view.sock — under a
@@ -35,7 +32,7 @@ export { BROKER_VIEW_SOCKET_WAIT_MS, waitForBrokerViewSocket } from './view-sock
35
32
  * server env, like every other crtr chrome pane). One helper so the viewer-pane
36
33
  * sites can't drift. */
37
34
  export function viewerSplitEnv() {
38
- const crtrHome = process.env['CRTR_HOME'];
35
+ const crtrHome = envHomeOverride();
39
36
  return crtrHome !== undefined ? { CRTR_HOME: crtrHome } : {};
40
37
  }
41
38
  /** Open a `crtr surface attach` VIEWER pane for `nodeId` — the PURE tmux half of
@@ -22,8 +22,9 @@
22
22
  // • The chrome/viewer tmux re-exports (§2.1): placement stays the sanctioned
23
23
  // model-over-driver. Raw driver access is confined to placement, its pure
24
24
  // tmux half, the bindings owner, and tmux chrome.
25
+ import { envNodeId } from '../../shared/env.js';
25
26
  import { getNode, fullName, deleteNode, listNodes, openFocusRow, closeFocusRow, getFocusByNode, getFocusByPane, getFocusById, listFocuses as listFocusRows, } from '../canvas/index.js';
26
- import { nodeHasAssistantMessage, isNodeStreaming } from '../canvas/render.js';
27
+ import { nodeHasAssistantMessage, isStreaming } from '../canvas/render-source.js';
27
28
  import { headlessBrokerHost } from './host.js';
28
29
  import { paneExists, paneLocation, closePane, currentTmux, switchClient, selectWindow, selectLayout, selectPane, getPaneOption, setPaneOption, respawnPaneSync, windowOfPane, renameWindow, } from './tmux-driver.js';
29
30
  // The PURE tmux + broker-socket half now lives in placement-tmux.ts. placement
@@ -47,7 +48,6 @@ import { isPidAlive } from '../canvas/pid.js';
47
48
  // tmux-driver.ts directly.
48
49
  export { splitWindow, breakPane, selectLayout, selectPane, focusWindow, selectWindow, switchClient, openNodeWindow, ensureSession, paneLocation, paneExists, paneRunning, paneCurrentPath, currentTmux, inTmux, paneOfNode, setPaneOption, getPaneOption, piCommand, shellQuote, respawnPaneSync, closePane, displayPopup, windowOfPane, renameWindow, paneOfWindow, windowAlive, listLivePanes, } from './tmux-driver.js';
49
50
  export { installTmuxBindings } from './tmux-bindings.js';
50
- // ---------------------------------------------------------------------------
51
51
  // Viewer registry reads — COMPOSE over the canvas focuses table (§2.3/§4).
52
52
  //
53
53
  // placement is the front door for "which nodes have a viewer on screen"; the SQL
@@ -57,7 +57,6 @@ export { installTmuxBindings } from './tmux-bindings.js';
57
57
  // GC is lazy on read: a row whose viewer pane no longer exists (the user closed
58
58
  // the window, the attach client died) is pruned the next time it is read, so the
59
59
  // registry self-heals without a sweeper.
60
- // ---------------------------------------------------------------------------
61
60
  /** Prune a viewer row whose pane is gone; return it iff its pane is still live. */
62
61
  function liveOrPrune(f) {
63
62
  if (f === null)
@@ -131,9 +130,7 @@ export function openViewerWindow(nodeId, session, opts = {}) {
131
130
  return null;
132
131
  return registerViewerFocus(nodeId, opened.pane, opened.session, opened.window);
133
132
  }
134
- // ---------------------------------------------------------------------------
135
133
  // focus — the front door for `surface node focus` / `surface node cycle` / nav (§A.4)
136
- // ---------------------------------------------------------------------------
137
134
  /** Bring a node's ONE viewer pane forefront (§A.4). Every node is a detached
138
135
  * broker, so there is no engine pane to swap into the caller's viewport — focus
139
136
  * instead ensures the node's single surface-attach viewer is on screen beside (or
@@ -283,9 +280,7 @@ export async function focus(nodeId, opts) {
283
280
  surfaceNewPane(opened.pane);
284
281
  return { focused: true, session: opened.session, inPlace: false, revived };
285
282
  }
286
- // ---------------------------------------------------------------------------
287
283
  // Teardown / detach — close a node's viewer pane + row
288
- // ---------------------------------------------------------------------------
289
284
  /** Tear a node off its viewer (close/reset/cancel teardown). The broker engine
290
285
  * is killed by its own host teardown over view.sock — this only closes the
291
286
  * on-screen viewer: kill its pane (the window collapses once its last pane goes)
@@ -326,9 +321,7 @@ export function detachToBackground(nodeId, pane) {
326
321
  reapIfEmpty(nodeId);
327
322
  return closed;
328
323
  }
329
- // ---------------------------------------------------------------------------
330
324
  // Reaping empty nodes (no AI output) on close / detach
331
- // ---------------------------------------------------------------------------
332
325
  /** Is this node an EMPTY SHELL right now — a node whose engine never produced an
333
326
  * assistant message? Such nodes are pure dead weight on the graph. A node
334
327
  * actively generating its FIRST turn is NOT empty (its output just hasn't landed
@@ -337,7 +330,7 @@ function isEmptyNode(nodeId) {
337
330
  const m = getNode(nodeId);
338
331
  if (m === null)
339
332
  return false;
340
- if (isNodeStreaming(nodeId))
333
+ if (isStreaming(nodeId, getNode(nodeId)?.pi_pid ?? null))
341
334
  return false;
342
335
  return !nodeHasAssistantMessage(m.pi_session_file);
343
336
  }
@@ -366,7 +359,7 @@ export function reapIfEmpty(nodeId, keepPane) {
366
359
  * deleting. Returns the reaped (or, under dryRun, reapable) node ids. */
367
360
  export function reapEmptyNodes(opts = {}) {
368
361
  const dryRun = opts.dryRun ?? false;
369
- const selfId = process.env['CRTR_NODE_ID'] ?? '';
362
+ const selfId = envNodeId() ?? '';
370
363
  const reaped = [];
371
364
  for (const row of listNodes()) {
372
365
  const id = row.node_id;
@@ -11,6 +11,7 @@
11
11
  // there is no local fallback, unlike naming, because a wrong recap is worse than
12
12
  // none.
13
13
  import { headlessText } from './headless-pi.js';
14
+ import { envRecapModelOverride } from '../../shared/env.js';
14
15
  /** Wall-clock budget for the headless pi call before we give up (no recap). */
15
16
  const RECAP_TIMEOUT_MS = 25_000;
16
17
  /** Fallback model when the live session has not resolved one yet. Ordinarily the
@@ -59,9 +60,9 @@ function recapUserPrompt(conversation, roadmap) {
59
60
  /** Model precedence is explicit env override → the conversation's live
60
61
  * working model → the small fallback. */
61
62
  function recapModel(liveModel) {
62
- const override = process.env['CRTR_RECAP_MODEL'];
63
- if (override !== undefined && override.trim() !== '')
64
- return override.trim();
63
+ const override = envRecapModelOverride();
64
+ if (override !== undefined)
65
+ return override;
65
66
  if (liveModel !== undefined && liveModel.trim() !== '')
66
67
  return liveModel.trim();
67
68
  return DEFAULT_RECAP_MODEL;