@north-light/crouter 0.3.233 → 0.3.234

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (379) hide show
  1. package/dist/api/client.js +16 -15
  2. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/__tests__/integration/provider-rotation.test.ts +1 -1
  3. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/claude-plugin-commands.ts +14 -17
  4. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/crtr-commands/index.ts +7 -11
  5. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/frontmatter-rules/index.ts +8 -8
  6. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/memory-slash-commands.ts +10 -11
  7. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/pi-shell-runner.ts +2 -2
  8. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/provider-rotation.d.ts +3 -3
  9. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/provider-rotation.js +7 -7
  10. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/provider-rotation.ts +8 -8
  11. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/statusline.ts +6 -6
  12. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/sysprompt-window.ts +3 -3
  13. package/dist/builtin-pi-packages/pi-crtr-extensions/lib/subscription-state.ts +1 -1
  14. package/dist/clients/attach/chrome/ticket-panel.js +15 -2
  15. package/dist/clients/attach/overlays/graph.js +0 -3
  16. package/dist/clients/attach/overlays/mcp.js +1 -2
  17. package/dist/clients/attach/render/chat-view.js +0 -12
  18. package/dist/clients/attach/render/crtr-output.js +0 -2
  19. package/dist/clients/attach/render/tool-calls.js +0 -6
  20. package/dist/clients/attach/slash/dispatch.js +8 -11
  21. package/dist/clients/attach/viewer.js +532 -534
  22. package/dist/clients/conversation/projection.js +0 -2
  23. package/dist/clients/inbox/controller.js +22 -3
  24. package/dist/clients/inbox/review/companion-pane.js +0 -4
  25. package/dist/clients/inbox/review-adapter.js +0 -7
  26. package/dist/clients/inbox/tui/slots.d.ts +3 -3
  27. package/dist/clients/inbox/tui/types.d.ts +2 -2
  28. package/dist/commands/api-client.js +0 -8
  29. package/dist/commands/attention.js +0 -8
  30. package/dist/commands/canvas-browse.js +2 -1
  31. package/dist/commands/canvas-config.js +0 -10
  32. package/dist/commands/canvas-history.js +2 -1
  33. package/dist/commands/cron.js +5 -18
  34. package/dist/commands/dashboard.js +0 -2
  35. package/dist/commands/human/feedback.js +2 -1
  36. package/dist/commands/human/prompts.js +2 -5
  37. package/dist/commands/human/queue.js +2 -7
  38. package/dist/commands/human/request.js +0 -14
  39. package/dist/commands/human/review.js +2 -1
  40. package/dist/commands/memory/__tests__/command-selector-and-mutation-guards.test.js +4 -4
  41. package/dist/commands/memory/find.js +2 -2
  42. package/dist/commands/memory/lint.js +2 -1
  43. package/dist/commands/memory/read.d.ts +2 -7
  44. package/dist/commands/memory/read.js +5 -16
  45. package/dist/commands/memory/shared.js +4 -7
  46. package/dist/commands/memory/write.js +4 -3
  47. package/dist/commands/node/bash.js +2 -1
  48. package/dist/commands/node/create.js +2 -1
  49. package/dist/commands/node/inspect.js +0 -6
  50. package/dist/commands/node/lifecycle.js +4 -13
  51. package/dist/commands/node/message.js +3 -2
  52. package/dist/commands/node/subscription.js +2 -1
  53. package/dist/commands/node/wait.js +4 -3
  54. package/dist/commands/node-context.js +2 -1
  55. package/dist/commands/node-lifecycle-revive.js +0 -2
  56. package/dist/commands/node-worktree.js +2 -1
  57. package/dist/commands/pkg/browse/actions.d.ts +1 -1
  58. package/dist/commands/pkg/browse/actions.js +4 -4
  59. package/dist/commands/pkg/browse/catalog.d.ts +1 -1
  60. package/dist/commands/pkg/browse/catalog.js +15 -15
  61. package/dist/commands/pkg/browse/command-view.d.ts +1 -1
  62. package/dist/commands/pkg/browse/command-view.js +3 -3
  63. package/dist/commands/pkg/browse/detail-panel.d.ts +1 -1
  64. package/dist/commands/pkg/browse/detail-panel.js +8 -8
  65. package/dist/commands/pkg/browse/doc-view.d.ts +1 -1
  66. package/dist/commands/pkg/browse/doc-view.js +3 -3
  67. package/dist/commands/pkg/browse/list-panel.d.ts +1 -1
  68. package/dist/commands/pkg/browse/list-panel.js +3 -3
  69. package/dist/commands/pkg/browse/model.d.ts +3 -3
  70. package/dist/commands/pkg/browse/shell.d.ts +2 -2
  71. package/dist/commands/pkg/browse/shell.js +13 -13
  72. package/dist/commands/pkg/plugin-manage.js +2 -1
  73. package/dist/commands/pkg/shared.js +0 -2
  74. package/dist/commands/profile/delete.js +2 -1
  75. package/dist/commands/push.js +2 -3
  76. package/dist/commands/revive.js +0 -2
  77. package/dist/commands/surface/node/placement.js +2 -1
  78. package/dist/commands/surface-edit.js +2 -1
  79. package/dist/commands/sys/config.js +0 -4
  80. package/dist/commands/sys/daemon.js +0 -10
  81. package/dist/commands/sys/doctor.js +3 -2
  82. package/dist/commands/sys/panels/broker-limits-panel.d.ts +2 -2
  83. package/dist/commands/sys/panels/broker-limits-panel.js +4 -4
  84. package/dist/commands/sys/panels/dependencies-panel.d.ts +2 -2
  85. package/dist/commands/sys/panels/dependencies-panel.js +2 -2
  86. package/dist/commands/sys/panels/keybindings-panel.d.ts +1 -1
  87. package/dist/commands/sys/panels/keybindings-panel.js +5 -5
  88. package/dist/commands/sys/panels/models-panel.d.ts +4 -4
  89. package/dist/commands/sys/panels/models-panel.js +11 -10
  90. package/dist/commands/sys/panels/plugins-panel.d.ts +2 -2
  91. package/dist/commands/sys/panels/plugins-panel.js +2 -2
  92. package/dist/commands/sys/panels/prefs-panel.d.ts +2 -2
  93. package/dist/commands/sys/panels/prefs-panel.js +4 -4
  94. package/dist/commands/sys/panels/profiles-panel.d.ts +1 -1
  95. package/dist/commands/sys/panels/profiles-panel.js +14 -13
  96. package/dist/commands/sys/panels/provider-panel.d.ts +3 -3
  97. package/dist/commands/sys/panels/provider-panel.js +8 -8
  98. package/dist/commands/sys/settings-shell.d.ts +3 -3
  99. package/dist/commands/sys/settings-shell.js +15 -15
  100. package/dist/commands/sys/setup-settings-runner.d.ts +3 -3
  101. package/dist/commands/sys/setup-settings-runner.js +5 -5
  102. package/dist/commands/sys/setup-wizard.d.ts +4 -4
  103. package/dist/commands/sys/setup-wizard.js +8 -8
  104. package/dist/commands/sys/support/prepare.js +4 -4
  105. package/dist/commands/sys/support/submit.js +2 -2
  106. package/dist/commands/sys/sync-deps.js +0 -2
  107. package/dist/commands/sys/sync-project-guidance.js +2 -1
  108. package/dist/commands/sys/sysprompt.js +2 -1
  109. package/dist/core/__tests__/boot.test.js +0 -8
  110. package/dist/core/__tests__/broker-extension-canvas-db-boundary.test.js +23 -1
  111. package/dist/core/__tests__/canvas-db-boundary.test.d.ts +1 -0
  112. package/dist/core/__tests__/canvas-db-boundary.test.js +46 -0
  113. package/dist/core/__tests__/canvas.test.js +1 -1
  114. package/dist/core/__tests__/child-death-wake.test.js +0 -2
  115. package/dist/core/__tests__/cron-armed-context-failure.test.d.ts +1 -0
  116. package/dist/core/__tests__/cron-armed-context-failure.test.js +145 -0
  117. package/dist/core/__tests__/daemon-wedge.test.js +0 -4
  118. package/dist/core/__tests__/dead-node-policy-table.test.js +0 -4
  119. package/dist/core/__tests__/fixtures/fake-engine.js +4 -14
  120. package/dist/core/__tests__/fixtures/fake-pi-host.js +5 -5
  121. package/dist/core/__tests__/focuses.test.js +0 -6
  122. package/dist/core/__tests__/fs-utils.test.d.ts +1 -0
  123. package/dist/core/__tests__/fs-utils.test.js +50 -0
  124. package/dist/core/__tests__/helpers/harness.js +1 -1
  125. package/dist/core/__tests__/inline-memory-refs.test.js +0 -2
  126. package/dist/core/__tests__/integration/broker-fork-seam.test.js +0 -2
  127. package/dist/core/__tests__/integration/broker-sdk-wiring.test.js +3 -16
  128. package/dist/core/__tests__/integration/command-plugins.test.js +0 -24
  129. package/dist/core/__tests__/integration/deferred-no-wake.test.js +0 -6
  130. package/dist/core/__tests__/integration/flagship-lifecycle.test.js +0 -26
  131. package/dist/core/__tests__/integration/live-mutation-verbs.test.js +0 -4
  132. package/dist/core/__tests__/integration/live-mutation.test.js +1 -5
  133. package/dist/core/__tests__/integration/refresh-stall-recycle.test.js +1 -5
  134. package/dist/core/__tests__/integration/revive.test.js +0 -12
  135. package/dist/core/__tests__/integration/subscription-delivery.test.js +0 -6
  136. package/dist/core/__tests__/integration/tmux-surface.test.js +24 -8
  137. package/dist/core/__tests__/integration/worktree-reap.test.js +0 -6
  138. package/dist/core/__tests__/lifecycle.test.js +0 -18
  139. package/dist/core/__tests__/memory-resolver-precedence.test.js +1 -1
  140. package/dist/core/__tests__/migration.test.js +2 -2
  141. package/dist/core/__tests__/on-read-dedup-resume.test.js +3 -3
  142. package/dist/core/__tests__/pid-identity-match.test.js +1 -1
  143. package/dist/core/__tests__/plugin-kinds.test.js +1 -1
  144. package/dist/core/__tests__/plugin-page-components.test.js +1 -1
  145. package/dist/core/__tests__/preview-registry-sync.test.js +42 -1
  146. package/dist/core/__tests__/respawn-throttle.test.js +0 -4
  147. package/dist/core/__tests__/revive-all.test.js +5 -5
  148. package/dist/core/__tests__/revive-parked-fresh.test.js +62 -11
  149. package/dist/core/__tests__/seam/broker-attach-multiclient.test.js +0 -8
  150. package/dist/core/__tests__/seam/broker-attach-stream.test.js +0 -8
  151. package/dist/core/__tests__/seam/broker-crash-teardown.test.js +0 -2
  152. package/dist/core/__tests__/tmux-driver-boundary.test.d.ts +1 -0
  153. package/dist/core/__tests__/tmux-driver-boundary.test.js +50 -0
  154. package/dist/core/activity-signal.d.ts +7 -0
  155. package/dist/core/activity-signal.js +38 -0
  156. package/dist/core/auto-update.js +2 -1
  157. package/dist/core/bootstrap.js +2 -1
  158. package/dist/core/canvas/__tests__/remote-canvas-source.test.js +1 -1
  159. package/dist/core/canvas/__tests__/remote-transport.test.js +1 -1
  160. package/dist/core/canvas/__tests__/render-remote.test.js +1 -1
  161. package/dist/core/canvas/browse/app.js +3 -2
  162. package/dist/core/canvas/browse/model.d.ts +1 -1
  163. package/dist/core/canvas/browse/model.js +0 -4
  164. package/dist/core/canvas/canvas.d.ts +22 -0
  165. package/dist/core/canvas/canvas.js +50 -18
  166. package/dist/core/canvas/crons.js +0 -6
  167. package/dist/core/canvas/db.d.ts +14 -20
  168. package/dist/core/canvas/db.js +30 -1332
  169. package/dist/core/canvas/focuses.js +0 -4
  170. package/dist/core/canvas/history.js +2 -1
  171. package/dist/core/canvas/migrations.d.ts +20 -0
  172. package/dist/core/canvas/migrations.js +1339 -0
  173. package/dist/core/canvas/nav-model.js +1 -11
  174. package/dist/core/canvas/nav-render.js +0 -8
  175. package/dist/core/canvas/remote-canvas-source.js +0 -2
  176. package/dist/core/canvas/render-source.js +31 -13
  177. package/dist/core/command-manifests/manifest.js +0 -10
  178. package/dist/core/command-manifests/schema.js +0 -4
  179. package/dist/core/command-plugins/transport/http-invoke.js +0 -14
  180. package/dist/core/command.js +0 -4
  181. package/dist/core/config.js +2 -1
  182. package/dist/core/feed/inbox.d.ts +10 -0
  183. package/dist/core/feed/inbox.js +18 -12
  184. package/dist/core/fs-utils.d.ts +9 -2
  185. package/dist/core/fs-utils.js +7 -15
  186. package/dist/core/help.js +0 -8
  187. package/dist/core/host-exports/export.d.ts +2 -2
  188. package/dist/core/host-exports/export.js +9 -12
  189. package/dist/core/human/answer.d.ts +2 -2
  190. package/dist/core/human/convention.d.ts +2 -5
  191. package/dist/core/human/convention.js +3 -13
  192. package/dist/core/human/page-schema.d.ts +2 -2
  193. package/dist/core/human/page-schema.js +2 -2
  194. package/dist/core/human/root.js +6 -23
  195. package/dist/core/human/scan.js +24 -7
  196. package/dist/core/human/tickets.d.ts +4 -1
  197. package/dist/core/human/tickets.js +20 -6
  198. package/dist/core/io.js +0 -12
  199. package/dist/core/memory/identity.js +0 -4
  200. package/dist/core/memory/inline-ref-inventory.js +3 -2
  201. package/dist/core/memory/lint.js +3 -6
  202. package/dist/core/memory-resolver.d.ts +5 -0
  203. package/dist/core/memory-resolver.js +14 -12
  204. package/dist/core/pagination.js +0 -2
  205. package/dist/core/preview-registry.d.ts +14 -0
  206. package/dist/core/preview-registry.js +2 -4
  207. package/dist/core/preview-result-path.js +2 -1
  208. package/dist/core/profiles/manifest.d.ts +37 -5
  209. package/dist/core/profiles/manifest.js +44 -24
  210. package/dist/core/profiles/select.js +0 -4
  211. package/dist/core/review/signal.js +4 -23
  212. package/dist/core/review/ticket-filter.d.ts +3 -3
  213. package/dist/core/review/ticket-filter.js +4 -9
  214. package/dist/core/runtime/__tests__/engine-route.test.js +1 -1
  215. package/dist/core/runtime/__tests__/spawn-env.test.js +0 -8
  216. package/dist/core/runtime/bearings-render.d.ts +56 -0
  217. package/dist/core/runtime/bearings-render.js +241 -0
  218. package/dist/core/runtime/bearings.d.ts +2 -17
  219. package/dist/core/runtime/bearings.js +12 -198
  220. package/dist/core/runtime/broker/auth-reload.d.ts +39 -0
  221. package/dist/core/runtime/broker/auth-reload.js +149 -0
  222. package/dist/core/runtime/broker/engine-drive.d.ts +26 -0
  223. package/dist/core/runtime/broker/engine-drive.js +257 -0
  224. package/dist/core/runtime/broker/engine-routing.d.ts +143 -0
  225. package/dist/core/runtime/broker/engine-routing.js +211 -0
  226. package/dist/core/runtime/broker/event-projection.js +3 -2
  227. package/dist/core/runtime/broker/fault-retry.js +5 -4
  228. package/dist/core/runtime/broker/frame-client.d.ts +38 -0
  229. package/dist/core/runtime/broker/frame-client.js +129 -0
  230. package/dist/core/runtime/broker/frame-dispatch.d.ts +12 -189
  231. package/dist/core/runtime/broker/frame-dispatch.js +89 -1476
  232. package/dist/core/runtime/broker/frame-engine-control.d.ts +20 -0
  233. package/dist/core/runtime/broker/frame-engine-control.js +231 -0
  234. package/dist/core/runtime/broker/frame-memory-refs.d.ts +19 -0
  235. package/dist/core/runtime/broker/frame-memory-refs.js +41 -0
  236. package/dist/core/runtime/broker/frame-model-query.d.ts +13 -0
  237. package/dist/core/runtime/broker/frame-model-query.js +69 -0
  238. package/dist/core/runtime/broker/frame-read.d.ts +19 -0
  239. package/dist/core/runtime/broker/frame-read.js +116 -0
  240. package/dist/core/runtime/broker/frame-replies.d.ts +24 -0
  241. package/dist/core/runtime/broker/frame-replies.js +64 -0
  242. package/dist/core/runtime/broker/frame-session.d.ts +34 -0
  243. package/dist/core/runtime/broker/frame-session.js +203 -0
  244. package/dist/core/runtime/broker/turn-ignition.d.ts +42 -0
  245. package/dist/core/runtime/broker/turn-ignition.js +74 -0
  246. package/dist/core/runtime/broker-extension-render.js +9 -85
  247. package/dist/core/runtime/broker-protocol.js +0 -4
  248. package/dist/core/runtime/broker-sdk.js +2 -1
  249. package/dist/core/runtime/broker.d.ts +1 -1
  250. package/dist/core/runtime/broker.js +5 -23
  251. package/dist/core/runtime/fault.js +3 -2
  252. package/dist/core/runtime/headless-pi.js +3 -2
  253. package/dist/core/runtime/host.js +2 -3
  254. package/dist/core/runtime/kickoff.js +0 -4
  255. package/dist/core/runtime/launch-target.js +2 -1
  256. package/dist/core/runtime/launch.js +3 -6
  257. package/dist/core/runtime/lifecycle.js +17 -26
  258. package/dist/core/runtime/managed-provider-cooling.d.ts +1 -1
  259. package/dist/core/runtime/naming.js +0 -2
  260. package/dist/core/runtime/nodes.js +2 -1
  261. package/dist/core/runtime/persona.js +0 -6
  262. package/dist/core/runtime/placement-tmux.js +2 -5
  263. package/dist/core/runtime/placement.js +4 -11
  264. package/dist/core/runtime/recap.js +4 -3
  265. package/dist/core/runtime/reopen.js +4 -7
  266. package/dist/core/runtime/reset.js +0 -4
  267. package/dist/core/runtime/resume-root.js +0 -2
  268. package/dist/core/runtime/revive.js +17 -13
  269. package/dist/core/runtime/session-list-cache.js +2 -2
  270. package/dist/core/runtime/shell-expansion.js +0 -2
  271. package/dist/core/runtime/spawn.js +0 -2
  272. package/dist/core/runtime/stream-watchdog.d.ts +0 -1
  273. package/dist/core/runtime/stream-watchdog.js +13 -18
  274. package/dist/core/runtime/tmux-bindings.js +0 -2
  275. package/dist/core/runtime/tmux-driver.d.ts +12 -0
  276. package/dist/core/runtime/tmux-driver.js +18 -13
  277. package/dist/core/runtime/warm-pool.js +0 -4
  278. package/dist/core/scope.js +4 -3
  279. package/dist/core/spawn.js +2 -3
  280. package/dist/core/subscription-state.js +10 -17
  281. package/dist/core/substrate/__tests__/surface-match-pre-command.test.js +3 -3
  282. package/dist/core/substrate/render.js +0 -12
  283. package/dist/core/substrate/schema.js +0 -12
  284. package/dist/core/substrate/surface-match.js +0 -2
  285. package/dist/core/termrender/display.js +2 -1
  286. package/dist/core/tui/page-host.d.ts +3 -3
  287. package/dist/core/tui/page-host.js +4 -4
  288. package/dist/core/tui/panel.js +3 -3
  289. package/dist/core/wake.js +0 -12
  290. package/dist/core/worktree.js +0 -2
  291. package/dist/daemon/__tests__/reconciler-signature.test.d.ts +1 -0
  292. package/dist/daemon/__tests__/reconciler-signature.test.js +36 -0
  293. package/dist/daemon/api/__tests__/seam/api-server.test.js +2 -16
  294. package/dist/daemon/api/__tests__/seam/b10-attach-modelauth.test.js +3 -11
  295. package/dist/daemon/api/__tests__/seam/leaf-api-parity.test.js +0 -6
  296. package/dist/daemon/api/handlers/attach.js +2 -1
  297. package/dist/daemon/api/handlers/broker-ops.js +3 -6
  298. package/dist/daemon/api/handlers/canvas.js +12 -18
  299. package/dist/daemon/api/handlers/feedback-comments.js +0 -8
  300. package/dist/daemon/api/handlers/health.js +3 -2
  301. package/dist/daemon/api/handlers/human-requests.js +0 -10
  302. package/dist/daemon/api/handlers/human.js +1 -5
  303. package/dist/daemon/api/handlers/inbox.js +0 -18
  304. package/dist/daemon/api/handlers/memory.js +1 -1
  305. package/dist/daemon/api/handlers/messages.js +4 -8
  306. package/dist/daemon/api/handlers/nodes.js +10 -21
  307. package/dist/daemon/api/handlers/reports.js +1 -1
  308. package/dist/daemon/api/handlers/reviews.js +1 -6
  309. package/dist/daemon/api/map.js +0 -6
  310. package/dist/daemon/api/router.js +0 -4
  311. package/dist/daemon/api/server.js +2 -5
  312. package/dist/daemon/boot-migrations.d.ts +14 -0
  313. package/dist/daemon/boot-migrations.js +31 -0
  314. package/dist/daemon/cron/armed-context.d.ts +21 -0
  315. package/dist/daemon/cron/armed-context.js +100 -0
  316. package/dist/daemon/cron/capture.d.ts +20 -0
  317. package/dist/daemon/cron/capture.js +45 -0
  318. package/dist/daemon/cron/disposition.d.ts +19 -0
  319. package/dist/daemon/cron/disposition.js +52 -0
  320. package/dist/daemon/cron/escalation.d.ts +12 -0
  321. package/dist/daemon/cron/escalation.js +124 -0
  322. package/dist/daemon/cron/held.d.ts +4 -0
  323. package/dist/daemon/cron/held.js +20 -0
  324. package/dist/daemon/cron/lease-recovery.d.ts +3 -0
  325. package/dist/daemon/cron/lease-recovery.js +119 -0
  326. package/dist/daemon/cron/live-runs.d.ts +11 -0
  327. package/dist/daemon/cron/live-runs.js +23 -0
  328. package/dist/daemon/cron/passes.d.ts +11 -0
  329. package/dist/daemon/cron/passes.js +78 -0
  330. package/dist/daemon/cron/settle.d.ts +28 -0
  331. package/dist/daemon/cron/settle.js +37 -0
  332. package/dist/daemon/cron/sinks.d.ts +2 -0
  333. package/dist/daemon/cron/sinks.js +100 -0
  334. package/dist/daemon/cron-run.d.ts +5 -41
  335. package/dist/daemon/cron-run.js +193 -882
  336. package/dist/daemon/crtrd-cli.js +2 -1
  337. package/dist/daemon/crtrd.js +22 -22
  338. package/dist/daemon/fleet.js +0 -4
  339. package/dist/daemon/human/deliver-action.js +2 -1
  340. package/dist/daemon/human/sweep.js +15 -4
  341. package/dist/daemon/manage.d.ts +2 -2
  342. package/dist/daemon/manage.js +4 -13
  343. package/dist/daemon/pidfile.js +2 -2
  344. package/dist/daemon/reconcilers/broker-supervision.js +26 -29
  345. package/dist/daemon/reconcilers/dormant-inbox.d.ts +1 -1
  346. package/dist/daemon/reconcilers/dormant-inbox.js +2 -1
  347. package/dist/daemon/reconcilers/live-obligation.js +2 -2
  348. package/dist/daemon/reconcilers/pending-review-submit.d.ts +1 -1
  349. package/dist/daemon/reconcilers/pending-review-submit.js +2 -1
  350. package/dist/daemon/reconcilers/storage-maintenance.d.ts +2 -1
  351. package/dist/daemon/reconcilers/storage-maintenance.js +2 -1
  352. package/dist/daemon/review/companion.js +2 -2
  353. package/dist/daemon/review/sweep.js +8 -3
  354. package/dist/migrations/001-surfaces-frontmatter.js +0 -6
  355. package/dist/migrations/corpus.js +2 -5
  356. package/dist/migrations/registry.js +15 -2
  357. package/dist/pi-extensions/__tests__/canvas-stophook-agentend.test.js +0 -4
  358. package/dist/pi-extensions/broker-local.js +2 -1
  359. package/dist/pi-extensions/canvas-bash-valve.js +3 -17
  360. package/dist/pi-extensions/canvas-context-intro.js +2 -7
  361. package/dist/pi-extensions/canvas-doc-substrate.js +2 -3
  362. package/dist/pi-extensions/canvas-goal-capture.js +2 -1
  363. package/dist/pi-extensions/canvas-inbox-watcher.js +5 -12
  364. package/dist/pi-extensions/canvas-passive-context.js +2 -5
  365. package/dist/pi-extensions/canvas-preview-result.js +2 -1
  366. package/dist/pi-extensions/canvas-prompt-scrub.js +2 -1
  367. package/dist/pi-extensions/canvas-recap.js +3 -16
  368. package/dist/pi-extensions/canvas-review-boundary.js +2 -5
  369. package/dist/pi-extensions/canvas-stophook.js +2 -26
  370. package/dist/pi-extensions/canvas-structured-output.js +2 -1
  371. package/dist/pi-extensions/canvas-tool-guide.js +2 -1
  372. package/dist/shared/__tests__/env-boundary.test.d.ts +1 -0
  373. package/dist/shared/__tests__/env-boundary.test.js +93 -0
  374. package/dist/shared/env.d.ts +68 -0
  375. package/dist/shared/env.js +171 -0
  376. package/package.json +3 -1
  377. package/runtime.lock.json +2 -2
  378. package/dist/core/canvas/render.d.ts +0 -47
  379. package/dist/core/canvas/render.js +0 -245
