@north-light/crouter 0.3.232 → 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 (392) hide show
  1. package/dist/api/client.js +16 -15
  2. package/dist/builtin-memory/internal/memory-loading.md +4 -3
  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 +532 -534
  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 +0 -8
  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 +5 -18
  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/memory/__tests__/command-selector-and-mutation-guards.test.js +4 -4
  42. package/dist/commands/memory/find.js +2 -2
  43. package/dist/commands/memory/lint.js +2 -1
  44. package/dist/commands/memory/read.d.ts +2 -7
  45. package/dist/commands/memory/read.js +5 -16
  46. package/dist/commands/memory/shared.d.ts +1 -1
  47. package/dist/commands/memory/shared.js +7 -10
  48. package/dist/commands/memory/write.js +4 -3
  49. package/dist/commands/node/bash.js +2 -1
  50. package/dist/commands/node/create.js +2 -1
  51. package/dist/commands/node/inspect.js +0 -6
  52. package/dist/commands/node/lifecycle.js +4 -13
  53. package/dist/commands/node/message.js +3 -2
  54. package/dist/commands/node/subscription.js +2 -1
  55. package/dist/commands/node/wait.js +4 -3
  56. package/dist/commands/node-context.js +2 -1
  57. package/dist/commands/node-lifecycle-revive.js +0 -2
  58. package/dist/commands/node-worktree.js +2 -1
  59. package/dist/commands/pkg/browse/actions.d.ts +1 -1
  60. package/dist/commands/pkg/browse/actions.js +4 -4
  61. package/dist/commands/pkg/browse/catalog.d.ts +1 -1
  62. package/dist/commands/pkg/browse/catalog.js +15 -15
  63. package/dist/commands/pkg/browse/command-view.d.ts +1 -1
  64. package/dist/commands/pkg/browse/command-view.js +3 -3
  65. package/dist/commands/pkg/browse/detail-panel.d.ts +1 -1
  66. package/dist/commands/pkg/browse/detail-panel.js +8 -8
  67. package/dist/commands/pkg/browse/doc-view.d.ts +1 -1
  68. package/dist/commands/pkg/browse/doc-view.js +3 -3
  69. package/dist/commands/pkg/browse/list-panel.d.ts +1 -1
  70. package/dist/commands/pkg/browse/list-panel.js +3 -3
  71. package/dist/commands/pkg/browse/model.d.ts +3 -3
  72. package/dist/commands/pkg/browse/shell.d.ts +2 -2
  73. package/dist/commands/pkg/browse/shell.js +13 -13
  74. package/dist/commands/pkg/plugin-manage.js +2 -1
  75. package/dist/commands/pkg/shared.js +0 -2
  76. package/dist/commands/profile/delete.js +2 -1
  77. package/dist/commands/push.js +2 -3
  78. package/dist/commands/revive.js +0 -2
  79. package/dist/commands/surface/node/placement.js +2 -1
  80. package/dist/commands/surface-edit.js +2 -1
  81. package/dist/commands/sys/config.js +0 -4
  82. package/dist/commands/sys/daemon.js +0 -10
  83. package/dist/commands/sys/doctor.js +3 -2
  84. package/dist/commands/sys/panels/broker-limits-panel.d.ts +2 -2
  85. package/dist/commands/sys/panels/broker-limits-panel.js +4 -4
  86. package/dist/commands/sys/panels/dependencies-panel.d.ts +2 -2
  87. package/dist/commands/sys/panels/dependencies-panel.js +2 -2
  88. package/dist/commands/sys/panels/keybindings-panel.d.ts +1 -1
  89. package/dist/commands/sys/panels/keybindings-panel.js +5 -5
  90. package/dist/commands/sys/panels/models-panel.d.ts +4 -4
  91. package/dist/commands/sys/panels/models-panel.js +11 -10
  92. package/dist/commands/sys/panels/plugins-panel.d.ts +2 -2
  93. package/dist/commands/sys/panels/plugins-panel.js +2 -2
  94. package/dist/commands/sys/panels/prefs-panel.d.ts +2 -2
  95. package/dist/commands/sys/panels/prefs-panel.js +4 -4
  96. package/dist/commands/sys/panels/profiles-panel.d.ts +1 -1
  97. package/dist/commands/sys/panels/profiles-panel.js +14 -13
  98. package/dist/commands/sys/panels/provider-panel.d.ts +3 -3
  99. package/dist/commands/sys/panels/provider-panel.js +8 -8
  100. package/dist/commands/sys/settings-shell.d.ts +3 -3
  101. package/dist/commands/sys/settings-shell.js +15 -15
  102. package/dist/commands/sys/setup-settings-runner.d.ts +3 -3
  103. package/dist/commands/sys/setup-settings-runner.js +5 -5
  104. package/dist/commands/sys/setup-wizard.d.ts +4 -4
  105. package/dist/commands/sys/setup-wizard.js +8 -8
  106. package/dist/commands/sys/support/prepare.js +4 -4
  107. package/dist/commands/sys/support/submit.js +2 -2
  108. package/dist/commands/sys/sync-deps.js +0 -2
  109. package/dist/commands/sys/sync-project-guidance.js +2 -1
  110. package/dist/commands/sys/sysprompt.js +2 -1
  111. package/dist/core/__tests__/boot.test.js +0 -8
  112. package/dist/core/__tests__/broker-extension-canvas-db-boundary.test.js +23 -1
  113. package/dist/core/__tests__/canvas-db-boundary.test.d.ts +1 -0
  114. package/dist/core/__tests__/canvas-db-boundary.test.js +46 -0
  115. package/dist/core/__tests__/canvas.test.js +1 -1
  116. package/dist/core/__tests__/child-death-wake.test.js +0 -2
  117. package/dist/core/__tests__/cron-armed-context-failure.test.d.ts +1 -0
  118. package/dist/core/__tests__/cron-armed-context-failure.test.js +145 -0
  119. package/dist/core/__tests__/daemon-wedge.test.js +0 -4
  120. package/dist/core/__tests__/dead-node-policy-table.test.js +0 -4
  121. package/dist/core/__tests__/fixtures/fake-engine.js +4 -14
  122. package/dist/core/__tests__/fixtures/fake-pi-host.js +5 -5
  123. package/dist/core/__tests__/focuses.test.js +0 -6
  124. package/dist/core/__tests__/fs-utils.test.d.ts +1 -0
  125. package/dist/core/__tests__/fs-utils.test.js +50 -0
  126. package/dist/core/__tests__/helpers/harness.js +1 -1
  127. package/dist/core/__tests__/inline-memory-refs.test.js +0 -2
  128. package/dist/core/__tests__/integration/broker-fork-seam.test.js +0 -2
  129. package/dist/core/__tests__/integration/broker-sdk-wiring.test.js +3 -16
  130. package/dist/core/__tests__/integration/command-plugins.test.js +0 -24
  131. package/dist/core/__tests__/integration/deferred-no-wake.test.js +0 -6
  132. package/dist/core/__tests__/integration/flagship-lifecycle.test.js +0 -26
  133. package/dist/core/__tests__/integration/live-mutation-verbs.test.js +0 -4
  134. package/dist/core/__tests__/integration/live-mutation.test.js +1 -5
  135. package/dist/core/__tests__/integration/refresh-stall-recycle.test.js +1 -5
  136. package/dist/core/__tests__/integration/revive.test.js +0 -12
  137. package/dist/core/__tests__/integration/subscription-delivery.test.js +0 -6
  138. package/dist/core/__tests__/integration/tmux-surface.test.js +24 -8
  139. package/dist/core/__tests__/integration/worktree-reap.test.js +0 -6
  140. package/dist/core/__tests__/lifecycle.test.js +0 -18
  141. package/dist/core/__tests__/memory-resolver-precedence.test.js +1 -1
  142. package/dist/core/__tests__/migration.test.js +2 -2
  143. package/dist/core/__tests__/on-read-dedup-resume.test.js +3 -3
  144. package/dist/core/__tests__/pid-identity-match.test.js +1 -1
  145. package/dist/core/__tests__/plugin-kinds.test.js +1 -1
  146. package/dist/core/__tests__/plugin-page-components.test.js +1 -1
  147. package/dist/core/__tests__/preview-registry-sync.test.js +42 -1
  148. package/dist/core/__tests__/respawn-throttle.test.js +0 -4
  149. package/dist/core/__tests__/revive-all.test.js +5 -5
  150. package/dist/core/__tests__/revive-parked-fresh.test.js +62 -11
  151. package/dist/core/__tests__/seam/broker-attach-multiclient.test.js +0 -8
  152. package/dist/core/__tests__/seam/broker-attach-stream.test.js +0 -8
  153. package/dist/core/__tests__/seam/broker-crash-teardown.test.js +0 -2
  154. package/dist/core/__tests__/tmux-driver-boundary.test.d.ts +1 -0
  155. package/dist/core/__tests__/tmux-driver-boundary.test.js +50 -0
  156. package/dist/core/activity-signal.d.ts +7 -0
  157. package/dist/core/activity-signal.js +38 -0
  158. package/dist/core/auto-update.js +2 -1
  159. package/dist/core/bootstrap.js +2 -1
  160. package/dist/core/canvas/__tests__/remote-canvas-source.test.js +1 -1
  161. package/dist/core/canvas/__tests__/remote-transport.test.js +1 -1
  162. package/dist/core/canvas/__tests__/render-remote.test.js +1 -1
  163. package/dist/core/canvas/browse/app.js +3 -2
  164. package/dist/core/canvas/browse/model.d.ts +1 -1
  165. package/dist/core/canvas/browse/model.js +0 -4
  166. package/dist/core/canvas/canvas.d.ts +22 -0
  167. package/dist/core/canvas/canvas.js +50 -18
  168. package/dist/core/canvas/crons.js +0 -6
  169. package/dist/core/canvas/db.d.ts +14 -20
  170. package/dist/core/canvas/db.js +30 -1332
  171. package/dist/core/canvas/focuses.js +0 -4
  172. package/dist/core/canvas/history.js +2 -1
  173. package/dist/core/canvas/migrations.d.ts +20 -0
  174. package/dist/core/canvas/migrations.js +1339 -0
  175. package/dist/core/canvas/nav-model.js +1 -11
  176. package/dist/core/canvas/nav-render.js +0 -8
  177. package/dist/core/canvas/remote-canvas-source.js +0 -2
  178. package/dist/core/canvas/render-source.js +31 -13
  179. package/dist/core/command-manifests/manifest.js +0 -10
  180. package/dist/core/command-manifests/schema.js +0 -4
  181. package/dist/core/command-plugins/transport/http-invoke.js +0 -14
  182. package/dist/core/command.js +0 -4
  183. package/dist/core/config.js +2 -1
  184. package/dist/core/feed/inbox.d.ts +10 -0
  185. package/dist/core/feed/inbox.js +18 -12
  186. package/dist/core/fs-utils.d.ts +9 -2
  187. package/dist/core/fs-utils.js +7 -15
  188. package/dist/core/help.js +0 -8
  189. package/dist/core/host-exports/export.d.ts +2 -2
  190. package/dist/core/host-exports/export.js +9 -12
  191. package/dist/core/human/answer.d.ts +2 -2
  192. package/dist/core/human/convention.d.ts +2 -5
  193. package/dist/core/human/convention.js +3 -13
  194. package/dist/core/human/page-schema.d.ts +2 -2
  195. package/dist/core/human/page-schema.js +2 -2
  196. package/dist/core/human/root.js +6 -23
  197. package/dist/core/human/scan.js +24 -7
  198. package/dist/core/human/tickets.d.ts +4 -1
  199. package/dist/core/human/tickets.js +20 -6
  200. package/dist/core/io.js +0 -12
  201. package/dist/core/memory/identity.js +0 -4
  202. package/dist/core/memory/inline-ref-inventory.js +3 -2
  203. package/dist/core/memory/lint.js +3 -6
  204. package/dist/core/memory-resolver.d.ts +5 -0
  205. package/dist/core/memory-resolver.js +14 -12
  206. package/dist/core/pagination.js +0 -2
  207. package/dist/core/preview-registry.d.ts +14 -0
  208. package/dist/core/preview-registry.js +2 -4
  209. package/dist/core/preview-result-path.js +2 -1
  210. package/dist/core/profiles/manifest.d.ts +37 -5
  211. package/dist/core/profiles/manifest.js +44 -24
  212. package/dist/core/profiles/select.js +0 -4
  213. package/dist/core/review/signal.js +4 -23
  214. package/dist/core/review/ticket-filter.d.ts +3 -3
  215. package/dist/core/review/ticket-filter.js +4 -9
  216. package/dist/core/runtime/__tests__/engine-route.test.js +1 -1
  217. package/dist/core/runtime/__tests__/spawn-env.test.js +0 -8
  218. package/dist/core/runtime/bearings-render.d.ts +56 -0
  219. package/dist/core/runtime/bearings-render.js +241 -0
  220. package/dist/core/runtime/bearings.d.ts +2 -17
  221. package/dist/core/runtime/bearings.js +12 -198
  222. package/dist/core/runtime/broker/auth-reload.d.ts +39 -0
  223. package/dist/core/runtime/broker/auth-reload.js +149 -0
  224. package/dist/core/runtime/broker/engine-drive.d.ts +26 -0
  225. package/dist/core/runtime/broker/engine-drive.js +257 -0
  226. package/dist/core/runtime/broker/engine-routing.d.ts +143 -0
  227. package/dist/core/runtime/broker/engine-routing.js +211 -0
  228. package/dist/core/runtime/broker/event-projection.js +3 -2
  229. package/dist/core/runtime/broker/fault-retry.js +5 -4
  230. package/dist/core/runtime/broker/frame-client.d.ts +38 -0
  231. package/dist/core/runtime/broker/frame-client.js +129 -0
  232. package/dist/core/runtime/broker/frame-dispatch.d.ts +12 -189
  233. package/dist/core/runtime/broker/frame-dispatch.js +89 -1476
  234. package/dist/core/runtime/broker/frame-engine-control.d.ts +20 -0
  235. package/dist/core/runtime/broker/frame-engine-control.js +231 -0
  236. package/dist/core/runtime/broker/frame-memory-refs.d.ts +19 -0
  237. package/dist/core/runtime/broker/frame-memory-refs.js +41 -0
  238. package/dist/core/runtime/broker/frame-model-query.d.ts +13 -0
  239. package/dist/core/runtime/broker/frame-model-query.js +69 -0
  240. package/dist/core/runtime/broker/frame-read.d.ts +19 -0
  241. package/dist/core/runtime/broker/frame-read.js +116 -0
  242. package/dist/core/runtime/broker/frame-replies.d.ts +24 -0
  243. package/dist/core/runtime/broker/frame-replies.js +64 -0
  244. package/dist/core/runtime/broker/frame-session.d.ts +34 -0
  245. package/dist/core/runtime/broker/frame-session.js +203 -0
  246. package/dist/core/runtime/broker/turn-ignition.d.ts +42 -0
  247. package/dist/core/runtime/broker/turn-ignition.js +74 -0
  248. package/dist/core/runtime/broker-extension-render.js +9 -85
  249. package/dist/core/runtime/broker-protocol.js +0 -4
  250. package/dist/core/runtime/broker-sdk.js +2 -1
  251. package/dist/core/runtime/broker.d.ts +1 -1
  252. package/dist/core/runtime/broker.js +5 -23
  253. package/dist/core/runtime/fault.js +3 -2
  254. package/dist/core/runtime/headless-pi.js +3 -2
  255. package/dist/core/runtime/host.js +2 -3
  256. package/dist/core/runtime/kickoff.js +0 -4
  257. package/dist/core/runtime/launch-target.js +2 -1
  258. package/dist/core/runtime/launch.js +3 -6
  259. package/dist/core/runtime/lifecycle.js +17 -26
  260. package/dist/core/runtime/managed-provider-cooling.d.ts +1 -1
  261. package/dist/core/runtime/naming.js +0 -2
  262. package/dist/core/runtime/nodes.js +2 -1
  263. package/dist/core/runtime/persona.js +0 -6
  264. package/dist/core/runtime/placement-tmux.js +2 -5
  265. package/dist/core/runtime/placement.js +4 -11
  266. package/dist/core/runtime/recap.js +4 -3
  267. package/dist/core/runtime/reopen.js +4 -7
  268. package/dist/core/runtime/reset.js +0 -4
  269. package/dist/core/runtime/resume-root.js +0 -2
  270. package/dist/core/runtime/revive.js +17 -13
  271. package/dist/core/runtime/session-list-cache.js +2 -2
  272. package/dist/core/runtime/shell-expansion.js +0 -2
  273. package/dist/core/runtime/spawn.js +0 -2
  274. package/dist/core/runtime/stream-watchdog.d.ts +0 -1
  275. package/dist/core/runtime/stream-watchdog.js +13 -18
  276. package/dist/core/runtime/tmux-bindings.js +0 -2
  277. package/dist/core/runtime/tmux-driver.d.ts +12 -0
  278. package/dist/core/runtime/tmux-driver.js +18 -13
  279. package/dist/core/runtime/warm-pool.js +0 -4
  280. package/dist/core/scope.js +4 -3
  281. package/dist/core/spawn.js +2 -3
  282. package/dist/core/subscription-state.js +10 -17
  283. package/dist/core/substrate/__tests__/surface-match-pre-command.test.d.ts +1 -0
  284. package/dist/core/substrate/__tests__/surface-match-pre-command.test.js +92 -0
  285. package/dist/core/substrate/frontmatter-validation.js +1 -1
  286. package/dist/core/substrate/injected-store.d.ts +6 -0
  287. package/dist/core/substrate/injected-store.js +24 -0
  288. package/dist/core/substrate/on-read.d.ts +17 -1
  289. package/dist/core/substrate/on-read.js +36 -2
  290. package/dist/core/substrate/render.js +0 -12
  291. package/dist/core/substrate/schema.d.ts +3 -3
  292. package/dist/core/substrate/schema.js +4 -16
  293. package/dist/core/substrate/surface-match.d.ts +19 -0
  294. package/dist/core/substrate/surface-match.js +60 -2
  295. package/dist/core/termrender/display.js +2 -1
  296. package/dist/core/tui/page-host.d.ts +3 -3
  297. package/dist/core/tui/page-host.js +4 -4
  298. package/dist/core/tui/panel.js +3 -3
  299. package/dist/core/wake.js +0 -12
  300. package/dist/core/worktree.js +0 -2
  301. package/dist/daemon/__tests__/reconciler-signature.test.d.ts +1 -0
  302. package/dist/daemon/__tests__/reconciler-signature.test.js +36 -0
  303. package/dist/daemon/api/__tests__/seam/api-server.test.js +2 -16
  304. package/dist/daemon/api/__tests__/seam/b10-attach-modelauth.test.js +3 -11
  305. package/dist/daemon/api/__tests__/seam/leaf-api-parity.test.js +0 -6
  306. package/dist/daemon/api/handlers/attach.js +2 -1
  307. package/dist/daemon/api/handlers/broker-ops.js +3 -6
  308. package/dist/daemon/api/handlers/canvas.js +12 -18
  309. package/dist/daemon/api/handlers/feedback-comments.js +0 -8
  310. package/dist/daemon/api/handlers/health.js +3 -2
  311. package/dist/daemon/api/handlers/human-requests.js +0 -10
  312. package/dist/daemon/api/handlers/human.js +1 -5
  313. package/dist/daemon/api/handlers/inbox.js +0 -18
  314. package/dist/daemon/api/handlers/memory.js +1 -1
  315. package/dist/daemon/api/handlers/messages.js +4 -8
  316. package/dist/daemon/api/handlers/nodes.js +10 -21
  317. package/dist/daemon/api/handlers/reports.js +1 -1
  318. package/dist/daemon/api/handlers/reviews.js +1 -6
  319. package/dist/daemon/api/map.js +0 -6
  320. package/dist/daemon/api/router.js +0 -4
  321. package/dist/daemon/api/server.js +2 -5
  322. package/dist/daemon/boot-migrations.d.ts +14 -0
  323. package/dist/daemon/boot-migrations.js +31 -0
  324. package/dist/daemon/cron/armed-context.d.ts +21 -0
  325. package/dist/daemon/cron/armed-context.js +100 -0
  326. package/dist/daemon/cron/capture.d.ts +20 -0
  327. package/dist/daemon/cron/capture.js +45 -0
  328. package/dist/daemon/cron/disposition.d.ts +19 -0
  329. package/dist/daemon/cron/disposition.js +52 -0
  330. package/dist/daemon/cron/escalation.d.ts +12 -0
  331. package/dist/daemon/cron/escalation.js +124 -0
  332. package/dist/daemon/cron/held.d.ts +4 -0
  333. package/dist/daemon/cron/held.js +20 -0
  334. package/dist/daemon/cron/lease-recovery.d.ts +3 -0
  335. package/dist/daemon/cron/lease-recovery.js +119 -0
  336. package/dist/daemon/cron/live-runs.d.ts +11 -0
  337. package/dist/daemon/cron/live-runs.js +23 -0
  338. package/dist/daemon/cron/passes.d.ts +11 -0
  339. package/dist/daemon/cron/passes.js +78 -0
  340. package/dist/daemon/cron/settle.d.ts +28 -0
  341. package/dist/daemon/cron/settle.js +37 -0
  342. package/dist/daemon/cron/sinks.d.ts +2 -0
  343. package/dist/daemon/cron/sinks.js +100 -0
  344. package/dist/daemon/cron-run.d.ts +5 -41
  345. package/dist/daemon/cron-run.js +193 -882
  346. package/dist/daemon/crtrd-cli.js +2 -1
  347. package/dist/daemon/crtrd.js +22 -22
  348. package/dist/daemon/fleet.js +0 -4
  349. package/dist/daemon/human/deliver-action.js +2 -1
  350. package/dist/daemon/human/sweep.js +15 -4
  351. package/dist/daemon/manage.d.ts +2 -2
  352. package/dist/daemon/manage.js +4 -13
  353. package/dist/daemon/pidfile.js +2 -2
  354. package/dist/daemon/reconcilers/broker-supervision.js +26 -29
  355. package/dist/daemon/reconcilers/dormant-inbox.d.ts +1 -1
  356. package/dist/daemon/reconcilers/dormant-inbox.js +2 -1
  357. package/dist/daemon/reconcilers/live-obligation.js +2 -2
  358. package/dist/daemon/reconcilers/pending-review-submit.d.ts +1 -1
  359. package/dist/daemon/reconcilers/pending-review-submit.js +2 -1
  360. package/dist/daemon/reconcilers/storage-maintenance.d.ts +2 -1
  361. package/dist/daemon/reconcilers/storage-maintenance.js +2 -1
  362. package/dist/daemon/review/companion.js +2 -2
  363. package/dist/daemon/review/sweep.js +8 -3
  364. package/dist/migrations/001-surfaces-frontmatter.js +0 -6
  365. package/dist/migrations/corpus.js +2 -5
  366. package/dist/migrations/registry.js +15 -2
  367. package/dist/pi-extensions/__tests__/canvas-stophook-agentend.test.js +0 -4
  368. package/dist/pi-extensions/__tests__/pre-command-gate.test.d.ts +1 -0
  369. package/dist/pi-extensions/__tests__/pre-command-gate.test.js +220 -0
  370. package/dist/pi-extensions/broker-local.js +2 -1
  371. package/dist/pi-extensions/canvas-bash-valve.js +3 -17
  372. package/dist/pi-extensions/canvas-context-intro.js +2 -7
  373. package/dist/pi-extensions/canvas-doc-substrate.d.ts +14 -0
  374. package/dist/pi-extensions/canvas-doc-substrate.js +77 -5
  375. package/dist/pi-extensions/canvas-goal-capture.js +2 -1
  376. package/dist/pi-extensions/canvas-inbox-watcher.js +5 -12
  377. package/dist/pi-extensions/canvas-passive-context.js +2 -5
  378. package/dist/pi-extensions/canvas-preview-result.js +2 -1
  379. package/dist/pi-extensions/canvas-prompt-scrub.js +2 -1
  380. package/dist/pi-extensions/canvas-recap.js +3 -16
  381. package/dist/pi-extensions/canvas-review-boundary.js +2 -5
  382. package/dist/pi-extensions/canvas-stophook.js +2 -26
  383. package/dist/pi-extensions/canvas-structured-output.js +2 -1
  384. package/dist/pi-extensions/canvas-tool-guide.js +2 -1
  385. package/dist/shared/__tests__/env-boundary.test.d.ts +1 -0
  386. package/dist/shared/__tests__/env-boundary.test.js +93 -0
  387. package/dist/shared/env.d.ts +68 -0
  388. package/dist/shared/env.js +171 -0
  389. package/package.json +3 -1
  390. package/runtime.lock.json +2 -2
  391. package/dist/core/canvas/render.d.ts +0 -47
  392. package/dist/core/canvas/render.js +0 -245
