@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
@@ -13,23 +13,18 @@
13
13
  // generation so settlement records an automatic pi→provider connection fault;
14
14
  // the existing fault-retry machinery owns re-driving the turn. The broker
15
15
  // process — and its in-memory queues — survives instead of being killed.
16
- const DEFAULT_STREAM_WATCHDOG_MS = 5 * 60_000; // 5 minutes.
17
- // Comfortably under the daemon's 20-minute WEDGE_QUIET_MS hard kick (crtrd.ts),
18
- // so this is the common-case recovery path and the daemon kick becomes the rare
19
- // backstop. Generous enough that a live, healthy stream never trips it: a
20
- // single LLM turn's own network round-trips (message_update deltas,
21
- // tool_execution_start/update/end) land well inside 5 minutes. The watchdog
22
- // covers ONLY time awaiting the provider: the broker disarms it while a tool is
23
- // executing (a legitimately silent tool can run longer than the timeout — see
24
- // broker.ts relayEvent) and rearms it once the last in-flight tool ends, so a
25
- // slow-but-live turn is never mistaken for a dead one.
26
- export function resolveStreamWatchdogMs() {
27
- const raw = process.env['CRTR_STREAM_WATCHDOG_MS'];
28
- if (raw === undefined)
29
- return DEFAULT_STREAM_WATCHDOG_MS;
30
- const n = Number(raw);
31
- return Number.isFinite(n) && n > 0 ? n : DEFAULT_STREAM_WATCHDOG_MS;
32
- }
16
+ // The default is 5 minutes, comfortably under the daemon's 20-minute
17
+ // WEDGE_QUIET_MS hard kick (crtrd.ts), so this is the common-case recovery
18
+ // path and the daemon kick becomes the rare backstop. Generous enough that a
19
+ // live, healthy stream never trips it: a single LLM turn's own network
20
+ // round-trips (message_update deltas, tool_execution_start/update/end) land
21
+ // well inside 5 minutes. The watchdog covers ONLY time awaiting the provider:
22
+ // the broker disarms it while a tool is executing (a legitimately silent tool
23
+ // can run longer than the timeout — see broker.ts relayEvent) and rearms it
24
+ // once the last in-flight tool ends, so a slow-but-live turn is never mistaken
25
+ // for a dead one. See `envStreamWatchdogMs()` (`shared/env.ts`) for the parsed
26
+ // `CRTR_STREAM_WATCHDOG_MS` accessor and default value.
27
+ import { envStreamWatchdogMs } from '../../shared/env.js';
33
28
  /**
34
29
  * A single-timer dead-man's switch: `poke(token)` (re)arms it, `clear()`
35
30
  * disarms it, and `onStall(token)` fires exactly once if `poke()` isn't called
@@ -50,7 +45,7 @@ export class StreamWatchdog {
50
45
  ms;
51
46
  constructor(onStall, ms) {
52
47
  this.onStall = onStall;
53
- this.ms = ms ?? resolveStreamWatchdogMs();
48
+ this.ms = ms ?? envStreamWatchdogMs();
54
49
  }
55
50
  /** Proof of progress — (re)arm the timer for `token`, canceling any prior
56
51
  * countdown. The fired `onStall` receives THIS arm's token. Inert while
@@ -10,9 +10,7 @@ import { normalizeTmuxPassthrough } from '../config.js';
10
10
  import { withExclusiveDirectoryLock } from '../exclusive-lock.js';
11
11
  import { surfaceTmuxStyleArgs } from './surface-bg.js';
12
12
  import { runTmux as tmux, shellQuote } from './tmux-driver.js';
13
- // ---------------------------------------------------------------------------
14
13
  // Crouter-owned tmux bindings
15
- // ---------------------------------------------------------------------------
16
14
  const TMUX_BINDING_MANIFEST = '@crtr_keybindings_manifest';
17
15
  const ROOT_TABLE = 'root';
18
16
  const LEAF_MENU_ACTIONS = {
@@ -7,6 +7,18 @@ declare function tmux(args: string[]): {
7
7
  stderr: string;
8
8
  };
9
9
  export declare function inTmux(): boolean;
10
+ /** Is any tmux SESSION currently up? `tmux list-sessions` exits 0 iff a server
11
+ * is running with at least one session, non-zero otherwise (including "no
12
+ * server"). This is the boot-time/bash-tooling probe — distinct from
13
+ * `tmuxClientsReachable`, which asks whether a server is reachable at all
14
+ * regardless of session count and is the human-surface gate. */
15
+ export declare function tmuxSessionsReachable(): boolean;
16
+ /** Is the tmux SERVER reachable at all? `tmux list-clients` exits 0 whenever a
17
+ * server is up (even with zero attached clients), non-zero when no server is
18
+ * running. This is the human-surface gate (core/spawn.ts's
19
+ * `tmuxServerReachable`) — distinct from `tmuxSessionsReachable`, which
20
+ * additionally requires a session to exist. */
21
+ export declare function tmuxClientsReachable(): boolean;
10
22
  /** The pane currently displaying `nodeId`, found by the authoritative `@crtr_node`
11
23
  * pane tag — set by EVERY viewer (a bare `surface attach`, a focus split, a
12
24
  * spawned viewer) on connect. Scans all panes (`list-panes -a`); the reverse of
@@ -7,9 +7,8 @@ import { execFile, spawnSync } from 'node:child_process';
7
7
  import { encodeAttachControlInput, } from '../keybindings/index.js';
8
8
  import { bundledPiCommandForShell } from './pi-cli.js';
9
9
  import { shellQuote } from '../../shared/shell-quote.js';
10
- // ---------------------------------------------------------------------------
10
+ import { envPiBinaryOverride } from '../../shared/env.js';
11
11
  // tmux invocation
12
- // ---------------------------------------------------------------------------
13
12
  export { shellQuote };
14
13
  function tmux(args) {
15
14
  const r = spawnSync('tmux', args, { encoding: 'utf8' });
@@ -25,6 +24,22 @@ function tmuxOnSocket(socket, args) {
25
24
  export function inTmux() {
26
25
  return process.env['TMUX'] !== undefined && process.env['TMUX'] !== '';
27
26
  }
27
+ /** Is any tmux SESSION currently up? `tmux list-sessions` exits 0 iff a server
28
+ * is running with at least one session, non-zero otherwise (including "no
29
+ * server"). This is the boot-time/bash-tooling probe — distinct from
30
+ * `tmuxClientsReachable`, which asks whether a server is reachable at all
31
+ * regardless of session count and is the human-surface gate. */
32
+ export function tmuxSessionsReachable() {
33
+ return spawnSync('tmux', ['list-sessions'], { encoding: 'utf8', stdio: 'ignore' }).status === 0;
34
+ }
35
+ /** Is the tmux SERVER reachable at all? `tmux list-clients` exits 0 whenever a
36
+ * server is up (even with zero attached clients), non-zero when no server is
37
+ * running. This is the human-surface gate (core/spawn.ts's
38
+ * `tmuxServerReachable`) — distinct from `tmuxSessionsReachable`, which
39
+ * additionally requires a session to exist. */
40
+ export function tmuxClientsReachable() {
41
+ return spawnSync('tmux', ['list-clients', '-F', '#{client_name}'], { encoding: 'utf8' }).status === 0;
42
+ }
28
43
  /** The pane currently displaying `nodeId`, found by the authoritative `@crtr_node`
29
44
  * pane tag — set by EVERY viewer (a bare `surface attach`, a focus split, a
30
45
  * spawned viewer) on connect. Scans all panes (`list-panes -a`); the reverse of
@@ -63,9 +78,7 @@ export function currentTmux() {
63
78
  const [session, window, pane] = r.stdout.split('\t');
64
79
  return { session, window, pane };
65
80
  }
66
- // ---------------------------------------------------------------------------
67
81
  // Sessions + windows
68
- // ---------------------------------------------------------------------------
69
82
  export function sessionExists(name) {
70
83
  return tmux(['has-session', '-t', name]).ok;
71
84
  }
@@ -517,9 +530,7 @@ export function panePid(pane) {
517
530
  export function respawnPaneInPlace(pane) {
518
531
  return tmux(['respawn-pane', '-k', '-t', pane]).ok;
519
532
  }
520
- // ---------------------------------------------------------------------------
521
533
  // pi command assembly
522
- // ---------------------------------------------------------------------------
523
534
  /** Turn a pi argv array into a single shell command string.
524
535
  *
525
536
  * The binary defaults to `CRTR_PI_BINARY` when that env var is set, else the
@@ -530,12 +541,10 @@ export function respawnPaneInPlace(pane) {
530
541
  * explicit `binary` arg still overrides the env. The value may be a multi-word
531
542
  * launcher (e.g. `node --import tsx/esm host.ts`); only argv entries are
532
543
  * shell-quoted, so a multi-word binary is spliced verbatim ahead of them. */