@@ -0,0 +1,203 @@
1
+ import { execFile } from 'node:child_process';
2
+ import { existsSync, readFileSync, unlinkSync, writeFileSync } from 'node:fs';
3
+ import { tmpdir } from 'node:os';
4
+ import { join } from 'node:path';
5
+ import { captureSessionChatCapabilities } from '../command-surface.js';
6
+ import { isPreBoundaryReviewEntry } from './frame-replies.js';
7
+ /** True iff a parsed JSONL branch-entry line is a hidden (`display:false`)
8
+ * custom message — the general display boundary crossed if left in an
9
+ * exported branch (not just `crtr-memory-refs` guidance; any hidden custom
10
+ * content). Covers both persisted shapes a branch entry can take: a
11
+ * `type:'custom_message'` entry (display at the entry's own top level) and a
12
+ * `type:'message'` entry wrapping a `role:'custom'` message (display nested
13
+ * under `.message`). */
14
+ export function isHiddenJsonlBranchEntry(entry) {
15
+ if (entry === null || typeof entry !== 'object')
16
+ return false;
17
+ const e = entry;
18
+ if (e.type === 'custom_message' && e.display === false)
19
+ return true;
20
+ if (e.type === 'message' && e.message?.role === 'custom' && e.message.display === false)
21
+ return true;
22
+ return false;
23
+ }
24
+ /** Rewrite an already-exported JSONL file in place, dropping every hidden
25
+ * (`display:false`) branch entry while re-chaining `parentId` so the
26
+ * remaining entries stay a single valid linear branch — applying the general
27
+ * display:false boundary to `session.exportToJsonl`'s own output, which
28
+ * serializes every entry verbatim regardless of display. The header line
29
+ * (line 1) is copied through untouched. */
30
+ export function stripHiddenEntriesFromJsonlExport(filePath) {
31
+ const raw = readFileSync(filePath, 'utf8');
32
+ const lines = raw.split('\n').filter((l) => l !== '');
33
+ if (lines.length === 0)
34
+ return;
35
+ const [headerLine, ...entryLines] = lines;
36
+ let prevId = null;
37
+ const kept = [];
38
+ for (const line of entryLines) {
39
+ const entry = JSON.parse(line);
40
+ if (isHiddenJsonlBranchEntry(entry))
41
+ continue;
42
+ kept.push(JSON.stringify({ ...entry, parentId: prevId }));
43
+ if (typeof entry.id === 'string')
44
+ prevId = entry.id;
45
+ }
46
+ writeFileSync(filePath, `${[headerLine, ...kept].join('\n')}\n`);
47
+ }
48
+ export function createSessionHandler(deps) {
49
+ const { registry, replies, rebind, session, boundaryReviewId, memoryRefs, reWelcomeAll } = deps;
50
+ return (client, frame) => {
51
+ switch (frame.type) {
52
+ case 'navigate_tree': {
53
+ if (replies.notWritable(client, 'navigate the session tree'))
54
+ break;
55
+ if (isPreBoundaryReviewEntry(session(), boundaryReviewId, frame.targetId)) {
56
+ replies.reviewBoundaryRefusalTo(client, frame.targetId);
57
+ break;
58
+ }
59
+ // navigateTree rewinds IN-PLACE (same session file, new leaf) and emits no
60
+ // relayed event, so every viewer must be re-snapshotted onto the rewound
61
+ // transcript — same reWelcomeAll the session-replacing ops use. The ack's
62
+ // detail carries the navigated-to user message's text (pi parity: the
63
+ // interactive tree navigator restores it to the editor for re-editing).
64
+ void session()
65
+ .navigateTree(frame.targetId, frame.options)
66
+ .then((r) => {
67
+ if (!r.cancelled)
68
+ reWelcomeAll();
69
+ replies.ackTo(client, 'navigate_tree', !r.cancelled, r.editorText);
70
+ })
71
+ .catch(replies.engineErrorTo(client));
72
+ break;
73
+ }
74
+ case 'reload': {
75
+ if (replies.notWritable(client, 'reload'))
76
+ break;
77
+ // A SUCCESSFUL reload invalidates the memoized ref inventory (work item
78
+ // 3) so the next read-op/submission re-walks the corpus. A FAILED
79
+ // reload (`.catch`) leaves it untouched: nothing about the corpus is
80
+ // known to have changed.
81
+ void session()
82
+ .reload({ beforeSessionStart: () => captureSessionChatCapabilities(session()) })
83
+ .then(() => {
84
+ // pi's reload() re-syncs queue modes from the shared settings.json
85
+ // (its ONE post-bind syncQueueModesFromSettings call site) —
86
+ // re-apply the broker's 'all' modes so a reload cannot silently
87
+ // restore one-at-a-time draining (see rebind.enqueueRebind).
88
+ const agent = session().agent;
89
+ if (agent) {
90
+ agent.steeringMode = 'all';
91
+ agent.followUpMode = 'all';
92
+ }
93
+ memoryRefs.invalidate();
94
+ registry.forEachHelloed((observer) => {
95
+ if (observer.role === 'observer')
96
+ registry.sendFrame(observer, { type: 'resources_reloaded' });
97
+ });
98
+ replies.ackTo(client, 'reload');
99
+ })
100
+ .catch(replies.engineErrorTo(client));
101
+ break;
102
+ }
103
+ case 'export': {
104
+ if (replies.notWritable(client, 'export the session'))
105
+ break;
106
+ if (frame.format === 'jsonl') {
107
+ try {
108
+ const outPath = session().exportToJsonl(frame.path);
109
+ stripHiddenEntriesFromJsonlExport(outPath);
110
+ replies.ackTo(client, 'export');
111
+ }
112
+ catch (err) {
113
+ replies.engineErrorTo(client)(err);
114
+ }
115
+ }
116
+ else {
117
+ void session()
118
+ .exportToHtml(frame.path)
119
+ .then(() => replies.ackTo(client, 'export'))
120
+ .catch(replies.engineErrorTo(client));
121
+ }
122
+ break;
123
+ }
124
+ case 'new_session': {
125
+ if (replies.notWritable(client, 'start a new session'))
126
+ break;
127
+ rebind.runReplacement(client, 'new_session', (rt) => rt.newSession());
128
+ break;
129
+ }
130
+ case 'switch_session': {
131
+ if (replies.notWritable(client, 'switch sessions'))
132
+ break;
133
+ rebind.runReplacement(client, 'switch_session', (rt) => rt.switchSession(frame.path));
134
+ break;
135
+ }
136
+ case 'fork': {
137
+ if (replies.notWritable(client, 'fork the session'))
138
+ break;
139
+ if (isPreBoundaryReviewEntry(session(), boundaryReviewId, frame.entryId)) {
140
+ replies.reviewBoundaryRefusalTo(client, frame.entryId);
141
+ break;
142
+ }
143
+ rebind.runReplacement(client, 'fork', (rt) => rt.fork(frame.entryId));
144
+ break;
145
+ }
146
+ case 'clone': {
147
+ if (replies.notWritable(client, 'clone the session'))
148
+ break;
149
+ rebind.runReplacement(client, 'clone', async (rt) => {
150
+ const sm = session().sessionManager;
151
+ const leafId = sm.getLeafId();
152
+ if (leafId === null) {
153
+ return { cancelled: true, detail: 'no current leaf to clone from' };
154
+ }
155
+ const newPath = sm.createBranchedSession(leafId);
156
+ if (newPath === undefined) {
157
+ return {
158
+ cancelled: true,
159
+ detail: 'createBranchedSession returned no path',
160
+ };
161
+ }
162
+ return rt.switchSession(newPath);
163
+ });
164
+ break;
165
+ }
166
+ case 'share': {
167
+ if (replies.notWritable(client, 'share the session'))
168
+ break;
169
+ const tmpPath = join(tmpdir(), `pi-share-${Date.now()}.html`);
170
+ void session()
171
+ .exportToHtml(tmpPath)
172
+ .then(() => new Promise((resolve, reject) => {
173
+ execFile('gh', ['gist', 'create', '--secret', tmpPath], { timeout: 30_000, maxBuffer: 1024 * 1024 }, (err, stdout) => {
174
+ try {
175
+ if (existsSync(tmpPath))
176
+ unlinkSync(tmpPath);
177
+ }
178
+ catch {
179
+ /* cleanup */
180
+ }
181
+ if (err) {
182
+ reject(err);
183
+ return;
184
+ }
185
+ resolve(stdout.trim());
186
+ });
187
+ }))
188
+ .then((url) => {
189
+ replies.ackTo(client, 'share', true, url);
190
+ })
191
+ .catch((err) => {
192
+ const msg = String(err?.message ?? err);
193
+ replies.ackTo(client, 'share', false, msg.includes('not found') ||
194
+ msg.includes('gh: command not found') ||
195
+ msg.includes('ENOENT')
196
+ ? '`gh` CLI not found — install GitHub CLI and authenticate with `gh auth login`'
197
+ : `share failed: ${msg}`);
198
+ });
199
+ break;
200
+ }
201
+ }
202
+ };
203
+ }
@@ -0,0 +1,42 @@
1
+ import type { BrokerSession } from './read-ops.js';
2
+ /**
3
+ * Prompt dispatches whose promise has not yet settled. `session.prompt()` does
4
+ * async setup (input transforms, model/auth validation, compaction) BEFORE
5
+ * `_isAgentRunActive` flips, so for a short window after a routed deliver the
6
+ * engine reads idle and `session.abort()` is a silent no-op — an interrupt
7
+ * racing that window would miss the turn entirely (verified live). The counter
8
+ * lets a caller distinguish "genuinely idle" from "turn still igniting".
9
+ */
10
+ export type PromptDispatchTracker = {
11
+ track(p: Promise<unknown>): Promise<unknown>;
12
+ pending(): number;
13
+ /** Wait (bounded) for a pending dispatch to reach an abortable turn — or for
14
+ * every dispatch to settle without one (an early return). Returns
15
+ * immediately when the engine is already streaming or nothing is pending. */
16
+ waitUntilAbortable(): Promise<void>;
17
+ };
18
+ export declare function createPromptDispatchTracker(session: () => BrokerSession): PromptDispatchTracker;
19
+ /**
20
+ * The one-shot waiters a `deliver` frame parks until its prompt is ACCEPTED by
21
+ * the engine (the broker's `agent_start`). Every waiter fires once, on the
22
+ * first acceptance after it was added.
23
+ */
24
+ export type TurnAcceptanceGate = {
25
+ waitFor(waiter: () => void): void;
26
+ cancel(waiter: () => void): void;
27
+ notifyAccepted(): void;
28
+ };
29
+ export declare function createTurnAcceptanceGate(): TurnAcceptanceGate;
30
+ /**
31
+ * The launch prompt, held until the session actually has a model. A model-less
32
+ * tier-3 boot leaves it pending; `drivePending` is a no-op until the
33
+ * `reload_auth` re-drive resolves a model, at which point it fires.
34
+ */
35
+ export type KickoffDriver = {
36
+ kickoff(prompt: string): void;
37
+ drivePending(): void;
38
+ };
39
+ export declare function createKickoffDriver(deps: {
40
+ session: () => BrokerSession;
41
+ dispatches: PromptDispatchTracker;
42
+ }): KickoffDriver;
@@ -0,0 +1,74 @@
1
+ import { emitEvent } from '../../events/emit.js';
2
+ import { operationIdContext } from '../../events/operation-id.js';
3
+ import { isUnknownModel } from '../model-selection.js';
4
+ /** How long a caller waits for a dispatched prompt to become abortable, and how
5
+ * often it re-reads the engine while waiting. */
6
+ const ABORTABLE_WAIT_MS = 5_000;
7
+ const ABORTABLE_POLL_MS = 25;
8
+ export function createPromptDispatchTracker(session) {
9
+ let pending = 0;
10
+ return {
11
+ track(p) {
12
+ pending++;
13
+ const tracked = Promise.resolve(p);
14
+ void tracked.finally(() => {
15
+ pending--;
16
+ });
17
+ return tracked;
18
+ },
19
+ pending: () => pending,
20
+ async waitUntilAbortable() {
21
+ if (session().isStreaming || pending === 0)
22
+ return;
23
+ const deadline = Date.now() + ABORTABLE_WAIT_MS;
24
+ while (Date.now() < deadline && !session().isStreaming && pending > 0) {
25
+ await new Promise((resolve) => setTimeout(resolve, ABORTABLE_POLL_MS));
26
+ }
27
+ },
28
+ };
29
+ }
30
+ export function createTurnAcceptanceGate() {
31
+ const waiters = new Set();
32
+ return {
33
+ waitFor: (waiter) => void waiters.add(waiter),
34
+ cancel: (waiter) => void waiters.delete(waiter),
35
+ notifyAccepted() {
36
+ const pending = [...waiters];
37
+ waiters.clear();
38
+ for (const waiter of pending)
39
+ waiter();
40
+ },
41
+ };
42
+ }
43
+ export function createKickoffDriver(deps) {
44
+ const { session, dispatches } = deps;
45
+ let pendingPrompt;
46
+ let inFlight = false;
47
+ const sessionHasNoModel = () => session().model === undefined || isUnknownModel(session().model);
48
+ const drivePending = () => {
49
+ if (pendingPrompt === undefined || inFlight || sessionHasNoModel())
50
+ return;
51
+ const prompt = pendingPrompt;
52
+ pendingPrompt = undefined;
53
+ inFlight = true;
54
+ dispatches.track(operationIdContext.fresh(() => session()
55
+ .prompt(prompt)
56
+ .catch((err) => {
57
+ emitEvent({
58
+ level: 'error',
59
+ event: 'broker.prompt.first_failed',
60
+ error: err,
61
+ });
62
+ })
63
+ .finally(() => {
64
+ inFlight = false;
65
+ })));
66
+ };
67
+ return {
68
+ kickoff(prompt) {
69
+ pendingPrompt = prompt;
70
+ drivePending();
71
+ },
72
+ drivePending,
73
+ };
74
+ }
@@ -1,118 +1,42 @@
1
1
  // Pure broker-side render adapters. crtrd supplies the fixed extension-state