@@ -11,6 +11,7 @@
11
11
  // trigger, so the agent cannot run on context alone before hearing the person.
12
12
  // A context-only batch keeps its wake by triggering on its LAST card while idle,
13
13
  // and rides the running turn while streaming.
14
+ import { envNodeId, envProfileId, envWatcherDebounceMs, envWatcherTickMs } from '../shared/env.js';
14
15
  import { coalesceBrokerInbox, entryCards, readBrokerCanceledEntryIds, readBrokerCursor, readBrokerInboxSince, reportRefNodeId, } from '../core/runtime/broker/inbox.js';
15
16
  import { advanceBrokerInboxCursor, brokerExtensionState } from '../core/runtime/broker/daemon-ops.js';
16
17
  import { emitEvent } from '../core/events/emit.js';
@@ -18,8 +19,6 @@ import { errorClassFromError } from '../core/events/errors.js';
18
19
  import { operationIdContext } from '../core/events/operation-id.js';
19
20
  import { situationalContextEnvelope, SITUATIONAL_CONTEXT_CUSTOM_TYPE } from '../core/runtime/situational-context.js';
20
21
  import { isProfilePaused } from '../core/profiles/manifest.js';
21
- const DEFAULT_TICK_MS = 800;
22
- const DEFAULT_DEBOUNCE_MS = 1000;
23
22
  const MAX_PENDING_HANDOFFS = 64;