533
- export function piCommand(argv, binary = process.env['CRTR_PI_BINARY'] ?? bundledPiCommandForShell(shellQuote)) {
544
+ export function piCommand(argv, binary = envPiBinaryOverride() ?? bundledPiCommandForShell(shellQuote)) {
534
545
  return [binary, ...argv.map(shellQuote)].join(' ');
535
546
  }
536
- // ---------------------------------------------------------------------------
537
547
  // Window liveness helpers (used by the supervisor daemon)
538
- // ---------------------------------------------------------------------------
539
548
  /** List all window ids present in `session`. Returns [] if the session does
540
549
  * not exist or tmux fails for any reason. Each entry is the raw window id
541
550
  * string reported by tmux (e.g. `@1`, `@2`, …). */
@@ -553,9 +562,7 @@ export function windowAlive(session, window) {
553
562
  return false;
554
563
  return listWindowIds(session).includes(window);
555
564
  }
556
- // ---------------------------------------------------------------------------
557
565
  // Focus helpers (used by the placement layer)
558
- // ---------------------------------------------------------------------------
559
566
  /** Activate a window within its session (same-session navigation). Equivalent
560
567
  * to `tmux select-window -t <session>:<window>`. Best-effort; never throws. */
561
568
  export function selectWindow(session, window) {
@@ -582,9 +589,7 @@ export function selectPane(pane) {
582
589
  export function switchClient(session) {
583
590
  return tmux(['switch-client', '-t', session]).ok;
584
591
  }
585
- // ---------------------------------------------------------------------------
586
592
  // send-keys (chrome — used by the prefix menu + nav bindings)
587
- // ---------------------------------------------------------------------------
588
593
  /** Type a literal (e.g. a `/graph` slash command) into a pane and press Enter
589
594
  * (`tmux send-keys -t <pane> '<text>' Enter`). Requires the pane's editor be
590
595
  * empty, same limitation as the menu's `/promote` item. Best-effort. */
@@ -182,9 +182,7 @@ function abandonSpare(nodeId, what, err) {
182
182
  catch { /* best-effort */ }
183
183
  return null;
184
184
  }
185
- // ---------------------------------------------------------------------------
186
185
  // Reaping
187
- // ---------------------------------------------------------------------------
188
186
  /** How long an UNCLAIMED spare is worth its resident broker (~150-300 MB).
189
187
  * A spare exists to make the NEXT create instant; one nobody has claimed in
190
188
  * this long is memory rented for nothing, so it is returned. Over-reaping
@@ -308,9 +306,7 @@ function mint(recipe) {
308
306
  }
309
307
  })();
310
308
  }
311
- // ---------------------------------------------------------------------------
312
309
  // Daemon-start pre-warm
313
- // ---------------------------------------------------------------------------
314
310
  /** How many profiles get a spare at daemon start. Each spare is a resident
315
311
  * broker at ~150-300 MB, so this is deliberately tiny. */
316
312
  const PREWARM_PROFILES = 2;
@@ -1,3 +1,4 @@
1
+ import { envProfileId } from '../shared/env.js';
1
2
  import { homedir } from 'node:os';
2
3
  import { existsSync, statSync } from 'node:fs';
3
4
  import { join, resolve, dirname } from 'node:path';
@@ -105,7 +106,7 @@ function collectAncestorScopeRoots(startDir, userRoot) {
105
106
  * (`findProjectScopeRootsWithMemory`), a projection layered onto this order
106
107
  * rather than part of it: discovery provenance decides precedence, the
107
108
  * relationship decides the ceiling, and the two never influence each other. */
108
- export function findProjectScopeRoots(startDir = process.cwd(), profileId = process.env['CRTR_PROFILE_ID'] || null) {
109
+ export function findProjectScopeRoots(startDir = process.cwd(), profileId = envProfileId() || null) {
109
110
  return resolveProjectScopeRoots(startDir, profileId).roots;
110
111
  }
111
112
  /** `findProjectScopeRoots` with each root's profile memory ceiling attached —
@@ -113,7 +114,7 @@ export function findProjectScopeRoots(startDir = process.cwd(), profileId = proc
113
114
  * walk takes that entry's `memory` (first entry in manifest order wins, even
114
115
  * when cwd also reaches the root); every other root takes the neutral
115
116
  * `content` ceiling. */
116
- export function findProjectScopeRootsWithMemory(startDir = process.cwd(), profileId = process.env['CRTR_PROFILE_ID'] || null) {
117
+ export function findProjectScopeRootsWithMemory(startDir = process.cwd(), profileId = envProfileId() || null) {
117
118
  return resolveProjectScopeRoots(startDir, profileId).annotated;
118
119
  }
119
120
  function resolveProjectScopeRoots(startDir, profileId) {
@@ -195,7 +196,7 @@ export function ensureProjectScopeRoot(startDir = process.cwd()) {
195
196
  // cache hit.
196
197
  const resolvedStart = resolve(startDir);
197
198
  const root = join(resolvedStart, CRTR_DIR_NAME);
198
- const profileId = process.env['CRTR_PROFILE_ID'] || null;
199
+ const profileId = envProfileId() || null;
199
200
  // Neutral ceiling: the empty cached stack proves no profile entry's ancestor
200
201
  // walk reached this root, since it did not exist to be walked into.
201
202
  scopeRootsCache.set(scopeRootsCacheKey(resolvedStart, profileId), {
@@ -4,7 +4,7 @@
4
4
  // (tmuxServerReachable) so it can decide whether to surface a prompt or degrade
5
5
  // to the inbox-drain follow-up. The canvas runtime has its own
6
6
  // one-window-per-node machinery in core/runtime/placement-tmux.ts.
7
- import { spawnSync } from 'node:child_process';
7
+ import { tmuxClientsReachable } from './runtime/tmux-driver.js';
8
8
  /** Does THIS process sit inside a tmux client (its own $TMUX is set)? NOT the
9
9
  * gate for the human surface — see tmuxServerReachable. A headless-broker child
10
10
  * strips TMUX/TMUX_PANE (broker.ts, anti-stophook-hijack) yet can still drive
@@ -22,6 +22,5 @@ export function isInTmux() {
22
22
  * no server is running — the true "no tmux to surface into" case that should
23
23
  * degrade to the inbox-drain follow-up. */
24
24
  export function tmuxServerReachable() {
25
- const r = spawnSync('tmux', ['list-clients', '-F', '#{client_name}'], { encoding: 'utf8' });
26
- return r.status === 0;
25
+ return tmuxClientsReachable();
27
26
  }
@@ -1,8 +1,9 @@
1
- import { chmodSync, existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
2
- import { createHash } from "node:crypto";
3
- import { homedir } from "node:os";
4
- import { dirname, join } from "node:path";
5
- import { LockedJsonFile } from "./locked-file.js";
1
+ import { existsSync, readFileSync } from 'node:fs';
2
+ import { createHash } from 'node:crypto';
3
+ import { homedir } from 'node:os';
4
+ import { join } from 'node:path';
5
+ import { atomicWriteJson } from './fs-utils.js';
6
+ import { LockedJsonFile } from './locked-file.js';
6
7
  export const ANTHROPIC_PROVIDER_ID = "anthropic";
7
8
  export const OPENAI_CODEX_PROVIDER_ID = "openai-codex";
8
9
  export const INVALID_REFRESH_TOKEN_BACKOFF_MS = 30 * 24 * 60 * 60 * 1000;
@@ -123,14 +124,6 @@ function parsePoolUnderLock(path, current) {
123
124
  throw new Error(`Subscription pool ${path} is corrupt: ${error instanceof Error ? error.message : String(error)}`);
124
125
  }
125
126
  }
126
- function writeJsonFile(path, value, mode) {
127
- mkdirSync(dirname(path), { recursive: true });
128
- const tmp = `${path}.${process.pid}.tmp`;
129
- writeFileSync(tmp, `${JSON.stringify(value, null, 2)}\n`, mode === undefined ? "utf8" : { encoding: "utf8", mode });
130
- if (mode !== undefined)
131
- chmodSync(tmp, mode);
132
- renameSync(tmp, path);
133
- }
134
127
  function normalizeStrength(value) {
135
128
  return value === "ultra" || value === "strong" || value === "medium" || value === "light" ? value : undefined;
136
129
  }
@@ -212,13 +205,13 @@ function assertCandidateRowsStorable(providerId, candidate) {
212
205
  // The ONE door every write to a pool file goes through. Both pool invariants -- one account
213
206
  // identity per pool, and every candidate row carrying a storable credential -- are properties
214
207
  // of the FILE, not of whichever code path happens to be writing, so they are asserted here
215
- // against the state this write replaces, under the caller's already-held lock. `writeJsonFile`
216
- // is reached for the pool NOWHERE ELSE: that makes the invariants unbypassable.
208
+ // against the state this write replaces, under the caller's already-held lock. `atomicWriteJson`
209
+ // is reached for the pool NOWHERE ELSE in this file: that makes the invariants unbypassable.
217
210
  function persistPool(providerId, poolFile, base, candidate) {
218
211
  assertCandidateRowsStorable(providerId, candidate);
219
212
  const next = normalizeCredentials(candidate);
220
213
  logCredentialSetChange(providerId, base, next);
221
- writeJsonFile(poolFile, next, 0o600);
214
+ atomicWriteJson(poolFile, next, { mode: 0o600 });
222
215
  return next;
223
216
  }
224
217
  // Accounts vanishing from a pool is the failure users actually feel ("I keep having to log in
@@ -721,7 +714,7 @@ export function writeRotationConfig(next) {
721
714
  defaultFallbackStrength: normalizeStrength(next.defaultFallbackStrength) ?? current.defaultFallbackStrength ?? DEFAULT_FALLBACK_STRENGTH,
722
715
  revertWhenAvailable: next.revertWhenAvailable ?? current.revertWhenAvailable ?? true,
723
716
  };
724
- writeJsonFile(rotationConfigFile(), merged);
717
+ atomicWriteJson(rotationConfigFile(), merged);
725
718
  return merged;
726
719
  }
727
720
  export function formatStatusLine(providerId, now = Date.now()) {
@@ -28,7 +28,7 @@ const SUBJECT = {
28
28
  function entry(match, gate) {
29
29
  return { on: 'pre-command', at: 'content', match, ...(gate !== undefined ? { gate } : {}) };
30
30
  }
31
- // --- the behaviour this event adds -----------------------------------------
31
+ // the behaviour this event adds
32
32
  test('a matched invocation inside a heredoc BODY does not hold the command', () => {
33
33
  const command = [
34
34
  "crtr memory write approvals --body <<'EOF'",
@@ -60,7 +60,7 @@ test('a `command` entry is never consumed by the pre-command matcher, nor the re
60
60
  assert.equal(matchesPreCommandEntry(post, SUBJECT, 'crtr integration run --action send'), false);
61
61
  assert.equal(matchesCommandEntry(entry(['crtr integration run*']), SUBJECT, 'crtr integration run --action send'), false);
62
62
  });
63
- // --- the rung fold ---------------------------------------------------------
63
+ // the rung fold
64
64
  test('the rung fold returns the highest `at` across matching entries, and none when nothing matches', () => {
65
65
  const doc = {
66
66
  surfaces: [
@@ -72,7 +72,7 @@ test('the rung fold returns the highest `at` across matching entries, and none w
72
72
  assert.equal(preCommandDeliveryRung(doc, SUBJECT, 'crtr integration run --action send'), 'content');
73
73
  assert.equal(preCommandDeliveryRung(doc, SUBJECT, 'ls -la'), 'none');
74
74
  });
75
- // --- shared routing: one command-glob semantics, not two --------------------
75
+ // shared routing: one command-glob semantics, not two
76
76
  test('outside a heredoc, pre-command matching is exactly the `command` matcher', () => {
77
77
  // The guard against a second command tokenizer drifting away from the first.
78
78
  // Whatever `command` globs mean today, `pre-command` means the same thing —
@@ -140,9 +140,7 @@ function bootCorpus() {
140
140
  }
141
141
  return { docs, sources };
142
142
  }
143
- // ---------------------------------------------------------------------------
144
143
  // Exposure registration
145
- // ---------------------------------------------------------------------------
146
144
  function rendersAtBoot(doc) {
147
145
  return isVisible(doc.bootRung) && (doc.bootRung !== 'content' || doc.body.trim() !== '');
148
146
  }
@@ -156,9 +154,7 @@ function recordRendered(target, docs) {
156
154
  registerDocumentExposure(target, d.path, d.body, d.bootRung);
157
155
  }
158
156
  }
159
- // ---------------------------------------------------------------------------
160
157
  // Step 2 — display ordering + grouped render.
161
- // ---------------------------------------------------------------------------
162
158
  /** Content-prose ordering rank: general → specific. builtin=0, user=1,
163
159
  * profile=2, project ranks start at 3 and climb by root nearness — the
164
160
  * OUTERMOST ancestor `.crouter/` root ranks lowest (renders first), the
@@ -412,9 +408,7 @@ function buildTree(docs, rootLabel) {
412
408
  renderChildren(root, '', lines);
413
409
  return lines.join('\n');
414
410
  }
415
- // ---------------------------------------------------------------------------
416
411
  // Block prose — the intro legend + the update directive for each kind.
417
- // ---------------------------------------------------------------------------
418
412
  const READ_LEGEND = 'Each %s below shows either its full content (indented beneath its name), a ' +
419
413
  '`# read when:` line telling you when to read the full document, or simply its name.';
420
414
  const PREFERENCES_INTRO = 'Preferences are how the user wants you to behave — standing directives and corrections. ' +
@@ -487,9 +481,7 @@ export function buildSubPersonaMenu(kind) {
487
481
  'dragging unrelated context into it.\n\n' +
488
482
  lines.join('\n'));
489
483
  }
490
- // ---------------------------------------------------------------------------
491
484
  // 1. Preferences — protocol prose + `<memory kind="preference">` (system prompt).
492
- // ---------------------------------------------------------------------------
493
485
  /** The system-prompt substrate splice, TWO parts in one string.
494
486
  *
495
487
  * 1. The operating protocol: shipped (builtin/plugin) content-rung bodies as
@@ -548,9 +540,7 @@ function buildPreferencesBlock(subject, target) {
548
540
  export function renderPreferencesForSubject(subject, target) {
549
541
  return buildPreferencesBlock(subject, target);
550
542
  }
551
- // ---------------------------------------------------------------------------
552
543
  // 2. Knowledge — `<memory kind="knowledge">` (the first-message bearings).
553
- // ---------------------------------------------------------------------------
554
544
  /** STEP 1 for the knowledge block, shared by the boot render and the
555
545
  * config-change delta: the resolver's knowledge winners ∪ the node's own store
556
546
  * (any kind), first-wins deduped resolver-first so a same-named node doc
@@ -589,9 +579,7 @@ export function renderKnowledgeForSubject(subject, nodeId, target) {
589
579
  // Surfaced INTO this node: swap literal `$CRTR_CONTEXT_DIR` for the real path.
590
580
  return interpolateNodePaths(`<memory kind="knowledge">\n${out}\n</memory>`, nodeId);
591
581
  }
592
- // ---------------------------------------------------------------------------
593
582
  // 3. The config-change delta — the memory a node's NEW configuration exposes.
594
- // ---------------------------------------------------------------------------
595
583
  const DELTA_PROTOCOL_INTRO = 'Your node configuration changed. This operating protocol is in force for you now:';
596
584
  const DELTA_PREFERENCES_INTRO = 'Your new configuration puts these preferences in force — standing directives and corrections ' +
597
585
  'from the user. To read one, run `crtr memory read <name>`. ' +
@@ -9,21 +9,17 @@
9
9
  // render, and `crtr memory lint` owns strict authoring-time enforcement.
10
10
  import { PROFILE_PROJECT_MEMORY_VALUES } from '../../api/dto/profiles.js';
11
11
  import { surfaceEntryGatePasses } from './gate.js';
12
- // ---------------------------------------------------------------------------
13
12
  // Kinds — the two semantic kinds (design §3): `knowledge` (consult — procedural
14
13
  // playbooks + factual references merged) vs `preference` (behave — standing
15
14
  // directives). `kind` is a load-bearing binary, not a content taxonomy.
16
- // ---------------------------------------------------------------------------
17
15
  export const KINDS = ['knowledge', 'preference'];
18
16
  /** Is `v` one of the two valid document kinds? */
19
17
  export function isDocKind(v) {
20
18
  return typeof v === 'string' && KINDS.includes(v);
21
19
  }
22
- // ---------------------------------------------------------------------------
23
20
  // The 4-rung visibility ladder (design §4). A strict, monotone ordering where
24
21
  // each rung is a superset of disclosure over the one before:
25
22
  // none < name < preview < content
26
- // ---------------------------------------------------------------------------
27
23
  // The ladder is ONE union, owned by the public API DTO (`ProfileProjectMemory`)
28
24
  // so a profile relationship's cap and a doc's authored rung are the same values
29
25
  // and clamp against each other without a conversion.
@@ -41,21 +37,17 @@ export function rungAtLeast(r, min) {
41
37
  export function minRung(a, b) {
42
38
  return rungRank(a) <= rungRank(b) ? a : b;
43
39
  }
44
- // ---------------------------------------------------------------------------
45
40
  // Identity — owned entirely by core/memory/identity.ts (ordering-prefix
46
41
  // normalization, INDEX collapse, explicit-name-then-path local identity,
47
42
  // namespace composition). Re-exported here so document parsing and identity
48
43
  // derivation stay one rule with one implementation.
49
- // ---------------------------------------------------------------------------
50
44
  export { normalizeNameSegment, normalizeDocName, resolveLocalName as resolveDocName } from '../memory/identity.js';
51
- // ---------------------------------------------------------------------------
52
45
  // Surfaces — explicit event routing (design: surfaces-design.md). A doc
53
46
  // declares WHEN it delivers as (event, match, rung) entries; matches are
54
47
  // always globs, always absolute in the event's namespace, with `./` as the
55
48
  // one relative spelling (anchoring to the doc's own container). A doc with
56
49
  // no `surfaces` at all does exactly one thing: appears in its directory's
57
50
  // listing.
58
- // ---------------------------------------------------------------------------
59
51
  export const SURFACE_EVENTS = ['boot', 'workspace-open', 'read', 'memory-read', 'command', 'pre-command'];
60
52
  /** The rungs an entry may deliver at. There is no `none` — silence is the
61
53
  * absence of an entry. (`Rung`'s `none` survives only as the internal
@@ -74,9 +66,7 @@ export function bootRung(doc, subject) {
74
66
  }
75
67
  return r;
76
68
  }
77
- // ---------------------------------------------------------------------------
78
69
  // Parse / validate.
79
- // ---------------------------------------------------------------------------
80
70
  /** Parse a raw frontmatter record (from `parseFrontmatterGeneric`, via the
81
71
  * resolver) into a typed schema with defaults applied. Returns `null` when the
82
72
  * record is absent or carries no valid `kind` — i.e. it is not a substrate
@@ -135,9 +125,7 @@ export function previewLine(doc) {
135
125
  const line = doc.whenAndWhyToRead.trim().replace(/\.+$/, '');
136
126
  return line === '' ? '' : `${line}.`;
137
127
  }
138
- // ---------------------------------------------------------------------------
139
128
  // Field coercion helpers (private).
140
- // ---------------------------------------------------------------------------
141
129
  function strField(v) {
142
130
  return typeof v === 'string' ? v : '';
143
131
  }
@@ -323,12 +323,10 @@ export function matchesPreCommandEntry(entry, subject, command) {
323
323
  const stripped = stripHeredocs(command);
324
324
  return entry.match.some((g) => commandGlobMatches(g, stripped));
325
325
  }
326
- // ---------------------------------------------------------------------------
327
326
  // Per-doc rung folds — a doc's delivery rung for one event occurrence is the
328
327
  // highest `at` over its matching entries of that event, `none` when none
329
328
  // match (entries OR together; `none` is the internal "does not deliver"
330
329
  // floor, never an authored rung).
331
- // ---------------------------------------------------------------------------
332
330
  /** The doc's delivery rung for a read of `absReadFile` (already realpathed). */
333
331
  export function readDeliveryRung(doc, subject, absReadFile, readFrontmatter) {
334
332
  let r = 'none';
@@ -1,3 +1,4 @@
1
+ import { envNodeId } from '../../shared/env.js';
1
2
  import { spawnSync } from 'node:child_process';
2
3
  import { resolve } from 'node:path';
3
4
  import { breakPane, closePane, currentTmux, paneOfNode, splitWindow, } from '../runtime/placement-tmux.js';
@@ -6,7 +7,7 @@ function targetPane() {
6
7
  const callerPane = process.env['TMUX_PANE'];
7
8
  if (callerPane !== undefined && callerPane !== '')
8
9
  return callerPane;
9
- const nodeId = process.env['CRTR_NODE_ID'];
10
+ const nodeId = envNodeId();
10
11
  if (nodeId !== undefined && nodeId !== '') {
11
12
  const viewerPane = paneOfNode(nodeId);
12
13
  if (viewerPane !== null)
@@ -1,6 +1,6 @@
1
- import { ProcessTerminal, type Component, type Focusable, type TUI } from "@earendil-works/pi-tui";
2
- import { readRawScopeConfig } from "../config.js";
3
- import { type BindingId, type BindingResolution } from "../keybindings/index.js";
1
+ import { ProcessTerminal, type Component, type Focusable, type TUI } from '@earendil-works/pi-tui';
2
+ import { readRawScopeConfig } from '../config.js';
3
+ import { type BindingId, type BindingResolution } from '../keybindings/index.js';
4
4
  /** A full-screen page: a focusable TUI component that cleans up after itself. */
5
5
  export type TuiPageSurface = Component & Focusable & {
6
6
  dispose(): void;
@@ -6,10 +6,10 @@
6
6
  // restore path that survives an exception, a signal, and process exit. Anything
7
7
  // a single page needs beyond that — pi's model runtime, a canvas client — the
8
8
  // page's own runner builds and closes over before calling in here.
9
- import { ProcessTerminal, TuiMainScreen, } from "@earendil-works/pi-tui";
10
- import { applyTheme } from "../../clients/attach/config.js";
11
- import { readRawScopeConfig } from "../config.js";
12
- import { resolveKeybindings, } from "../keybindings/index.js";
9
+ import { ProcessTerminal, TuiMainScreen, } from '@earendil-works/pi-tui';
10
+ import { applyTheme } from '../../clients/attach/config.js';
11
+ import { readRawScopeConfig } from '../config.js';
12
+ import { resolveKeybindings, } from '../keybindings/index.js';
13
13
  /** Mount one page as the TUI's only child and resolve when it closes.
14
14
  *
15
15
  * `interceptCancel` is a process-level escape hatch for surfaces that must be
@@ -1,5 +1,5 @@
1
- import { getMarkdownTheme, getSelectListTheme, getSettingsListTheme, } from "@earendil-works/pi-coding-agent";
2
- import { truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
1
+ import { getMarkdownTheme, getSelectListTheme, getSettingsListTheme, } from '@earendil-works/pi-coding-agent';
2
+ import { truncateToWidth, visibleWidth } from '@earendil-works/pi-tui';
3
3
  const ansiGreen = (text) => `\x1b[32m${text}\x1b[39m`;
4
4
  const ansiYellow = (text) => `\x1b[33m${text}\x1b[39m`;
5
5
  export const theme = {
@@ -95,7 +95,7 @@ export function framed(lines, width) {
95
95
  export function fitTail(lines, budget) {
96
96
  return budget >= lines.length ? lines : lines.slice(lines.length - budget);
97
97
  }
98
- // --- Page chrome shared by the settings shell and the setup wizard ----------
98
+ // Page chrome shared by the settings shell and the setup wizard
99
99
  /** Rows the page frame spends on its own title, rules and footer. */
100
100
  export const PAGE_CHROME_ROWS = 4;
101
101
  /** Title row with right-aligned scope metadata, a rule under it, the body
package/dist/core/wake.js CHANGED
@@ -10,9 +10,7 @@
10
10
  // design §5.3 + D7 (engine evaluates UTC; the surface bakes the IANA zone
11
11
  // into a calendar `recur`). Tick-scale only — no sub-second precision.
12
12
  import { CronExpressionParser } from 'cron-parser';
13
- // ---------------------------------------------------------------------------
14
13
  // Constants
15
- // ---------------------------------------------------------------------------
16
14
  /** Cadence floor: reject any interval / cron min-spacing below this (AC-N4). */
17
15
  export const CADENCE_FLOOR_MS = 60_000;
18
16
  const DURATION_UNIT_MS = {
@@ -38,9 +36,7 @@ const CRON_ALIASES = {
38
36
  '@midnight': '0 0 * * *',
39
37
  '@hourly': '0 * * * *',
40
38
  };
41
- // ---------------------------------------------------------------------------
42
39
  // Internal helpers
43
- // ---------------------------------------------------------------------------
44
40
  function whenError(code, message, received) {
45
41
  return { error: { code, message, received } };
46
42
  }
@@ -127,9 +123,7 @@ function classifyIso(s) {
127
123
  return { zoned: true };
128
124
  return null; // trailing garbage -> not a valid ISO instant
129
125
  }
130
- // ---------------------------------------------------------------------------
131
126
  // parseWhen — resolve a one-shot <when> to an absolute UTC instant
132
- // ---------------------------------------------------------------------------
133
127
  /**
134
128
  * Resolve a `<when>` to a single UTC `fireAt`:
135
129
  * - relative duration (`90s`, `1h30m`) -> now + Σ
@@ -182,9 +176,7 @@ export function parseWhen(when, opts) {
182
176
  }
183
177
  return whenError('bad_when', `Could not parse "${received}". Use a duration ("5m", "1h30m"), a zoned ISO ("2026-06-07T09:00:00Z"), or a bare ISO ("2026-06-07T09:00").`, received);
184
178
  }
185
- // ---------------------------------------------------------------------------
186
179
  // parseCadence — resolve --every to a stored recur + first fire
187
- // ---------------------------------------------------------------------------
188
180
  /**
189
181
  * Resolve a `<cadence>` (the `--every` value) to a stored `recur` JSON string
190
182
  * plus the first `fireAt`:
@@ -240,9 +232,7 @@ export function parseCadence(every, opts) {
240
232
  firstFireAt,
241
233
  };
242
234
  }
243
- // ---------------------------------------------------------------------------
244
235
  // cadenceDisplay — a recur JSON rendered for humans + agents
245
- // ---------------------------------------------------------------------------
246
236
  /**
247
237
  * Render a stored `recur` JSON as a compact, human-readable cadence:
248
238
  * - {"every":"6h"} -> `every 6h`
@@ -270,9 +260,7 @@ export function cadenceDisplay(recur) {
270
260
  }
271
261
  return 'none';
272
262
  }
273
- // ---------------------------------------------------------------------------
274
263
  // nextSlotAfter — the coalescing primitive (daemon advance)
275
- // ---------------------------------------------------------------------------
276
264
  /**
277
265
  * Earliest occurrence of `recur` strictly greater than `now`, as a UTC ISO
278
266
  * string. Anchors on `now` (not the stored `fire_at`), so it structurally
@@ -357,9 +357,7 @@ export function closeManagedWorktree(nodeId) {
357
357
  return closeManagedWorktreeLocked(nodeId);
358
358
  return withRepositoryWorktreeLock(wt.repo_root, () => closeManagedWorktreeLocked(nodeId));
359
359
  }
360
- // ---------------------------------------------------------------------------
361
360
  // Push-final auto-drop of a clean, zero-commit managed worktree
362
- // ---------------------------------------------------------------------------
363
361
  /** Delete a source branch only while the ref that proved it already delivered
364
362
  * is still at the exact inspected SHA. `containingRef` can be the recorded
365
363
  * local base or that base's remote-tracking upstream. */
@@ -0,0 +1,36 @@
1
+ // Every reconciler in crtrd's tick loop conforms to one shape:
2
+ // `run(now: number, ctx: TCtx): void | Promise<unknown>`. The call loop treats
3
+ // every reconciler identically — a 1-arg or discard-shaped signature is exactly
4
+ // the drift this test catches (a runtime arity check; a compile-time
5
+ // `satisfies` pass over the same five/seven classes would need a shared
6
+ // Reconciler<TCtx> interface that adds no real safety this doesn't already
7
+ // give, since `tsc --noEmit` already fails hard on a wrong-shaped ctx object
8
+ // at each call site in crtrd.ts).
9
+ import { test } from 'node:test';
10
+ import assert from 'node:assert/strict';
11
+ import { BrokerSupervisionReconciler } from '../reconcilers/broker-supervision.js';
12
+ import { ControllerDeathReconciler, DormantInboxReconciler } from '../reconcilers/dormant-inbox.js';
13
+ import { CronLaneReconciler } from '../reconcilers/cron-lane.js';
14
+ import { HumanDeliveryLaneReconciler } from '../reconcilers/human-delivery-lane.js';
15
+ import { PendingReviewSubmitReconciler } from '../reconcilers/pending-review-submit.js';
16
+ import { StorageMaintenanceReconciler } from '../reconcilers/storage-maintenance.js';
17
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
18
+ const RECONCILERS = [
19
+ ['BrokerSupervisionReconciler', BrokerSupervisionReconciler],
20
+ ['ControllerDeathReconciler', ControllerDeathReconciler],
21
+ ['DormantInboxReconciler', DormantInboxReconciler],
22
+ ['CronLaneReconciler', CronLaneReconciler],
23
+ ['HumanDeliveryLaneReconciler', HumanDeliveryLaneReconciler],
24
+ ['PendingReviewSubmitReconciler', PendingReviewSubmitReconciler],
25
+ ['StorageMaintenanceReconciler', StorageMaintenanceReconciler],
26
+ ];
27
+ test('every crtrd reconciler declares run(now, ctx) — arity 2', () => {
28
+ const offenders = [];
29
+ for (const [name, Ctor] of RECONCILERS) {
30
+ const instance = new Ctor();
31
+ if (instance.run.length !== 2) {
32
+ offenders.push(`${name}.run has arity ${instance.run.length}, expected 2`);
33
+ }
34
+ }
35
+ assert.deepEqual(offenders, [], `reconciler(s) not shaped run(now, ctx):\n${offenders.join('\n')}`);
36
+ });