2
2
  // DTO; these functions only combine it with node-local/filesystem information.
3
3
  import { contextDir, nodeDir } from '../canvas/paths.js';
4
- import { execSync, spawnSync } from 'node:child_process';
5
- import { readdirSync } from 'node:fs';
6
- import { hostname } from 'node:os';
7
4
  import { interpolateNodePaths } from '../canvas/paths.js';
8
5
  import { fullName } from '../canvas/labels.js';
9
6
  import { renderKnowledgeForSubject, renderPreferencesForSubject } from '../substrate/render.js';
10
7
  import { renderWorkspaceOpenDocsForSubject } from '../substrate/on-read.js';
11
- import { loadProfileManifest } from '../profiles/manifest.js';
8
+ import { buildProjectContextBlock, contextDirNote as contextDirNoteShared, profileNote as profileNoteShared, relativeLabel as relativeLabelShared, renderGraphMap, buildIdentityAssertion, worktreeNote as worktreeNoteShared, } from './bearings-render.js';
12
9
  export function editorLabelForBrokerNode(node) {
13
10
  const icon = (node.icon ?? '').trim();
14
11
  const handle = node.name !== node.kind ? node.name.trim() : '';
15
12
  const name = handle !== '' ? handle : (node.description ?? '').trim() || node.name;
16
13
  return icon === '' ? name : `${icon} ${name}`;
17
14
  }