24
23
  const MAX_SOURCE_OPERATION_IDS = 16;
25
24
  let currentWatcher;
@@ -31,26 +30,20 @@ class WatcherFatalError extends Error {
31
30
  }
32
31
  }
33
32
  function resolveCadence() {
34
- const parse = (raw, fallback) => {
35
- if (raw === undefined)
36
- return fallback;
37
- const value = Number(raw);
38
- return Number.isFinite(value) && value > 0 ? value : fallback;
39
- };
40
33
  return {
41
- tickMs: parse(process.env['CRTR_WATCHER_TICK_MS'], DEFAULT_TICK_MS),
42
- debounceMs: parse(process.env['CRTR_WATCHER_DEBOUNCE_MS'], DEFAULT_DEBOUNCE_MS),
34
+ tickMs: envWatcherTickMs(),
35
+ debounceMs: envWatcherDebounceMs(),
43
36
  };
44
37
  }
45
38
  function currentNodeId() {
46
- const nodeId = process.env['CRTR_NODE_ID'];
39
+ const nodeId = envNodeId();
47
40
  return nodeId === undefined || nodeId.trim() === '' ? undefined : nodeId;
48
41
  }
49
42
  /** The node's profile, read fresh each poll: `crtr profile pause` mutates the
50
43
  * manifest file directly, so a live broker sees the flip on its next tick with
51
44
  * no daemon round-trip. '' is the historical no-profile row. */
