@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
@@ -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(', ')}.` });
@@ -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`
@@ -258,7 +257,7 @@ function buildLaunchSpecFromMerged(kind, mode, opts, merged) {
258
257
  }
259
258
  /** Build a launch recipe for callers that are not on crtrd's event loop. */
260
259
  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)));
260
+ return buildLaunchSpecFromMerged(kind, mode, opts, readMergedLaunchConfig(opts.cwd ?? process.cwd(), opts.profileId !== undefined ? opts.profileId : (envProfileId() || null)));
262
261
  }
263
262
  /**
264
263
  * Resolve launch config in a bounded subprocess before building a recipe.
@@ -268,7 +267,7 @@ export function buildLaunchSpec(kind, mode, opts) {
268
267
  */
269
268
  export async function buildLaunchSpecAsync(kind, mode, opts) {
270
269
  const cwd = opts.cwd ?? process.cwd();
271
- const profileId = opts.profileId !== undefined ? opts.profileId : (process.env['CRTR_PROFILE_ID'] || null);
270
+ const profileId = opts.profileId !== undefined ? opts.profileId : (envProfileId() || null);
272
271
  const merged = await readMergedLaunchConfigAsync(cwd, profileId);
273
272
  return buildLaunchSpecFromMerged(kind, mode, opts, merged);
274
273
  }
@@ -308,9 +307,7 @@ export function readMergedLaunchConfigAsync(cwd, profileId) {
308
307
  });
309
308
  });
310
309
  }
311
- // ---------------------------------------------------------------------------
312
310
  // Build the pi argv to launch / revive a node
313
- // ---------------------------------------------------------------------------
314
311
  /** Persist a node's (possibly large) system prompt to a file in its node dir and
315
312
  * return the absolute path, so callers can pass a short path to pi instead of
316
313
  * 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;
@@ -38,11 +38,10 @@
38
38
  // mutating the live pointer — the explicit operation must not retract a
39
39
  // canonical final unless it can actually enact the requested re-task.
40
40
  import { join } from 'node:path';
41
- import { openDb, reportsDir, updateNode } from '../canvas/index.js';
41
+ import { clearFinalReportIfMatches, getFinalReport, reportsDir, updateNode } from '../canvas/index.js';
42
42
  import { InputError } from '../io.js';
43
43
  function currentFinalReport(nodeId) {
44
- const row = openDb().prepare('SELECT final_report FROM nodes WHERE node_id = ?').get(nodeId);
45
- return row?.final_report ?? null;
44
+ return getFinalReport(nodeId);
46
45
  }
47
46
  function throwNodeFinalized(nodeId, finalReport) {
48
47
  const reportPath = join(reportsDir(nodeId), finalReport);
@@ -90,10 +89,8 @@ export function assertFinalizedForReopen(nodeId) {
90
89
  * canonical result. Callers MUST NOT proceed to revive/deliver when this
91
90
  * throws. */