18
- function command(cwd, text) {
19
- try {
20
- return execSync(text, { cwd, encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] }).trim();
21
- }
22
- catch {
23
- return '';
24
- }
25
- }
26
15
  /** Same environment-only project context rendered at normal boot, without a
27
16
  * canvas import. */
28
17
  export function buildProjectContextBlockForBroker(cwd) {
29
- let listing;
30
- try {
31
- const entries = readdirSync(cwd, { withFileTypes: true }).sort((a, b) => Number(b.isDirectory()) - Number(a.isDirectory()) || a.name.localeCompare(b.name));
32
- const shown = entries.slice(0, 50).map((entry) => `${entry.name}${entry.isDirectory() ? '/' : ''}`);
33
- listing = entries.length > shown.length ? [...shown, `[+${entries.length - shown.length} more]`] : shown;
34
- }
35
- catch {
36
- listing = ['Directory listing: unavailable (cwd missing or unreadable).'];
37
- }
38
- const tmux = spawnSync('tmux', ['list-sessions'], { encoding: 'utf8', stdio: 'ignore' }).status === 0;
39
- let git = 'Git: not a git repository.';
40
- let worktrees = 'Worktrees: none.';
41
- if (command(cwd, 'git rev-parse --is-inside-work-tree') !== '') {
42
- const branch = command(cwd, 'git branch --show-current') || command(cwd, 'git rev-parse --abbrev-ref HEAD') || 'HEAD';
43
- const sha = command(cwd, 'git rev-parse --short HEAD') || 'unknown';
44
- const changes = command(cwd, 'git status --porcelain').split('\n').filter(Boolean).length;
45
- const raw = command(cwd, 'git worktree list --porcelain').split('\n');
46
- const found = [];
47
- let path = '';
48
- let wtBranch = 'detached HEAD';
49
- for (const line of raw) {
50
- if (line.startsWith('worktree ')) {
51
- if (path !== '')
52
- found.push(`${path} (${wtBranch})`);
53
- path = line.slice(9);
54
- wtBranch = 'detached HEAD';
55
- }
56
- else if (line.startsWith('branch '))
57
- wtBranch = line.slice(7).replace(/^refs\/heads\//, '');
58
- }
59
- if (path !== '')
60
- found.push(`${path} (${wtBranch})`);
61
- git = `Git: ${branch} @ ${sha}; ${changes === 0 ? 'clean' : `${changes} uncommitted change${changes === 1 ? '' : 's'}`}; ${found.length} worktree${found.length === 1 ? '' : 's'}.`;
62
- worktrees = found.length === 0 ? 'Worktrees: none.' : `Worktrees: ${found.join(', ')}`;
63
- }
64
- return `<project_context>\n\n<environment cwd="${cwd.replaceAll('&', '&amp;').replaceAll('"', '&quot;').replaceAll('<', '&lt;')}">\nHost: ${process.platform} ${process.arch} · ${hostname()} · node ${process.version} · tmux server: ${tmux ? 'reachable' : 'none'}\nDirectory:\n${listing.map((line) => ` ${line}`).join('\n')}\n${git}\n${worktrees}\n</environment>\n</project_context>`;
18
+ return buildProjectContextBlock(cwd);
65
19
  }
66
20
  function contextDirNote(nodeId) {
67
- return `Your context directory is \`${contextDir(nodeId)}\` (also in the env as \`$CRTR_CONTEXT_DIR\`) — durable scratch space on disk, and the one place the other nodes on the canvas can read from. Put documents here that you want to share by reference instead of re-explaining them in a prompt: specs, designs, findings, notes worth pointing a sibling, child, or parent at. It is a shared document store, not a task tracker. **Address it by absolute path** — write artifacts as \`$CRTR_CONTEXT_DIR/<name>.md\` (or the full path above). Your working dir is the project, NOT this directory, so a bare relative \`context/...\` lands in the repo, not here — and other nodes can only read what is actually here. When you report a file, report its absolute path.`;
21
+ return contextDirNoteShared(nodeId);
68
22
  }
69
23
  function profileNote(node) {
70
- if (node.profile_id === null || node.profile_id === '')
71
- return '';
72
- try {
73
- const entry = loadProfileManifest(node.profile_id);
74
- if (entry.manifest.projects.length === 0)
75
- return '';
76
- return `Your working dir is \`${node.cwd}\`. Your profile **${entry.manifest.name}** also has purview over: ${entry.manifest.projects.map((project) => project.path).join(', ')}.`;
77
- }
78
- catch {
79
- return '';
80
- }
24
+ return profileNoteShared(node.cwd, node.profile_id);
81
25
  }
82
26
  function relativeLabel(node) {
83
- const desc = fullName(node);
84
- const descPart = desc !== '' && desc !== node.kind && desc !== node.name ? ` — ${desc}` : '';
85
- return `${node.node_id} (${node.kind} · ${node.status})${descPart}`;
27
+ return relativeLabelShared(node);
86
28
  }
87
29
  function graphMap(state) {
88
30
  const { ancestors, children, node } = state;
89
- if (ancestors.length === 0 && children.length === 0)
90
- return '';
91
- const indent = (depth) => ' '.repeat(depth);
92
- const lines = ['Your place in the canvas graph (ancestry above you, your direct children below):'];
93
- ancestors.forEach((ancestor, index) => lines.push(`${indent(index)}${index === 0 ? '' : '└ '}${relativeLabel(ancestor)}`));
94
- const depth = ancestors.length;
95
- lines.push(`${indent(depth)}${depth === 0 ? '' : '└ '}● you: ${node.node_id} (${node.kind}, ${node.mode})`);
96
- const shown = children.slice(0, 5);
97
- shown.forEach((child, index) => lines.push(`${indent(depth + 1)}${index === shown.length - 1 && children.length === shown.length ? '└ ' : '├ '}${relativeLabel(child)}`));
98
- if (children.length > shown.length)
99
- lines.push(`${indent(depth + 1)}└ +${children.length - shown.length} more`);
100
- return lines.join('\n');
31
+ return renderGraphMap(node.node_id, node.kind, node.mode, ancestors, children);
101
32
  }
102
33
  function identity(state) {
103
34
  const { node, fork_source: source } = state;
104
- const lines = [`You are node ${node.node_id} — kind ${node.kind}, mode ${node.mode}.`];
105
- if (node.fork_from !== null && node.fork_from !== '') {
106
- const sourceLabel = source === null ? node.fork_from : `${node.fork_from} ("${fullName(source)}")`;
107
- lines.push('', `You are a new FORK of ${sourceLabel}, not ${sourceLabel}. Pi copied the earlier conversation into this session. Treat that first-person history as reference material, not your own past.`);
108
- }
109
- return lines.join('\n');
35
+ const sourceLabel = source === null ? (node.fork_from ?? '') : `${node.fork_from} ("${fullName(source)}")`;
36
+ return buildIdentityAssertion(node.node_id, node.kind, node.mode, node.fork_from, sourceLabel);
110
37
  }
111
38
  function worktreeNote(node) {
112
- const worktree = node.managed_worktree;
113
- if (worktree === null || worktree === undefined || worktree.state !== 'open')
114
- return '';
115
- return `Branch: \`${worktree.branch}\`\nWorktree: \`${worktree.path}\`\nBase: \`${worktree.base_ref}\` @ \`${worktree.base_sha}\`\nCommit work here. Land it with \`crtr node worktree close\` before finishing. Auto-drop applies only when the worktree is clean (no uncommitted changes) and nothing remains to land (zero commits ahead of the base, or its commits are already contained in the base); unlanded commits (commits not already contained in the base) or uncommitted changes require an explicit close/landing decision.`;
39
+ return worktreeNoteShared(node.managed_worktree);
116
40
  }
117
41
  /** The companion note — rendered ONLY when a node carries its immutable
118
42
  * companion binding. Names the target (the document under review, or the page
@@ -16,9 +16,7 @@
16
16
  // pi. pi names the correlation field `id` (design §5.2 wrote `request_id`); we
17
17
  // follow pi.
18
18
  import { StringDecoder } from 'node:string_decoder';
19
- // ---------------------------------------------------------------------------
20
19
  // Client → broker frames
21
- // ---------------------------------------------------------------------------
22
20
  /** Largest transcript window a client may request in its welcome snapshot. */
23
21
  export const SNAPSHOT_TAIL_MAX = 500;
24
22
  /** Invalid requests preserve the complete welcome; valid requests are capped. */
@@ -27,10 +25,8 @@ export function normalizeSnapshotTail(value) {
27
25
  return undefined;
28
26
  return Math.min(value, SNAPSHOT_TAIL_MAX);
29
27
  }
30
- // ---------------------------------------------------------------------------
31
28
  // Newline-delimited JSON codec (the framing pi solves with `jsonl`, reproduced
32
29
  // locally so the protocol module owns no transport dependency)
33
- // ---------------------------------------------------------------------------
34
30
  /** Encode one frame as a single newline-terminated JSON line. */
35
31
  export function encodeFrame(frame) {
36
32
  return JSON.stringify(frame) + '\n';
@@ -10,6 +10,7 @@
10
10
  import { execFileSync } from 'node:child_process';
11
11
  import { readFileSync, statSync, writeFileSync } from 'node:fs';
12
12
  import { join } from 'node:path';
13
+ import { envBrokerEngine } from '../../shared/env.js';
13
14
  import { createAgentSessionServices, createAgentSessionFromServices, createAgentSessionRuntime, SessionManager, VERSION, } from '@earendil-works/pi-coding-agent';
14
15
  import { crtrHome } from '../canvas/paths.js';
15
16
  import { emitEvent } from '../events/emit.js';
@@ -24,7 +25,7 @@ export { createAgentSessionServices, createAgentSessionFromServices, createAgent
24
25
  * through here so the seam stays a single, auditable swap.
25
26
  */
26
27
  export async function loadBrokerEngine() {
27
- const spec = process.env.CRTR_BROKER_ENGINE ?? '@earendil-works/pi-coding-agent';
28
+ const spec = envBrokerEngine();
28
29
  const mod = (await import(spec));
29
30
  if (typeof mod.createAgentSessionServices !== 'function' ||
30
31
  typeof mod.createAgentSessionFromServices !== 'function' ||
@@ -7,7 +7,7 @@ import { type BrokerToClient, type RpcExtensionUIRequest, type RpcExtensionUIRes
7
7
  /** A review companion may only navigate or fork within a branch whose ancestry
8
8
  * has crossed its durable transcript boundary. The broker sends this typed local
9
9
  * failure as a dedicated error frame without invoking pi's tree mutation. */
10
- export { ReviewBoundaryNavigationError, REVIEW_BOUNDARY_NAVIGATION_CODE } from './broker/frame-dispatch.js';
10
+ export { ReviewBoundaryNavigationError, REVIEW_BOUNDARY_NAVIGATION_CODE } from './broker/frame-replies.js';
11
11
  export type { SubscriptionGeneration } from './broker/rebind.js';
12
12
  /** A blocking dialog awaiting a response from ANY writable client, the
13
13
  * broker-side default timeout, or the engine's abort. The dialog is fanned out
@@ -16,6 +16,7 @@
16
16
  // + model registry are real SDK construction helpers (NOT part of the swappable
17
17
  // engine seam — the fake engine receives the real, extension-loaded loader so
18
18
  // the real canvas-stophook fires session_start).
19
+ import { envModelIntent, envNodeCwd, envProfileId } from '../../shared/env.js';
19
20
  import { createServer } from 'node:net';
20
21
  import { existsSync, readFileSync, unlinkSync } from 'node:fs';
21
22
  import { join } from 'node:path';
@@ -54,7 +55,7 @@ import { FrameOverflowError, normalizeSnapshotTail, } from './broker-protocol.js
54
55
  /** A review companion may only navigate or fork within a branch whose ancestry
55
56
  * has crossed its durable transcript boundary. The broker sends this typed local
56
57
  * failure as a dedicated error frame without invoking pi's tree mutation. */
57
- export { ReviewBoundaryNavigationError, REVIEW_BOUNDARY_NAVIGATION_CODE } from './broker/frame-dispatch.js';
58
+ export { ReviewBoundaryNavigationError, REVIEW_BOUNDARY_NAVIGATION_CODE } from './broker/frame-replies.js';
58
59
  /**
59
60
  * pi 0.82 made `ModelRuntime` the canonical model/auth surface and demoted
60
61
  * `ModelRegistry` to the synchronous facade over it (`find`/`getAll`/
@@ -83,9 +84,7 @@ function formatExactModelSpec(model, thinkingLevel) {
83
84
  const base = formatModelSpec(model, undefined);
84
85
  return base === null ? null : `${base}:${thinkingLevel === undefined || thinkingLevel === '' ? 'off' : thinkingLevel}`;
85
86
  }
86
- // ---------------------------------------------------------------------------
87
87
  // Tunables (backpressure / dialog anti-deadlock)
88
- // ---------------------------------------------------------------------------
89
88
  /** Broker-side default dialog timeout (anti-deadlock). When an extension
90
89
  * dialog is forwarded to every writable client, the broker ALWAYS arms a timeout
91
90
  * (this default, or a shorter per-dialog `opts.timeout` if the extension passed
@@ -93,14 +92,12 @@ function formatExactModelSpec(model, thinkingLevel) {
93
92
  * — can never hang the agent turn forever. On fire it resolves to the SAFE default
94
93
  * (deny for confirm; cancel/undefined for select/input/editor). */
95
94
  const DEFAULT_DIALOG_TIMEOUT_MS = 120_000; // 120 s
96
- // ---------------------------------------------------------------------------
97
95
  // M3 (scout mq5thyli): the active engine session, exposed so broker-cli's FATAL
98
96
  // handlers (uncaughtException / unhandledRejection / the runBroker reject) can
99
97
  // dispose it before process.exit. The bash tool spawns children `detached` (own
100
98
  // pgid); only session.dispose() (→ abortBash / agent.abort → killProcessTree)
101
99
  // reaps them. The graceful exit path already disposes; without this the crash
102
100
  // path would `process.exit(1)` and ORPHAN every in-flight bash subprocess.
103
- // ---------------------------------------------------------------------------
104
101
  let activeSession = null;
105
102
  /** Dispose the live engine session if one exists (idempotent). Called by
106
103
  * broker-cli's fatal handlers before exit so a crash never orphans detached
@@ -117,9 +114,7 @@ export function disposeActiveSession() {
117
114
  }
118
115
  }
119
116
  }
120
- // ---------------------------------------------------------------------------
121
117
  // runBroker — the broker process body (plan T4 steps 1–6)
122
- // ---------------------------------------------------------------------------
123
118
  export async function runBroker(nodeId, startupAt = process.hrtime.bigint(), startupTs = new Date().toISOString()) {
124
119
  // A clean-abort certificate belongs only to the dead broker that wrote it.
125
120
  // Fleet classification consumes it as proof that the dead broker had a turn
@@ -275,9 +270,7 @@ export async function runBroker(nodeId, startupAt = process.hrtime.bigint(), sta
275
270
  // N3: the node's display name is re-applied by the rebind lane below so it
276
271
  // survives a session replacement (new_session/switch_session/fork), not only at
277
272
  // boot.
278
- // -------------------------------------------------------------------------
279
273
  // Socket fan-out, dialogs, and transcript tool groups
280
- // -------------------------------------------------------------------------
281
274
  const sockPath = viewSocketPath(nodeId);
282
275
  const registry = new BrokerClientRegistry(nodeId);
283
276
  const pendingDialogs = new Map();
@@ -445,10 +438,8 @@ export async function runBroker(nodeId, startupAt = process.hrtime.bigint(), sta
445
438
  const reWelcomeAll = () => {
446
439
  registry.forEachHelloed(sendWelcome);
447
440
  };
448
- // -------------------------------------------------------------------------
449
441
  // The single exit helper: dispose the engine, close + unlink the
450
442
  // socket, exit 0. Idempotent — every trigger converges here.
451
- // -------------------------------------------------------------------------
452
443
  const completeExit = (status) => {
453
444
  if (disposed)
454
445
  return;
@@ -509,7 +500,6 @@ export async function runBroker(nodeId, startupAt = process.hrtime.bigint(), sta
509
500
  }
510
501
  completeExit(status);
511
502
  };
512
- // -------------------------------------------------------------------------
513
503
  // Extension-dialog routing (C2). makeBrokerUiContext owns the dialogPromise;
514
504
  // here we just give it the three broker-side hooks it needs: the live set of
515
505
  // writable clients (empty = nobody can answer → noOp fallback), how to forward a
@@ -517,7 +507,6 @@ export async function runBroker(nodeId, startupAt = process.hrtime.bigint(), sta
517
507
  // first settles through. The zero-writable path NEVER hangs and NEVER waits on a
518
508
  // per-dialog timeout (design §5.4's timeout premise is false — see
519
509
  // makeBrokerUiContext).
520
- // -------------------------------------------------------------------------
521
510
  uiContext = makeBrokerUiContext({
522
511
  writable: () => registry.writableClients(),
523
512
  forward: (client, request) => registry.sendFrame(client, request),
@@ -542,9 +531,7 @@ export async function runBroker(nodeId, startupAt = process.hrtime.bigint(), sta
542
531
  });
543
532
  notifyTurnAccepted = frameDispatch.notifyTurnAccepted;
544
533
  resetFrameState = frameDispatch.resetForSession;
545
- // -------------------------------------------------------------------------
546
534
  // 4 (plan): the socket listener — unlink any stale socket first.
547
- // -------------------------------------------------------------------------
548
535
  try {
549
536
  if (existsSync(sockPath))
550
537
  unlinkSync(sockPath);
@@ -671,7 +658,6 @@ export async function runBroker(nodeId, startupAt = process.hrtime.bigint(), sta
671
658
  process.on('SIGTERM', () => { void abortAndExit('SIGTERM'); });
672
659
  process.on('SIGINT', () => { void abortAndExit('SIGINT'); });
673
660
  }
674
- // ---------------------------------------------------------------------------
675
661
  // snapshotMessages — the catch-up snapshot's ordered message history.
676
662
  //
677
663
  // The broker is the SOLE writer of the node's session `.jsonl`, so the session
@@ -722,7 +708,6 @@ export function snapshotMessages(session, boundaryReviewId) {
722
708
  }
723
709
  return messages === undefined ? undefined : visibleMessages(messages, { boundaryReviewId });
724
710
  }
725
- // ---------------------------------------------------------------------------
726
711
  // resolveLaunchModel — the SOLE launch model-selection policy, module-scoped
727
712
  // so both buildForManager (initial boot, inside buildBrokerSession below) and
728
713
  // the reload_auth re-drive (inside runBroker, a DIFFERENT function scope) can
@@ -731,15 +716,14 @@ export function snapshotMessages(session, boundaryReviewId) {
731
716
  // viable at all (tier 3) — this is NOT a fault; the caller decides what to do
732
717
  // with it (construct a model-less session at boot, or leave a running session
733
718
  // alone on a failed re-drive attempt).
734
- // ---------------------------------------------------------------------------
735
719
  async function resolveLaunchModel(services, cfg) {
736
- const routeRequest = modelRequestFromConfig(cfg.model, process.env['CRTR_MODEL_INTENT'], cfg.modelExact === true);
720
+ const routeRequest = modelRequestFromConfig(cfg.model, envModelIntent(), cfg.modelExact === true);
737
721
  if (routeRequest === undefined)
738
722
  return undefined;
739
723
  const registry = registryOf(services);
740
724
  const routeOptions = {
741
- cwd: process.env['CRTR_NODE_CWD'] ?? cfg.cwd,
742
- profileId: process.env['CRTR_PROFILE_ID'],
725
+ cwd: envNodeCwd() ?? cfg.cwd,
726
+ profileId: envProfileId(),
743
727
  };
744
728
  const unknown = unregisteredConcreteModel(routeRequest, registry, routeOptions);
745
729
  if (unknown !== undefined) {
@@ -812,11 +796,9 @@ function modelLessRegistry(real) {
812
796
  });
813
797
  return { runtime, reveal: () => { masked = false; } };
814
798
  }
815
- // ---------------------------------------------------------------------------
816
799
  // buildBrokerSession (plan T4 steps 2–4) — turn the launch recipe into a live
817
800
  // engine session via the pi SDK SERVICES path. Exported so the C3/C4 real-SDK
818
801
  // regression tests can drive the EXACT production wiring (not the mock).
819
- // ---------------------------------------------------------------------------
820
802
  export async function buildBrokerSession(engine, cfg) {
821
803
  // 2. Build cwd-bound runtime services via the SERVICES path (C3) — NOT plain
822
804
  // createAgentSession. createAgentSessionServices builds + reloads the