52
45
  function profilePaused() {
53
- const profileId = process.env['CRTR_PROFILE_ID'];
46
+ const profileId = envProfileId();
54
47
  return profileId !== undefined && profileId !== '' && isProfilePaused(profileId);
55
48
  }
56
49
  function isFilesystemError(error) {
@@ -18,6 +18,7 @@
18
18
  //
19
19
  // Plain TS-with-types — no imports from @earendil-works/* so this compiles inside
20
20
  // crouter's own tsc build without a dep on the pi packages.
21
+ import { envNodeId } from '../shared/env.js';
21
22
  import { existsSync, readFileSync } from 'node:fs';
22
23
  import { brokerExtensionState } from '../core/runtime/broker/daemon-ops.js';
23
24
  import { reportPathFromRef } from '../core/runtime/broker-extension-render.js';
@@ -26,9 +27,7 @@ import { parseCard } from '../shared/generated-context.js';
26
27
  // Per-entry body cap so a single fat report can't blow the context budget. The
27
28
  // full report stays on disk at `ref` if the agent needs more.
28
29
  const BODY_CAP = 4_000;
29
- // ---------------------------------------------------------------------------
30
30
  // Report dereference — turn a passive pointer into the message text it carries.
31
- // ---------------------------------------------------------------------------
32
31
  /** Strip the leading YAML frontmatter block a report is written with, returning
33
32
  * just the body. Tolerant: no frontmatter → returns the input unchanged. */
34
33
  function stripFrontmatter(raw) {
@@ -126,9 +125,7 @@ export function placeBacklog(text, preText) {
126
125
  const bodyStart = text.length - card.body.length - CARD_CLOSE.length;
127
126
  return `${text.slice(0, bodyStart)}${preText}\n${text.slice(bodyStart)}`;
128
127
  }
129
- // ---------------------------------------------------------------------------
130
128
  // Extension
131
- // ---------------------------------------------------------------------------
132
129
  /**
133
130
  * Register the passive-context drain on `pi`.
134
131
  *
@@ -138,7 +135,7 @@ export function placeBacklog(text, preText) {
138
135
  export function registerCanvasPassiveContext(pi) {
139
136
  pi.on('input', async (event) => {
140
137
  try {
141
- const nodeId = process.env['CRTR_NODE_ID'];
138
+ const nodeId = envNodeId();
142
139
  if (nodeId === undefined || nodeId.trim() === '')
143
140
  return; // not a canvas node
144
141
  const drained = drainBrokerPassive(nodeId);
@@ -6,6 +6,7 @@
6
6
  // mirrors its record there, and this hook attaches the record to Pi's
7
7
  // ToolResult.details. The attach viewer can render a structured preview
8
8
  // without parsing the agent-facing text.
9
+ import { envNodeId } from '../shared/env.js';
9
10
  import { existsSync, readFileSync, rmSync } from 'node:fs';
10
11
  import { previewResultPath } from '../core/preview-result-path.js';
11
12
  function isPreviewRecord(value) {
@@ -19,7 +20,7 @@ function isPreviewRecord(value) {
19
20
  /** Loaded into every canvas broker. Plain Pi sessions have no node id and
20
21
  * deliberately receive no filesystem transport. */
21
22
  export function registerCanvasPreviewResult(pi) {
22
- if (process.env['CRTR_NODE_ID'] === undefined || process.env['CRTR_NODE_ID'] === '')
23
+ if (envNodeId() === undefined || envNodeId() === '')
23
24
  return;
24
25
  pi.on('tool_result', (event) => {
25
26
  if (event.toolName !== 'bash')
@@ -23,6 +23,7 @@
23
23
  //
24
24
  // Every rule below is anchored to pi's exact wording and degrades to a no-op if
25
25
  // pi rewords, so a pi upgrade can never break a turn here.
26
+ import { envNodeId } from '../shared/env.js';
26
27
  const PI_IDENTITY = 'You are an expert coding assistant operating inside pi, a coding agent harness.';
27
28
  const NEUTRAL_IDENTITY = 'You are an expert coding assistant.';
28
29
  const PI_DOCS_MARKER = 'Pi documentation (read only';
@@ -40,7 +41,7 @@ export function scrubPromptSelfReference(systemPrompt) {
40
41
  }
41
42
  /** Register the system-prompt scrub for a canvas node. */
42
43
  export function registerCanvasPromptScrub(pi) {
43
- const nodeId = process.env['CRTR_NODE_ID'];
44
+ const nodeId = envNodeId();
44
45
  if (nodeId === undefined || nodeId.trim() === '')
45
46
  return;
46
47
  pi.on('before_agent_start', (event) => {
@@ -38,20 +38,17 @@
38
38
  //
39
39
  // Plain TS-with-types — no imports from @earendil-works/* so this compiles
40
40
  // inside crouter's own tsc build without a dep on the pi packages.
41
+ import { envNodeId, envRecapIdleMs } from '../shared/env.js';
41
42
  import { brokerExtensionState } from '../core/runtime/broker/daemon-ops.js';
42
43
  import { generateRecap } from '../core/runtime/recap.js';
43
44
  import { isReviewBoundary } from '../core/runtime/session-visibility.js';
44
45
  import { editorLabelForBrokerNode } from '../core/runtime/broker-extension-render.js';
45
46
  import { generateAndCommitRecapName, readRoadmap } from './broker-local.js';
46
- // ---------------------------------------------------------------------------
47
47
  // Tuning constants
48
- // ---------------------------------------------------------------------------
49
48
  /** No-new-message window before a recap is shown. 60s by default; override with
50
49
  * CRTR_RECAP_IDLE_MS (for fast runtime verification). */
51
50
  function idleMs() {
52
- const raw = process.env['CRTR_RECAP_IDLE_MS'];
53
- const n = raw !== undefined ? parseInt(raw, 10) : NaN;
54
- return Number.isFinite(n) && n > 0 ? n : 60_000;
51
+ return envRecapIdleMs();
55
52
  }
56
53
  /** How often the single low-rate timer checks elapsed-since-last-message. */
57
54
  const POLL_MS = 7_000;
@@ -62,14 +59,10 @@ const CONVO_EDGE_MESSAGES = 5;
62
59
  * (10 messages × this ≈ the total budget the model needs for the gist). */
63
60
  const MESSAGE_CHAR_BUDGET = 1_200;
64
61
  const WIDGET_KEY = 'crtr-recap';
65
- // ---------------------------------------------------------------------------
66
62
  // Module-level state — persists across /reload so the timer doesn't stack.
67
- // ---------------------------------------------------------------------------
68
63
  /** The one live poll timer. Cleared and replaced on every re-registration. */
69
64
  let liveTimer;
70
- // ---------------------------------------------------------------------------
71
65
  // Conversation extraction — literal user+assistant text ONLY.
72
- // ---------------------------------------------------------------------------
73
66
  /** Pull the literal text out of a message's content (string or text blocks),
74
67
  * dropping non-text blocks (thinking, toolCall, image). */
75
68
  function messageText(msg) {
@@ -162,9 +155,7 @@ function buildNamingConversation(sm, assistantPartial, reviewId) {
162
155
  });
163
156
  return sampled.join('\n\n');
164
157
  }
165
- // ---------------------------------------------------------------------------
166
158
  // Extension
167
- // ---------------------------------------------------------------------------
168
159
  /**
169
160
  * Register the inactivity-recap chrome on `pi`.
170
161
  *
@@ -172,7 +163,7 @@ function buildNamingConversation(sm, assistantPartial, reviewId) {
172
163
  * or legacy job agent loads it as a no-op.
173
164
  */
174
165
  export function registerCanvasRecap(pi, recapGenerator = generateRecap, nameRegenerator) {
175
- const nodeId = process.env['CRTR_NODE_ID'];
166
+ const nodeId = envNodeId();
176
167
  if (nodeId === undefined || nodeId.trim() === '')
177
168
  return; // not a canvas node
178
169
  // Captured on session_start; used by the timer and the message handlers.
@@ -344,9 +335,7 @@ export function registerCanvasRecap(pi, recapGenerator = generateRecap, nameRege
344
335
  requestRecap(true);
345
336
  },
346
337
  });
347
- // -------------------------------------------------------------------------
348
338
  // Events
349
- // -------------------------------------------------------------------------
350
339
  pi.on('session_start', (_event, ctx) => {
351
340
  ui = ctx.ui;
352
341
  sessionManager = ctx.sessionManager;
@@ -382,9 +371,7 @@ export function registerCanvasRecap(pi, recapGenerator = generateRecap, nameRege
382
371
  captureLiveModel(ctx);
383
372
  onMessage();
384
373
  });
385
- // -------------------------------------------------------------------------
386
374
  // The single low-rate idle poll.
387
- // -------------------------------------------------------------------------
388
375
  if (liveTimer !== undefined)
389
376
  clearInterval(liveTimer);
390
377
  const timer = setInterval(() => {
@@ -4,13 +4,12 @@
4
4
  // inherited fork context and before canvas-context-intro appends fork bearings.
5
5
  // The display projection starts at this marker while the model keeps the full
6
6
  // inherited context.
7
+ import { envNodeId } from '../shared/env.js';
7
8
  import { brokerExtensionState } from '../core/runtime/broker/daemon-ops.js';
8
9
  import { isReviewBoundary, REVIEW_BOUNDARY_CUSTOM_TYPE } from '../core/runtime/session-visibility.js';
9
10
  import { formatCard, parseCard } from '../shared/generated-context.js';
10
11
  import { truncateToWidth } from './truncate.js';
11
- // ---------------------------------------------------------------------------
12
12
  // Collapsed-by-default rendering
13
- // ---------------------------------------------------------------------------
14
13
  /** Render the marker as a single compact divider; its full content remains in
15
14
  * the message list for the model and generated-context presenters. */
16
15
  export function renderReviewBoundary(message, options, theme) {
@@ -32,9 +31,7 @@ export function renderReviewBoundary(message, options, theme) {
32
31
  },
33
32
  };
34
33
  }
35
- // ---------------------------------------------------------------------------
36
34
  // Extension
37
- // ---------------------------------------------------------------------------
38
35
  /**
39
36
  * Register the review boundary marker. Companion nodes receive one boundary
40
37
  * marker on session_start that separates inherited context from the visible
@@ -43,7 +40,7 @@ export function renderReviewBoundary(message, options, theme) {
43
40
  export function registerCanvasReviewBoundary(pi) {
44
41
  pi.registerMessageRenderer(REVIEW_BOUNDARY_CUSTOM_TYPE, renderReviewBoundary);
45
42
  pi.on('session_start', async (_event, ctx) => {
46
- const nodeId = process.env['CRTR_NODE_ID'];
43
+ const nodeId = envNodeId();
47
44
  if (!nodeId || nodeId.trim() === '')
48
45
  return;
49
46
  let node;
@@ -20,6 +20,7 @@
20
20
  //
21
21
  // Plain TS-with-types — no imports from @earendil-works/* so this compiles inside
22
22
  // crouter's own tsc build without a dep on the pi packages.
23
+ import { envNodeId } from '../shared/env.js';
23
24
  import { writeFileSync, mkdirSync, existsSync, readFileSync } from 'node:fs';
24
25
  import { join } from 'node:path';
25
26
  import { contextDir, jobDir } from '../core/canvas/paths.js';
@@ -79,9 +80,7 @@ function flushTelemetry(jobDirPath, tokensIn, tokensOut, model, contextTokens, a
79
80
  /* telemetry is best-effort; never surface */
80
81
  }
81
82
  }
82
- // ---------------------------------------------------------------------------
83
83
  // Message-extraction helpers
84
- // ---------------------------------------------------------------------------
85
84
  /** Walk backwards through the agent_end messages array to find the last
86
85
  * assistant turn. */
87
86
  function lastAssistantMessage(messages) {
@@ -117,7 +116,6 @@ function currentReviewBoundaryIds(ctx) {
117
116
  return [];
118
117
  }
119
118
  }
120
- // ---------------------------------------------------------------------------
121
119
  // Context-size steering bands — a single shared schedule of thresholds that
122
120
  // ESCALATE in tone as input context grows. The first band is a gentle
123
121
  // "consider it"; a later band turns firm. The schedule TIGHTENS as it climbs:
@@ -150,7 +148,6 @@ function currentReviewBoundaryIds(ctx) {
150
148
  // worthwhile parallel work. Only a terminal node is ever told `push final` (a
151
149
  // resident finishes by yielding or being closed); only an orchestrator already
152
150
  // HAS a roadmap to checkpoint before it yields.
153
- // ---------------------------------------------------------------------------
154
151
  const STEER_STEP = 10_000;
155
152
  // Shared escalation schedule (both lifecycles). Tightens as it climbs:
156
153
  // 130k, 150k, 170k, 185k, 200k, then every 10k (210k, 220k, …).
@@ -233,9 +230,7 @@ export function steerNote(at, lifecycle, mode) {
233
230
  }
234
231
  return `Context ~${k}k — this is getting long. Wrap up now before this context overflows: \`crtr push final\` if you're close, otherwise \`crtr node yield\` immediately to continue in a fresh window against your goal and your note.`;
235
232
  }
236
- // ---------------------------------------------------------------------------
237
233
  // Extension
238
- // ---------------------------------------------------------------------------
239
234
  /**
240
235
  * Register the canvas turn and settlement handlers on `pi`.
241
236
  *
@@ -245,7 +240,7 @@ export function steerNote(at, lifecycle, mode) {
245
240
  * per node lifecycle, matching how pi loads extensions).
246
241
  */
247
242
  export function registerCanvasStophook(pi) {
248
- const nodeId = process.env['CRTR_NODE_ID'];
243
+ const nodeId = envNodeId();
249
244
  if (nodeId === undefined || nodeId.trim() === '')
250
245
  return; // not a canvas node
251
246
  const jobDirPath = jobDir(nodeId);
@@ -310,7 +305,6 @@ export function registerCanvasStophook(pi) {
310
305
  touchBusy(nodeId);
311
306
  clearFault(nodeId, { link: 'daemon→node' });
312
307
  };
313
- // ---------------------------------------------------------------------------
314
308
  // session_start — capture pi's session id, and detect `/new`.
315
309
  //
316
310
  // pi tags each session_start with a `reason`: 'startup' on boot (a fresh
@@ -327,7 +321,6 @@ export function registerCanvasStophook(pi) {
327
321
  //
328
322
  // `/new` splits by who ran it: crtrd derives rootness from the node row and
329
323
  // either directs a root relaunch or resets a child's session coordinates.
330
- // ---------------------------------------------------------------------------
331
324
  pi.on('session_start', async (event, ctx) => {
332
325
  try {
333
326
  const id = ctx?.sessionManager?.getSessionId?.();
@@ -390,11 +383,9 @@ export function registerCanvasStophook(pi) {
390
383
  });
391
384
  }
392
385
  });
393
- // ---------------------------------------------------------------------------
394
386
  // agent_start — pi entered a turn. Mark the node mid-turn (busy) so a
395
387
  // focus-away while it is genuinely working backstages it (Invariant F2)
396
388
  // rather than reaping it. Cleared only after the whole run settles.
397
- // ---------------------------------------------------------------------------
398
389
  pi.on('agent_start', () => {
399
390
  // A fresh turn started. Keep any active fault marker in place so the daemon's
400
391
  // retry accounting survives a failed retry cycle; just mark the turn busy.
@@ -403,7 +394,6 @@ export function registerCanvasStophook(pi) {
403
394
  }
404
395
  catch { /* best-effort */ }
405
396
  });
406
- // ---------------------------------------------------------------------------
407
397
  // input — a new user-directed run is beginning. This is the ONLY place stop
408
398
  // signals are cleared: `agent_start` re-fires for pi's automatic retries and
409
399
  // post-compaction continuations within the SAME run, so clearing there would
@@ -413,18 +403,15 @@ export function registerCanvasStophook(pi) {
413
403
  // `agent_settled` consumes them; a leftover from an errored or interrupted run
414
404
  // of this same broker is discarded here, when the next prompt arrives (the pid
415
405
  // stamp only rules out a PRIOR broker's markers, not this broker's own).
416
- // ---------------------------------------------------------------------------
417
406
  pi.on('input', () => {
418
407
  try {
419
408
  clearStopSignals(nodeId);
420
409
  }
421
410
  catch { /* best-effort */ }
422
411
  });
423
- // ---------------------------------------------------------------------------
424
412
  // tool_execution_start — capture the live "what is it doing" cue. Writes the
425
413
  // one-line tool summary to telemetry.json so out-of-process readers (the nav
426
414
  // chrome) show it inline without a second file or any extra read on their side.
427
- // ---------------------------------------------------------------------------
428
415
  pi.on('tool_execution_start', (event) => {
429
416
  try {
430
417
  lastActivity = summarizeActivity(String(event?.toolName ?? ''), event?.args);
@@ -434,7 +421,6 @@ export function registerCanvasStophook(pi) {
434
421
  }
435
422
  catch { /* best-effort */ }
436
423
  });
437
- // ---------------------------------------------------------------------------
438
424
  // tool_execution_update — wedge heartbeat + fault clear only. Fired with
439
425
  // partial/streaming tool output (when a tool reports progress); throttled via
440
426
  // `heartbeat()` since it can arrive many times per second. Most tools (e.g.
@@ -442,27 +428,23 @@ export function registerCanvasStophook(pi) {
442
428
  // for them — it exists so any tool that DOES stream partial output (now or in
443
429
  // the future) counts as live progress instead of tripping the wedge detector,
444
430
  // AND clears any wedge notice that already fired before this progress resumed.
445
- // ---------------------------------------------------------------------------
446
431
  pi.on('tool_execution_update', () => {
447
432
  try {
448
433
  heartbeat();
449
434
  }
450
435
  catch { /* best-effort */ }
451
436
  });
452
- // ---------------------------------------------------------------------------
453
437
  // message_update — wedge heartbeat + fault clear only. Fired token-by-token
454
438
  // while the assistant is actively streaming a reply — genuine progress,
455
439
  // throttled the same way, and equally clears a stale wedge notice if streaming
456
440
  // resumes before a coarser boundary. A node stuck streaming forever without
457
441
  // ever firing this is exactly the daemon's busy+quiet wedge signature.
458
- // ---------------------------------------------------------------------------
459
442
  pi.on('message_update', () => {
460
443
  try {
461
444
  heartbeat();
462
445
  }
463
446
  catch { /* best-effort */ }
464
447
  });
465
- // ---------------------------------------------------------------------------
466
448
  // tool_execution_end — IMMEDIATE refresh-yield. When a tool this turn set
467
449
  // intent='refresh' (the node ran `crtr node yield`), shut the broker down NOW
468
450
  // instead of letting the model keep burning its bloated context to the turn's
@@ -494,11 +476,9 @@ export function registerCanvasStophook(pi) {
494
476
  }
495
477
  catch { /* best-effort */ }
496
478
  });
497
- // ---------------------------------------------------------------------------
498
479
  // session_shutdown — Pi's clean /quit fact goes to crtrd, which owns the
499
480
  // finish transition, focus teardown, presence clear, and fault clear. Other
500
481
  // session swaps keep the same node and need no durable settlement.
501
- // ---------------------------------------------------------------------------
502
482
  pi.on('session_shutdown', async (event, ctx) => {
503
483
  clearBusy(nodeId); // turn marker is meaningless once Pi is exiting
504
484
  if (event?.reason !== 'quit')
@@ -525,10 +505,8 @@ export function registerCanvasStophook(pi) {
525
505
  if (typeof msg.model === 'string' && msg.model !== '')
526
506
  model = msg.model;
527
507
  };
528
- // ---------------------------------------------------------------------------
529
508
  // turn_end — live telemetry refresh.
530
509
  // event shape: { message: AssistantMessage, ... }
531
- // ---------------------------------------------------------------------------
532
510
  pi.on('turn_end', async (event, ctx) => {
533
511
  accumulate(event?.message);
534
512
  // The CURRENT context size via ctx.getContextUsage() — the exact figure pi's
@@ -615,10 +593,8 @@ export function registerCanvasStophook(pi) {
615
593
  }
616
594
  catch { /* best-effort */ }
617
595
  });
618
- // ---------------------------------------------------------------------------
619
596
  // agent_settled — Pi supplies stop facts; crtrd commits every canvas effect
620
597
  // and returns the one Pi-side consequence to enact.
621
- // ---------------------------------------------------------------------------
622
598
  pi.on('agent_settled', async (_event, ctx) => {
623
599
  const event = stagedAgentEnd;
624
600
  stagedAgentEnd = null;
@@ -3,6 +3,7 @@
3
3
  // Loaded into every canvas node. Inert unless nodes/<id>/output-schema.json exists
4
4
  // and is valid. If the request file is malformed, the tool is not registered — the
5
5
  // stop-guard will reprompt the node with an explicit error about the invalid file.
6
+ import { envNodeId } from '../shared/env.js';
6
7
  import { mkdirSync, writeFileSync } from 'node:fs';
7
8
  import { dirname } from 'node:path';
8
9
  import { validateToolArguments } from '@earendil-works/pi-ai';
@@ -168,7 +169,7 @@ function toolDescription(request) {
168
169
  return 'Submit the requested one-off structured result. Calling this records the result, pushes an update report automatically, frees you from the structured-output gate, and lets you continue working.';
169
170
  }
170
171
  export function registerCanvasStructuredOutput(pi) {
171
- const nodeId = process.env['CRTR_NODE_ID'];
172
+ const nodeId = envNodeId();
172
173
  if (nodeId === undefined || nodeId.trim() === '')
173
174
  return;
174
175
  let registeredKey = null;
@@ -3,6 +3,7 @@
3
3
  // Capturing `crtr -h` builds the full configured command tree, including commands
4
4
  // discovered from the node's cwd. It runs only at session start; before_agent_start
5
5
  // injects the last successful capture without starting work or waiting.
6
+ import { envNodeId } from '../shared/env.js';
6
7
  import { execFile } from 'node:child_process';
7
8
  import { createHash } from 'node:crypto';
8
9
  import { mkdirSync, readFileSync, writeFileSync } from 'node:fs';
@@ -25,7 +26,7 @@ function injectToolGuide(systemPrompt, helpText) {
25
26
  }
26
27
  /** Register the root crtr tool-guide injection for a canvas node. */
27
28
  export function registerCanvasToolGuide(pi) {
28
- const nodeId = process.env['CRTR_NODE_ID'];
29
+ const nodeId = envNodeId();
29
30
  if (nodeId === undefined || nodeId.trim() === '')
30
31
  return;
31
32
  let helpText = '';
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,93 @@
1
+ // `shared/env.ts` is the reader-side surface for the env vars listed below —
2
+ // every one of them was migrated off a scattered `process.env` read onto a
3
+ // named accessor there. This grep-enforces that boundary: outside the owner
4
+ // module itself, a read of any migrated var may live only in an explicitly
5
+ // documented exemption — a distinct, non-read use of the SAME var. Exemptions
6
+ // are itemized below so a new stray read fails loud instead of silently
7
+ // reintroducing the var outside its accessor.
8
+ import { test } from 'node:test';
9
+ import assert from 'node:assert/strict';
10
+ import { readdirSync, readFileSync, statSync } from 'node:fs';
11
+ import { join, resolve, relative } from 'node:path';
12
+ const SRC_ROOT = resolve(process.cwd(), 'src');
13
+ // Every var migrated onto an accessor in `shared/env.ts`. Complete: every
14
+ // forbidden-file sweep has landed, so this is the full migrated set.
15
+ const MIGRATED_VARS = [
16
+ 'CRTR_NODE_ID',
17
+ 'CRTR_PROFILE_ID',
18
+ 'CRTR_NODE_CWD',
19
+ 'CRTR_MODEL_INTENT',
20
+ 'CRTR_HOME',
21
+ 'CRTR_STREAM_WATCHDOG_MS',
22
+ 'CRTR_WATCHER_TICK_MS',
23
+ 'CRTR_WATCHER_DEBOUNCE_MS',
24
+ 'CRTR_RECAP_IDLE_MS',
25
+ 'CRTR_RECAP_MODEL',
26
+ 'CRTR_BASH_VALVE_MS',
27
+ 'CRTR_PIDFILE',
28
+ 'CRTR_BROKER_ENGINE',
29
+ 'CRTR_PI_BINARY',
30
+ 'CRTR_NO_DAEMON_AUTOSTART',
31
+ 'CRTRD_TCP',
32
+ 'CRTR_NO_EXPORTS',
33
+ 'CRTR_DEBUG',
34
+ 'CRTR_MAX_LIVE_BROKERS',
35
+ 'CRTR_WARN_LIVE_BROKERS',
36
+ 'CRTR_TEST_UNATTENDED_PARK_MS',
37
+ 'CRTR_TEST_PARK_SUMMARY_GRACE_MS',
38
+ ];
39
+ // Matches `process.env['VAR']`, `process.env.VAR`, or `process.env[\`VAR\`]`
40
+ // for exactly one of MIGRATED_VARS — not a longer name that merely starts
41
+ // with it (e.g. `CRTR_DEBUG_PID_LIVENESS` must NOT match `CRTR_DEBUG`).
42
+ const VAR_PATTERN = MIGRATED_VARS.join('|');
43
+ const READ_RE = new RegExp(String.raw `process\.env(?:\.(?:${VAR_PATTERN})\b|\[['"\`](?:${VAR_PATTERN})['"\`]\])`);
44
+ // Relative to src/, forward-slash form. Each entry is a genuinely distinct
45
+ // use of the SAME var name that the migration deliberately leaves in place —
46
+ // not an unmigrated read.
47
+ const EXEMPTIONS = new Map([
48
+ [
49
+ 'core/canvas/paths.ts',
50
+ 'crtrHome() is the canonical resolved-path accessor for CRTR_HOME; ' +
51
+ 'envHomeOverride() in shared/env.ts is for callers that want the raw ' +
52
+ 'override itself (see the header comment there).',
53
+ ],
54
+ [
55
+ 'core/runtime/broker.ts',
56
+ "clears CRTR_MODEL_INTENT via `delete process.env[...]` — a mutation, " +
57
+ 'not a read, so it stays here per shared/env.ts\'s header comment.',
58
+ ],
59
+ ]);
60
+ function tsFilesUnder(dir) {
61
+ const out = [];
62
+ for (const entry of readdirSync(dir)) {
63
+ const path = join(dir, entry);
64
+ const stat = statSync(path);
65
+ if (stat.isDirectory()) {
66
+ if (entry === '__tests__')
67
+ continue;
68
+ out.push(...tsFilesUnder(path));
69
+ }
70
+ else if (entry.endsWith('.ts') && !entry.endsWith('.test.ts')) {
71
+ out.push(path);
72
+ }
73
+ }
74
+ return out;
75
+ }
76
+ test('every migrated env var is read only through shared/env.ts or an explicit exemption', () => {
77
+ const offenders = [];
78
+ for (const file of tsFilesUnder(SRC_ROOT)) {
79
+ const relPath = relative(SRC_ROOT, file).split('\\').join('/');
80
+ if (relPath === 'shared/env.ts')
81
+ continue;
82
+ if (EXEMPTIONS.has(relPath))
83
+ continue;
84
+ const source = readFileSync(file, 'utf8');
85
+ const lines = source.split('\n');
86
+ lines.forEach((line, index) => {
87
+ if (READ_RE.test(line)) {
88
+ offenders.push(`${relPath}:${index + 1}: ${line.trim()}`);
89
+ }
90
+ });
91
+ }
92
+ assert.deepEqual(offenders, [], `migrated env var read(s) outside shared/env.ts and its exemptions:\n${offenders.join('\n')}`);
93
+ });
@@ -0,0 +1,68 @@
1
+ /** The current node's id (`CRTR_NODE_ID`), or undefined outside a node. */
2
+ export declare function envNodeId(): string | undefined;
3
+ /** The current node's target profile id (`CRTR_PROFILE_ID`), or undefined. */
4
+ export declare function envProfileId(): string | undefined;
5
+ /** The current node's working directory (`CRTR_NODE_CWD`), or undefined. */
6
+ export declare function envNodeCwd(): string | undefined;
7
+ /** An explicit model-routing override for this turn (`CRTR_MODEL_INTENT`), or
8
+ * undefined. Cleared (not read) via `delete process.env['CRTR_MODEL_INTENT']`
9
+ * in `runtime/broker.ts` — a mutation, not a read, so it stays there. */
10
+ export declare function envModelIntent(): string | undefined;
11
+ /** The raw `CRTR_HOME` override, unresolved. Use `crtrHome()`
12
+ * (`core/canvas/paths.ts`) to get the resolved canvas-home path; use this
13
+ * only when a caller needs the override itself — to check whether one is
14
+ * set, or to propagate it verbatim into a child process's env. */
15
+ export declare function envHomeOverride(): string | undefined;
16
+ /** Dead-provider watchdog timeout (`CRTR_STREAM_WATCHDOG_MS`), 5 minutes by
17
+ * default. See `runtime/stream-watchdog.ts` for what it guards. */
18
+ export declare function envStreamWatchdogMs(): number;
19
+ /** The canvas inbox watcher's poll tick (`CRTR_WATCHER_TICK_MS`). */
20
+ export declare function envWatcherTickMs(): number;
21
+ /** The canvas inbox watcher's debounce window (`CRTR_WATCHER_DEBOUNCE_MS`). */
22
+ export declare function envWatcherDebounceMs(): number;
23
+ /** No-new-message window before a recap is shown (`CRTR_RECAP_IDLE_MS`), 60s
24
+ * by default. */
25
+ export declare function envRecapIdleMs(): number;
26
+ /** Explicit recap-model override (`CRTR_RECAP_MODEL`), trimmed; undefined if
27
+ * unset or blank. */
28
+ export declare function envRecapModelOverride(): string | undefined;
29
+ /** The bash safety-valve deadline (`CRTR_BASH_VALVE_MS`), 5 minutes by
30
+ * default; undocumented test-only override so the E2E valve check doesn't
31
+ * sleep 5 minutes. */
32
+ export declare function envBashValveMs(): number;
33
+ /** Explicit pidfile path override (`CRTR_PIDFILE`, tests only); undefined if
34
+ * unset or blank. */
35
+ export declare function envPidfileOverride(): string | undefined;
36
+ /** The broker engine module spec (`CRTR_BROKER_ENGINE`, the T11 test seam),
37
+ * defaulting to the real SDK. `runtime/host.ts` layers `inv.env` ahead of
38
+ * this (a launch-time override mirrored into the child's env, since
39
+ * spawn-env.ts's default-deny allowlist strips `CRTR_*` from ambient env). */
40
+ export declare function envBrokerEngine(): string;
41
+ /** Test-only pi-binary substitution (`CRTR_PI_BINARY`) so the integration
42
+ * harness can point a real `crtr node new` at a deterministic fake-pi
43
+ * vehicle. Undefined in production. */
44
+ export declare function envPiBinaryOverride(): string | undefined;
45
+ /** Whether implicit daemon autostart is suppressed for this invocation
46
+ * (`CRTR_NO_DAEMON_AUTOSTART=1`). */
47
+ export declare function envNoDaemonAutostart(): boolean;
48
+ /** The `--tcp` fallback for crtrd's opt-in TCP listener (`CRTRD_TCP`), raw
49
+ * (`host:port` or undefined — absent means unix socket only). */
50
+ export declare function envCrtrdTcp(): string | undefined;
51
+ /** Kill switch for the host-exports writer/pruner (`CRTR_NO_EXPORTS=1`). */
52
+ export declare function envNoExports(): boolean;
53
+ /** Verbose-diagnostics gate for otherwise-silent best-effort catches
54
+ * (`CRTR_DEBUG=1`). Distinct from `CRTR_DEBUG_PID_LIVENESS`
55
+ * (`core/canvas/pid.ts`), a different, unrelated var. */
56
+ export declare function envDebug(): boolean;
57
+ /** Live-broker automatic-revive cap override (`CRTR_MAX_LIVE_BROKERS`); wins
58
+ * over `readConfig('user').brokerThresholds.automaticReviveCap` when set. */
59
+ export declare function envMaxLiveBrokers(): number | undefined;
60
+ /** Live-broker warning threshold override (`CRTR_WARN_LIVE_BROKERS`); wins
61
+ * over `readConfig('user').brokerThresholds.warning` when set. */
62
+ export declare function envWarnLiveBrokers(): number | undefined;
63
+ /** Test-only unattended-park interval override (`CRTR_TEST_UNATTENDED_PARK_MS`),
64
+ * gated by `envNoDaemonAutostart()` at the call site; no production default. */
65
+ export declare function envTestUnattendedParkMs(): number | undefined;
66
+ /** Test-only park-summary grace-period override
67
+ * (`CRTR_TEST_PARK_SUMMARY_GRACE_MS`); no production default. */
68
+ export declare function envTestParkSummaryGraceMs(): number | undefined;