92
91
  export function commitReopen(nodeId, expectedFinalReport) {
93
- const result = openDb()
94
- .prepare('UPDATE nodes SET final_report = NULL, finalized_at = NULL WHERE node_id = ? AND final_report = ?')
95
- .run(nodeId, expectedFinalReport);
96
- if (result.changes === 0) {
92
+ const cleared = clearFinalReportIfMatches(nodeId, expectedFinalReport);
93
+ if (!cleared) {
97
94
  throw new InputError({
98
95
  error: 'reopen_latch_changed',
99
96
  message: `${nodeId}'s finalization latch changed since it was last inspected — a concurrent --reopen (or a fresh push final) already moved it, so clearing it now would risk erasing a newer canonical result.`,
@@ -27,9 +27,7 @@ import { tearDownNode, focusOf, registerViewerFocus, respawnPaneSync, windowOfPa
27
27
  import { viewerSplitEnv, waitForBrokerViewSocket } from './placement-tmux.js';
28
28
  import { buildLaunchSpecAsync, buildPiArgv } from './launch.js';
29
29
  import { spawnNode, rootOfSpine } from './nodes.js';
30
- // ---------------------------------------------------------------------------
31
30
  // reapDescendants — tear down a root's descendant sub-DAG (shared helper)
32
- // ---------------------------------------------------------------------------
33
31
  /** Reap the descendant sub-DAG of `rootId`: mark each **canceled** (the user
34
32
  * moved on — a clean teardown, NOT a fault) + clear intent FIRST, then kill its
35
33
  * window (closes the daemon revive race). Edges are LEFT INTACT — descendants
@@ -175,9 +173,7 @@ export function handleNewSession(nodeId, newSessionId, newSessionFile) {
175
173
  resetRoot(nodeId, newSessionId, newSessionFile);
176
174
  return { path: 'reset-child' };
177
175
  }
178
- // ---------------------------------------------------------------------------
179
176
  // markCleanExitDone — the clean-exit→done termination guard
180
- // ---------------------------------------------------------------------------
181
177
  /** Resolve a cleanly-exiting node to `done`. Returns true iff it transitioned.
182
178
  * Guard: only a real quit, and only a node still active|idle with no pending
183
179
  * intent — so it never clobbers a node already routed by agent_end to done
@@ -113,9 +113,7 @@ async function attachInline(nodeId) {
113
113
  });
114
114
  process.exit(code);
115
115
  }
116
- // ---------------------------------------------------------------------------
117
116
  // `-r` picker — a minimal raw-mode keypress menu (pre-boot, we own the TTY).
118
- // ---------------------------------------------------------------------------
119
117
  const HILITE = accent('\u25b8');
120
118
  const cursorUp = (n) => (n > 0 ? `\u001b[${n}A\r` : '');
121
119
  const CLEAR_DOWN = '\u001b[0J';
@@ -17,7 +17,9 @@
17
17
  //
18
18
  // These are requests. Durable `intent=refresh` or `cycle_pending=true` always
19
19
  // vetoes strict resume so focus and daemon callers cannot reopen an old
20
- // context while a fresh cycle is still unconfirmed.
20
+ // context while a fresh cycle is still unconfirmed. A parked node vetoes it
21
+ // too, but only when the revival carries new input — unseen wake-worthy mail —
22
+ // and there is goal/roadmap ground state to re-enter on.
21
23
  //
22
24
  // reviveNode remains the ONLY sanctioned launcher of the node engine.
23
25
  import { existsSync } from 'node:fs';
@@ -30,6 +32,7 @@ import { InputError } from '../io.js';
30
32
  import { buildPiArgv } from './launch.js';
31
33
  import { buildReviveKickoff, drainBearings, readGoal, readYieldMessage, RUNTIME_RESTART_CONTINUATION } from './kickoff.js';
32
34
  import { readRoadmap } from './roadmap.js';
35
+ import { hasUnseenWakeMail } from '../feed/inbox.js';
33
36
  import { headlessBrokerHost } from './host.js';
34
37
  import { boundFleet } from './fleet.js';
35
38
  import { clearFault, beginBootFaultAttempt } from './fault.js';
@@ -37,9 +40,7 @@ import { clearContextExposureState, initializeForkContextExposure, } from '../su
37
40
  import { rootOfSpine } from './nodes.js';
38
41
  import { isReviewCompanionBound } from '../review/companion.js';
39
42
  import { assertProfileActive } from '../profiles/manifest.js';
40
- // ---------------------------------------------------------------------------
41
43
  // resumeArgs — which session source a revive resumes from
42
- // ---------------------------------------------------------------------------
43
44
  /** Pick the `--session` source for a revive. Both resume=true (a true resume)
44
45
  * and resume=false (a refresh-yield) resume by the absolute session-file path
45
46
  * (immune to cwd; pi opens it directly) — but ONLY when that file actually
@@ -62,9 +63,7 @@ export function resumeArgs(meta, resume) {
62
63
  return {};
63
64
  return resume ? { resumeSessionPath: path } : { resumeSessionPath: path, newCycle: true };
64
65
  }
65
- // ---------------------------------------------------------------------------
66
66
  // Return type
67
- // ---------------------------------------------------------------------------
68
67
  /** A relaunch that has NO transcript to resume or branch from, and no durable
69
68
  * work state on disk (no roadmap, no yield note), is not a revive at all — the
70
69
  * node never got a conversation off the ground, so there is no "previous
@@ -91,9 +90,7 @@ export function hasFreshGroundState(nodeId) {
91
90
  const roadmap = readRoadmap(nodeId);
92
91
  return roadmap !== null && roadmap.trim() !== '';
93
92
  }
94
- // ---------------------------------------------------------------------------
95
93
  // reviveNode
96
- // ---------------------------------------------------------------------------
97
94
  /** Relaunch `nodeId`'s broker engine from its persisted recipe and update canvas
98
95
  * meta. Opens no viewer (engine-only).
99
96
  *
@@ -194,14 +191,21 @@ export function reviveNode(nodeId, opts) {
194
191
  // selects only an existing absolute session path: strict
195
192
  // resume continues its leaf, while fresh/cycle mode roots a new one.
196
193
  // A parked node re-enters on a FRESH cycle grounded in goal + roadmap rather
197
- // than resuming the transcript its parking turn concluded — the second veto
198
- // on strict resume, and a property of the node, so every revival doorway gets
199
- // it without learning a predicate. Its one exception is a park with nothing
200
- // on disk to ground a fresh window (reachable only on the degraded path,
201
- // which writes no summary): resuming an old transcript beats waking amnesiac.
194
+ // than resuming the transcript its parking turn concluded — but ONLY when the
195
+ // revival carries new input. Mail that arrived after the park is what a fresh
196
+ // window is for: every doorway that wakes a parked node because something
197
+ // arrived appends the durable entry BEFORE calling in, so the unseen set IS
198
+ // that input. An input-less revival — a bare `node lifecycle revive`, a viewer
199
+ // attach, a focus — has nothing to answer, so a fresh cycle would only buy a
200
+ // nothing-to-do turn and a re-park 15 minutes later: it quietly resumes the
201
+ // old transcript instead, injects no kickoff, and generates nothing. The park
202
+ // clock re-parks it in due course.
203
+ // The second exception is unchanged: a park with nothing on disk to ground a
204
+ // fresh window (reachable only on the degraded path, which writes no summary)
205
+ // resumes rather than waking amnesiac.
202
206
  // The transition below clears the marker, and this snapshot precedes it.
203
207
  const refreshPending = meta.intent === 'refresh' || meta.cycle_pending === true;
204
- const parkedReentry = isParked(meta) && hasFreshGroundState(nodeId);
208
+ const parkedReentry = isParked(meta) && hasFreshGroundState(nodeId) && hasUnseenWakeMail(nodeId);
205
209
  const effectiveResume = refreshPending || parkedReentry ? false : opts.resume;
206
210
  const resume = forkBirthPending ? {} : resumeArgs(meta, effectiveResume);
207
211
  // hasSessionPath: there is a real `.jsonl` to hand pi via `--session`, whether
@@ -77,7 +77,7 @@ function isValidEntry(e) {
77
77
  const c = e;
78
78
  return isFiniteNonNeg(c['mtimeMs']) && isFiniteNonNeg(c['size']) && isValidWireInfo(c['info']);
79
79
  }
80
- // ---- faithful mirror of pi's private buildSessionInfo -----------------------
80
+ // faithful mirror of pi's private buildSessionInfo
81
81
  function parseLine(line) {
82
82
  if (!line.trim())
83
83
  return null;
@@ -187,7 +187,7 @@ async function buildWireSessionInfo(filePath) {
187
187
  return null;
188
188
  }
189
189
  }
190
- // ---- the cache ---------------------------------------------------------------
190
+ // the cache
191
191
  /** In-memory + file-backed cache keyed by absolute `.jsonl` path. One instance
192
192
  * per broker; the file is shared across brokers (last-writer-wins via atomic
193
193
  * rename — a lost update just costs one file's rebuild next open, never
@@ -1,5 +1,4 @@
1
1
  import { execFile } from 'node:child_process';
2
- // ---------------------------------------------------------------------------
3
2
  // Shell expansion inside Markdown bodies: `!`cmd`` inline and ```! fenced
4
3
  // blocks are replaced by the command's output.
5
4
  //
@@ -13,7 +12,6 @@ import { execFile } from 'node:child_process';
13
12
  // Node built-ins ONLY. This module is reachable from `src/commands/**`, where a
14
13
  // `@earendil-works/pi-coding-agent` import would load the whole pi runtime just
15
14
  // to read a document.
16
- // ---------------------------------------------------------------------------
17
15
  // Built fresh per use, never shared. A module-level /g regex carries mutable
18
16
  // `lastIndex`, and both `.test()` and `matchAll` (which copies it into its
19
17
  // internal clone) would make one call silently skip matches another consumed.
@@ -35,10 +35,8 @@ import { modelRequestFromConfig, unregisteredConcreteModel } from '../model-rout
35
35
  import { openModelRegistry } from './model-registry.js';
36
36
  import { MODEL_SPEC_NEXT } from './model-selection.js';
37
37
  import { initializeForkContextExposure } from '../substrate/injected-store.js';
38
- // ---------------------------------------------------------------------------
39
38
  // Shared broker-launch wrapper — catch launch/preflight failures at the call
40
39
  // site, mark the node crashed, then rethrow a clear error.
41
- // ---------------------------------------------------------------------------
42
40
  function launchBrokerOrCrash(nodeId, inv, opts) {
43
41
  try {
44
42
  return headlessBrokerHost.launch(nodeId, inv, opts);
@@ -1,4 +1,3 @@
1
- export declare function resolveStreamWatchdogMs(): number;
2
1
  /**
3
2
  * A single-timer dead-man's switch: `poke(token)` (re)arms it, `clear()`
4
3
  * disarms it, and `onStall(token)` fires exactly once if `poke()` isn't called