@north-light/crouter 0.3.170 → 0.3.172

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 (670) hide show
  1. package/dist/api/client.d.ts +37 -11
  2. package/dist/api/client.js +84 -15
  3. package/dist/api/dto/human.d.ts +53 -39
  4. package/dist/api/dto/human.js +2 -3
  5. package/dist/api/dto/inbox.d.ts +0 -1
  6. package/dist/api/dto/nodes.d.ts +12 -0
  7. package/dist/api/dto/review-comments.d.ts +137 -0
  8. package/dist/api/dto/review-comments.js +5 -0
  9. package/dist/api/dto/reviews.d.ts +109 -0
  10. package/dist/api/dto/reviews.js +5 -0
  11. package/dist/api/index.d.ts +2 -0
  12. package/dist/api/index.js +2 -0
  13. package/dist/api/routes.d.ts +17 -4
  14. package/dist/api/routes.js +21 -5
  15. package/dist/build-root.d.ts +1 -16
  16. package/dist/build-root.js +1 -25
  17. package/dist/builtin-memory/00-runtime-base.md +7 -5
  18. package/dist/builtin-memory/04-base-worker.md +1 -1
  19. package/dist/builtin-memory/04-orchestration-kernel.md +7 -7
  20. package/dist/builtin-memory/05-kinds/design/00-base.md +1 -1
  21. package/dist/builtin-memory/05-kinds/design/01-orchestrator.md +1 -1
  22. package/dist/builtin-memory/05-kinds/developer/00-base.md +1 -1
  23. package/dist/builtin-memory/05-kinds/explore/00-base.md +1 -1
  24. package/dist/builtin-memory/05-kinds/plan/00-base.md +1 -1
  25. package/dist/builtin-memory/05-kinds/plan/01-orchestrator.md +1 -1
  26. package/dist/builtin-memory/05-kinds/review/00-base.md +1 -1
  27. package/dist/builtin-memory/05-kinds/review/companion/00-base.md +20 -0
  28. package/dist/builtin-memory/05-kinds/spec/00-base.md +3 -7
  29. package/dist/builtin-memory/05-kinds/spec/01-orchestrator.md +3 -3
  30. package/dist/builtin-memory/05-kinds/spec/requirements.md +3 -3
  31. package/dist/builtin-memory/design.md +2 -2
  32. package/dist/builtin-memory/insights/capture.md +50 -0
  33. package/dist/builtin-memory/insights/init.md +45 -0
  34. package/dist/builtin-memory/insights/listen.md +9 -0
  35. package/dist/builtin-memory/internal/agent-shaping.md +2 -2
  36. package/dist/builtin-memory/internal/nodes-and-canvas.md +1 -1
  37. package/dist/builtin-memory/internal/plugins.md +12 -11
  38. package/dist/builtin-memory/internal/storage-tiers.md +2 -2
  39. package/dist/builtin-memory/{planning.md → plan/roadmap.md} +3 -3
  40. package/dist/builtin-memory/plan.md +19 -0
  41. package/dist/builtin-memory/spec/guide.md +38 -0
  42. package/dist/builtin-memory/spec/requirements.md +31 -0
  43. package/dist/builtin-memory/spec/roadmap.md +35 -0
  44. package/dist/builtin-memory/spec.md +10 -86
  45. package/dist/builtin-pi-packages/pi-crtr-extensions/README.md +3 -4
  46. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/__tests__/provider-rotation.test.ts +49 -5
  47. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/provider-rotation.d.ts +56 -0
  48. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/provider-rotation.js +920 -0
  49. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/provider-rotation.ts +221 -156
  50. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/statusline.ts +4 -5
  51. package/dist/cli.js +2 -6
  52. package/dist/clients/attach/__tests__/attach-chrome-remote.test.js +7 -35
  53. package/dist/clients/attach/__tests__/attach-keybindings.test.js +32 -16
  54. package/dist/clients/attach/__tests__/context-message.test.js +142 -6
  55. package/dist/clients/attach/__tests__/crtr-output.test.js +29 -45
  56. package/dist/clients/attach/__tests__/diagram.test.js +1 -1
  57. package/dist/clients/attach/__tests__/file-review-focus.test.js +48 -0
  58. package/dist/clients/attach/__tests__/group-activity.test.js +266 -0
  59. package/dist/clients/attach/__tests__/input-controller-extension-command.test.js +32 -0
  60. package/dist/clients/attach/__tests__/session-identity-refresh.test.js +32 -0
  61. package/dist/clients/attach/__tests__/titled-editor-preview.test.js +1 -2
  62. package/dist/clients/attach/chrome/header.d.ts +10 -0
  63. package/dist/clients/attach/chrome/header.js +30 -0
  64. package/dist/clients/attach/chrome/recap.d.ts +7 -0
  65. package/dist/clients/attach/chrome/recap.js +19 -0
  66. package/dist/clients/attach/chrome/status-line.d.ts +3 -0
  67. package/dist/clients/attach/chrome/status-line.js +5 -0
  68. package/dist/clients/attach/chrome/widgets.js +11 -21
  69. package/dist/clients/attach/command.js +11 -7
  70. package/dist/clients/attach/config.d.ts +4 -0
  71. package/dist/clients/attach/config.js +3 -0
  72. package/dist/clients/attach/input/controller.d.ts +19 -0
  73. package/dist/clients/attach/input/controller.js +62 -28
  74. package/dist/clients/attach/input/titled-editor.d.ts +0 -4
  75. package/dist/clients/attach/input/titled-editor.js +2 -10
  76. package/dist/clients/attach/overlays/file-review.d.ts +9 -5
  77. package/dist/clients/attach/overlays/file-review.js +9 -70
  78. package/dist/clients/attach/overlays/help.d.ts +33 -0
  79. package/dist/clients/attach/overlays/help.js +204 -0
  80. package/dist/clients/attach/render/activity-slot.d.ts +4 -0
  81. package/dist/clients/attach/render/activity-slot.js +13 -0
  82. package/dist/clients/attach/render/chat-view.d.ts +117 -12
  83. package/dist/clients/attach/render/chat-view.js +604 -135
  84. package/dist/clients/attach/render/context-message.d.ts +2 -1
  85. package/dist/clients/attach/render/context-message.js +17 -13
  86. package/dist/clients/attach/render/crtr-output.js +1 -3
  87. package/dist/clients/attach/render/diagram.js +2 -2
  88. package/dist/clients/attach/render/edit-diff.d.ts +4 -1
  89. package/dist/clients/attach/render/edit-diff.js +7 -11
  90. package/dist/clients/attach/render/group-activity.d.ts +32 -0
  91. package/dist/clients/attach/render/group-activity.js +206 -0
  92. package/dist/clients/attach/render/group-recap.d.ts +10 -0
  93. package/dist/clients/attach/render/group-recap.js +93 -0
  94. package/dist/clients/attach/render/html-markdown.d.ts +17 -0
  95. package/dist/clients/attach/render/html-markdown.js +228 -0
  96. package/dist/clients/attach/render/measured-container.d.ts +96 -0
  97. package/dist/clients/attach/render/measured-container.js +322 -0
  98. package/dist/clients/attach/render/scroll-animation.d.ts +8 -0
  99. package/dist/clients/attach/render/scroll-animation.js +80 -0
  100. package/dist/clients/attach/render/tool-calls.js +6 -6
  101. package/dist/clients/attach/render/transcript-copy.d.ts +9 -0
  102. package/dist/clients/attach/render/transcript-copy.js +81 -0
  103. package/dist/clients/attach/render/viewport.d.ts +29 -0
  104. package/dist/clients/attach/render/viewport.js +159 -0
  105. package/dist/clients/attach/session/bindings.d.ts +10 -7
  106. package/dist/clients/attach/session/bindings.js +23 -8
  107. package/dist/clients/attach/session/connection.js +4 -5
  108. package/dist/clients/attach/session/context.d.ts +6 -4
  109. package/dist/clients/attach/session/frame.d.ts +34 -0
  110. package/dist/clients/attach/session/frame.js +176 -0
  111. package/dist/clients/attach/session/frames.d.ts +4 -1
  112. package/dist/clients/attach/session/frames.js +14 -1
  113. package/dist/clients/attach/session/identity.js +4 -3
  114. package/dist/clients/attach/session/input-wiring.d.ts +14 -4
  115. package/dist/clients/attach/session/input-wiring.js +15 -13
  116. package/dist/clients/attach/session/invariant.d.ts +9 -0
  117. package/dist/clients/attach/session/invariant.js +15 -0
  118. package/dist/clients/attach/session/keys.d.ts +10 -2
  119. package/dist/clients/attach/session/keys.js +13 -5
  120. package/dist/clients/attach/session/layout.d.ts +10 -8
  121. package/dist/clients/attach/session/layout.js +33 -34
  122. package/dist/clients/attach/session/mouse.d.ts +18 -0
  123. package/dist/clients/attach/session/mouse.js +64 -0
  124. package/dist/clients/attach/session/pane-tag.d.ts +3 -0
  125. package/dist/clients/attach/session/pane-tag.js +51 -8
  126. package/dist/clients/attach/session/reconnect.d.ts +3 -0
  127. package/dist/clients/attach/session/reconnect.js +42 -5
  128. package/dist/clients/attach/session/surface-frame.d.ts +20 -0
  129. package/dist/clients/attach/session/surface-frame.js +73 -0
  130. package/dist/clients/attach/session/surface-host.d.ts +64 -0
  131. package/dist/clients/attach/session/surface-host.js +255 -0
  132. package/dist/clients/attach/session/whip.d.ts +17 -0
  133. package/dist/clients/attach/session/whip.js +334 -0
  134. package/dist/clients/attach/slash/dispatch.d.ts +13 -1
  135. package/dist/clients/attach/slash/dispatch.js +51 -29
  136. package/dist/clients/attach/viewer.js +907 -819
  137. package/dist/clients/conversation/conversation.d.ts +65 -0
  138. package/dist/clients/conversation/conversation.js +318 -0
  139. package/dist/clients/conversation/frames.d.ts +30 -0
  140. package/dist/clients/conversation/frames.js +52 -0
  141. package/dist/clients/conversation/region.d.ts +51 -0
  142. package/dist/clients/conversation/region.js +265 -0
  143. package/dist/clients/conversation/submit.d.ts +38 -0
  144. package/dist/clients/conversation/submit.js +51 -0
  145. package/dist/clients/inbox/__tests__/inbox-controller.test.js +247 -0
  146. package/dist/clients/inbox/__tests__/mount-panel.test.js +776 -0
  147. package/dist/clients/inbox/controller.d.ts +79 -0
  148. package/dist/clients/inbox/controller.js +448 -0
  149. package/dist/clients/inbox/deck-adapter.d.ts +29 -0
  150. package/dist/clients/inbox/deck-adapter.js +62 -0
  151. package/dist/clients/inbox/layout.d.ts +8 -0
  152. package/dist/clients/inbox/layout.js +9 -0
  153. package/dist/clients/inbox/resolve.d.ts +10 -0
  154. package/dist/clients/inbox/resolve.js +34 -0
  155. package/dist/clients/inbox/review/__tests__/editor-roundtrip.test.js +29 -0
  156. package/dist/clients/inbox/review/__tests__/remap.test.js +196 -0
  157. package/dist/clients/inbox/review/__tests__/review-core.test.js +341 -0
  158. package/dist/clients/inbox/review/anchors.d.ts +41 -0
  159. package/dist/clients/inbox/review/anchors.js +89 -0
  160. package/dist/clients/inbox/review/columns.d.ts +22 -0
  161. package/dist/clients/inbox/review/columns.js +40 -0
  162. package/dist/clients/inbox/review/comments-client.d.ts +66 -0
  163. package/dist/clients/inbox/review/comments-client.js +98 -0
  164. package/dist/clients/inbox/review/composition.d.ts +60 -0
  165. package/dist/clients/inbox/review/composition.js +278 -0
  166. package/dist/clients/inbox/review/document-region.d.ts +120 -0
  167. package/dist/clients/inbox/review/document-region.js +902 -0
  168. package/dist/clients/inbox/review/frame.d.ts +45 -0
  169. package/dist/clients/inbox/review/frame.js +215 -0
  170. package/dist/clients/inbox/review/keys.d.ts +67 -0
  171. package/dist/clients/inbox/review/keys.js +130 -0
  172. package/dist/clients/inbox/review/launch.d.ts +10 -0
  173. package/dist/clients/inbox/review/launch.js +129 -0
  174. package/dist/clients/inbox/review/live-source.d.ts +24 -0
  175. package/dist/clients/inbox/review/live-source.js +160 -0
  176. package/dist/clients/inbox/review/nvim.d.ts +13 -0
  177. package/dist/clients/inbox/review/nvim.js +42 -0
  178. package/dist/clients/inbox/review/remap.d.ts +51 -0
  179. package/dist/clients/inbox/review/remap.js +157 -0
  180. package/dist/clients/inbox/review/review-client.d.ts +30 -0
  181. package/dist/clients/inbox/review/review-client.js +107 -0
  182. package/dist/clients/inbox/review/roundtrip.d.ts +5 -0
  183. package/dist/clients/inbox/review/roundtrip.js +36 -0
  184. package/dist/clients/inbox/review/state.d.ts +143 -0
  185. package/dist/clients/inbox/review/state.js +282 -0
  186. package/dist/clients/inbox/review-adapter.d.ts +29 -0
  187. package/dist/clients/inbox/review-adapter.js +92 -0
  188. package/dist/clients/inbox/review-visibility.d.ts +5 -0
  189. package/dist/clients/inbox/review-visibility.js +19 -0
  190. package/dist/clients/inbox/surface.d.ts +19 -0
  191. package/dist/clients/inbox/surface.js +128 -0
  192. package/dist/clients/inbox/tui/ansi.d.ts +66 -0
  193. package/dist/clients/inbox/tui/ansi.js +317 -0
  194. package/dist/clients/inbox/tui/clipboard.d.ts +2 -0
  195. package/dist/clients/inbox/tui/clipboard.js +26 -0
  196. package/dist/clients/inbox/tui/input.d.ts +7 -0
  197. package/dist/clients/inbox/tui/input.js +653 -0
  198. package/dist/clients/inbox/tui/keys.d.ts +28 -0
  199. package/dist/clients/inbox/tui/keys.js +111 -0
  200. package/dist/clients/inbox/tui/panel.d.ts +2 -0
  201. package/dist/clients/inbox/tui/panel.js +217 -0
  202. package/dist/clients/inbox/tui/render.d.ts +37 -0
  203. package/dist/clients/inbox/tui/render.js +476 -0
  204. package/dist/clients/inbox/tui/types.d.ts +57 -0
  205. package/dist/clients/inbox/tui/types.js +1 -0
  206. package/dist/clients/inbox/tui.d.ts +6 -0
  207. package/dist/clients/inbox/tui.js +74 -0
  208. package/dist/clients/surfaces/host.d.ts +66 -0
  209. package/dist/clients/surfaces/host.js +1 -0
  210. package/dist/clients/surfaces/region.d.ts +16 -0
  211. package/dist/clients/surfaces/region.js +1 -0
  212. package/dist/clients/web/__tests__/push-engine.test.js +14 -158
  213. package/dist/clients/web/dev-server.d.ts +3 -3
  214. package/dist/clients/web/dev-server.js +9 -23
  215. package/dist/clients/web/events.js +4 -6
  216. package/dist/clients/web/push-engine.d.ts +2 -66
  217. package/dist/clients/web/push-engine.js +30 -161
  218. package/dist/clients/web/push-registry.d.ts +0 -4
  219. package/dist/clients/web/push-registry.js +2 -7
  220. package/dist/clients/web/server.js +3 -35
  221. package/dist/clients/web/web-client/shared/protocol.d.ts +0 -108
  222. package/dist/clients/web/web-cmd.js +2 -2
  223. package/dist/commands/__tests__/human.test.js +27 -15
  224. package/dist/commands/api-client.js +3 -3
  225. package/dist/commands/canvas-browse.js +1 -1
  226. package/dist/commands/canvas-issue.js +1 -1
  227. package/dist/commands/canvas-use.js +1 -1
  228. package/dist/commands/human/doc.d.ts +2 -0
  229. package/dist/commands/human/doc.js +98 -0
  230. package/dist/commands/human/inbox.d.ts +2 -0
  231. package/dist/commands/human/inbox.js +101 -0
  232. package/dist/commands/human/prompts.d.ts +2 -3
  233. package/dist/commands/human/prompts.js +27 -180
  234. package/dist/commands/human/queue.d.ts +1 -5
  235. package/dist/commands/human/queue.js +54 -216
  236. package/dist/commands/human/review.d.ts +2 -0
  237. package/dist/commands/human/review.js +298 -0
  238. package/dist/commands/human/shared.d.ts +2 -69
  239. package/dist/commands/human/shared.js +1 -181
  240. package/dist/commands/human.js +15 -20
  241. package/dist/commands/memory/__tests__/lint.test.d.ts +1 -0
  242. package/dist/commands/memory/__tests__/lint.test.js +15 -0
  243. package/dist/commands/memory/delete.js +2 -2
  244. package/dist/commands/memory/lint.d.ts +1 -0
  245. package/dist/commands/memory/lint.js +21 -3
  246. package/dist/commands/memory/read.js +4 -4
  247. package/dist/commands/node.js +8 -7
  248. package/dist/commands/pkg/plugin-manage.js +143 -68
  249. package/dist/commands/surface-inspect.js +2 -2
  250. package/dist/commands/surface.js +6 -12
  251. package/dist/commands/sys/__tests__/setup-core.test.js +9 -5
  252. package/dist/commands/sys/__tests__/setup-front-door.test.js +7 -42
  253. package/dist/commands/sys/config.js +29 -3
  254. package/dist/commands/sys/feedback.js +1 -1
  255. package/dist/commands/sys/setup-core.d.ts +12 -11
  256. package/dist/commands/sys/setup-core.js +55 -46
  257. package/dist/commands/sys/setup-wizard.d.ts +4 -1
  258. package/dist/commands/sys/setup-wizard.js +50 -9
  259. package/dist/commands/sys/sync-deps.js +1 -1
  260. package/dist/commands/sys.js +4 -6
  261. package/dist/core/__tests__/broker-sdk-wiring.test.js +6 -1
  262. package/dist/core/__tests__/broker-snapshot-history.test.js +9 -1
  263. package/dist/core/__tests__/dead-node-policy-table.test.js +3 -4
  264. package/dist/core/__tests__/fixtures/fake-engine.d.ts +8 -5
  265. package/dist/core/__tests__/fixtures/fake-engine.js +28 -2
  266. package/dist/core/__tests__/full/broker-provider-retry.test.js +87 -2
  267. package/dist/core/__tests__/full/broker-whip-interrupt.test.d.ts +1 -0
  268. package/dist/core/__tests__/full/broker-whip-interrupt.test.js +39 -0
  269. package/dist/core/__tests__/full/detach-focus.test.js +1 -1
  270. package/dist/core/__tests__/human-deliver-e2e.test.js +64 -64
  271. package/dist/core/__tests__/human-deliver.test.js +71 -345
  272. package/dist/core/__tests__/inline-memory-refs.test.js +2 -12
  273. package/dist/core/__tests__/memory-resolver-precedence.test.js +12 -15
  274. package/dist/core/__tests__/migration.test.js +10 -0
  275. package/dist/core/__tests__/model-pin-durability.test.js +14 -0
  276. package/dist/core/__tests__/phase4-review-migration.test.d.ts +1 -0
  277. package/dist/core/__tests__/phase4-review-migration.test.js +100 -0
  278. package/dist/core/__tests__/phase4-review-store.test.d.ts +1 -0
  279. package/dist/core/__tests__/phase4-review-store.test.js +143 -0
  280. package/dist/core/__tests__/remote-canvas-target.test.js +2 -2
  281. package/dist/core/__tests__/revive.test.js +22 -53
  282. package/dist/core/__tests__/session-cycles.test.js +48 -18
  283. package/dist/core/__tests__/spawn-no-kickoff.test.js +8 -12
  284. package/dist/core/__tests__/stop-guard.test.js +16 -1
  285. package/dist/core/__tests__/tmux-surface.test.js +21 -25
  286. package/dist/core/asset-root.d.ts +7 -0
  287. package/dist/core/asset-root.js +18 -0
  288. package/dist/core/broker-client/client.d.ts +0 -7
  289. package/dist/core/broker-client/client.js +4 -10
  290. package/dist/core/broker-client/transport-relay.d.ts +3 -4
  291. package/dist/core/broker-client/transport-relay.js +3 -4
  292. package/dist/core/canvas/__tests__/attention.test.js +1 -1
  293. package/dist/core/canvas/__tests__/remote-canvas-source.test.js +1 -1
  294. package/dist/core/canvas/__tests__/remote-transport.test.d.ts +1 -0
  295. package/dist/core/{view/__tests__/transport-remote.test.js → canvas/__tests__/remote-transport.test.js} +2 -2
  296. package/dist/core/canvas/__tests__/render-remote.test.js +1 -1
  297. package/dist/core/canvas/attention.js +4 -4
  298. package/dist/core/canvas/canvas.d.ts +6 -11
  299. package/dist/core/canvas/canvas.js +11 -33
  300. package/dist/core/canvas/db.js +207 -38
  301. package/dist/core/canvas/extensions.d.ts +1 -1
  302. package/dist/core/canvas/extensions.js +8 -1
  303. package/dist/core/canvas/index.d.ts +0 -1
  304. package/dist/core/canvas/index.js +0 -1
  305. package/dist/core/canvas/nav-model.d.ts +1 -1
  306. package/dist/core/canvas/nav-model.js +1 -1
  307. package/dist/core/canvas/paths.d.ts +5 -0
  308. package/dist/core/canvas/paths.js +16 -0
  309. package/dist/core/canvas/remote-canvas-source.js +2 -2
  310. package/dist/core/canvas/remote-transport.d.ts +7 -0
  311. package/dist/core/{view/transport-remote.js → canvas/remote-transport.js} +3 -4
  312. package/dist/core/canvas/source.js +1 -1
  313. package/dist/core/canvas/status-glyph.js +4 -12
  314. package/dist/core/canvas/types.d.ts +10 -35
  315. package/dist/core/clipboard-text.d.ts +8 -0
  316. package/dist/core/clipboard-text.js +18 -0
  317. package/dist/core/command-manifests/registry.d.ts +1 -3
  318. package/dist/core/command-plugins/bundle.d.ts +29 -0
  319. package/dist/core/command-plugins/bundle.js +198 -0
  320. package/dist/core/command-plugins/discovery.d.ts +0 -8
  321. package/dist/core/command-plugins/discovery.js +2 -39
  322. package/dist/core/command-plugins/transport/http-fetch.d.ts +4 -30
  323. package/dist/core/command-plugins/transport/http-fetch.js +17 -95
  324. package/dist/core/command.d.ts +1 -13
  325. package/dist/core/command.js +1 -16
  326. package/dist/core/config.d.ts +4 -0
  327. package/dist/core/config.js +28 -3
  328. package/dist/core/document-lines.d.ts +4 -0
  329. package/dist/core/document-lines.js +6 -0
  330. package/dist/core/feed/feed.d.ts +2 -0
  331. package/dist/core/feed/feed.js +11 -7
  332. package/dist/core/human/__tests__/inbox-cancel-worker.d.ts +1 -0
  333. package/dist/core/human/__tests__/inbox-cancel-worker.js +7 -0
  334. package/dist/core/human/__tests__/inbox-claim-worker.d.ts +1 -0
  335. package/dist/core/human/__tests__/inbox-claim-worker.js +17 -0
  336. package/dist/core/human/__tests__/inbox-core.test.d.ts +1 -0
  337. package/dist/core/human/__tests__/inbox-core.test.js +144 -0
  338. package/dist/core/human/__tests__/inbox-stale-claim-worker.d.ts +1 -0
  339. package/dist/core/human/__tests__/inbox-stale-claim-worker.js +8 -0
  340. package/dist/core/human/__tests__/visible-paths.test.d.ts +1 -0
  341. package/dist/{clients/attach → core/human}/__tests__/visible-paths.test.js +9 -0
  342. package/dist/core/human/claim.d.ts +23 -0
  343. package/dist/core/human/claim.js +67 -0
  344. package/dist/core/human/convention.d.ts +30 -0
  345. package/dist/core/human/convention.js +202 -0
  346. package/dist/core/human/deck-factories.d.ts +8 -0
  347. package/dist/core/human/deck-factories.js +15 -0
  348. package/dist/core/human/deck-schema.d.ts +70 -0
  349. package/dist/core/human/deck-schema.js +92 -0
  350. package/dist/core/human/root.d.ts +7 -0
  351. package/dist/core/human/root.js +37 -0
  352. package/dist/core/human/scan.d.ts +3 -0
  353. package/dist/core/human/scan.js +76 -0
  354. package/dist/core/human/summary.d.ts +8 -0
  355. package/dist/core/human/summary.js +44 -0
  356. package/dist/core/human/tickets.d.ts +49 -0
  357. package/dist/core/human/tickets.js +190 -0
  358. package/dist/core/human/types.d.ts +136 -0
  359. package/dist/core/human/types.js +5 -0
  360. package/dist/{clients/attach → core/human}/visible-paths.d.ts +1 -1
  361. package/dist/{clients/attach → core/human}/visible-paths.js +3 -2
  362. package/dist/core/inspector/chrome.d.ts +6 -0
  363. package/dist/core/inspector/chrome.js +14 -0
  364. package/dist/core/inspector/contract.d.ts +105 -0
  365. package/dist/core/inspector/contract.js +1 -0
  366. package/dist/core/inspector/core.d.ts +4 -5
  367. package/dist/core/inspector/core.js +3 -5
  368. package/dist/core/inspector/host.d.ts +46 -0
  369. package/dist/core/{tui → inspector}/host.js +42 -208
  370. package/dist/core/inspector/text.d.ts +2 -2
  371. package/dist/core/inspector/transport.d.ts +6 -0
  372. package/dist/core/inspector/transport.js +36 -0
  373. package/dist/core/inspector/tui.d.ts +2 -2
  374. package/dist/core/inspector/tui.js +15 -15
  375. package/dist/core/keybindings/__tests__/inbox-affordance.test.js +11 -11
  376. package/dist/core/keybindings/__tests__/resolve.test.js +10 -19
  377. package/dist/core/keybindings/attach-control.d.ts +9 -3
  378. package/dist/core/keybindings/attach-control.js +3 -1
  379. package/dist/core/keybindings/catalog.d.ts +6 -5
  380. package/dist/core/keybindings/catalog.js +61 -133
  381. package/dist/core/keybindings/inbox.d.ts +5 -6
  382. package/dist/core/keybindings/inbox.js +9 -10
  383. package/dist/core/keybindings/index.d.ts +3 -3
  384. package/dist/core/keybindings/index.js +3 -3
  385. package/dist/core/keybindings/persistence.d.ts +5 -1
  386. package/dist/core/keybindings/persistence.js +24 -6
  387. package/dist/core/keybindings/resolve.d.ts +6 -0
  388. package/dist/core/keybindings/resolve.js +10 -3
  389. package/dist/core/memory/inline-ref-grammar.js +2 -3
  390. package/dist/core/memory-resolver.d.ts +27 -29
  391. package/dist/core/memory-resolver.js +45 -39
  392. package/dist/core/model-routes.d.ts +33 -0
  393. package/dist/core/model-routes.js +42 -3
  394. package/dist/core/preview-registry.js +17 -55
  395. package/dist/core/profiles/select.js +3 -3
  396. package/dist/core/{view/remote-canvas-target.d.ts → remote-canvas-target.d.ts} +2 -2
  397. package/dist/core/{view/remote-canvas-target.js → remote-canvas-target.js} +4 -4
  398. package/dist/core/review/__tests__/capture-origin.test.d.ts +1 -0
  399. package/dist/core/review/__tests__/capture-origin.test.js +139 -0
  400. package/dist/core/review/__tests__/comments.test.d.ts +1 -0
  401. package/dist/core/review/__tests__/comments.test.js +98 -0
  402. package/dist/core/review/__tests__/project.test.d.ts +1 -0
  403. package/dist/core/review/__tests__/project.test.js +71 -0
  404. package/dist/core/review/__tests__/remap.test.d.ts +1 -0
  405. package/dist/core/review/__tests__/remap.test.js +159 -0
  406. package/dist/core/review/__tests__/stage-identity.test.d.ts +1 -0
  407. package/dist/core/review/__tests__/stage-identity.test.js +140 -0
  408. package/dist/core/review/birth.d.ts +7 -0
  409. package/dist/core/review/birth.js +23 -0
  410. package/dist/core/review/comments.d.ts +69 -0
  411. package/dist/core/review/comments.js +295 -0
  412. package/dist/core/review/companion.d.ts +35 -0
  413. package/dist/core/review/companion.js +205 -0
  414. package/dist/core/review/document.d.ts +27 -0
  415. package/dist/core/review/document.js +138 -0
  416. package/dist/core/review/project.d.ts +13 -0
  417. package/dist/core/review/project.js +58 -0
  418. package/dist/core/review/realize.d.ts +19 -0
  419. package/dist/core/review/realize.js +217 -0
  420. package/dist/core/review/remap.d.ts +24 -0
  421. package/dist/core/review/remap.js +156 -0
  422. package/dist/core/review/signal.d.ts +5 -0
  423. package/dist/core/review/signal.js +31 -0
  424. package/dist/core/review/stage.d.ts +26 -0
  425. package/dist/core/review/stage.js +184 -0
  426. package/dist/core/review/store.d.ts +46 -0
  427. package/dist/core/review/store.js +163 -0
  428. package/dist/core/review/ticket-filter.d.ts +17 -0
  429. package/dist/core/review/ticket-filter.js +32 -0
  430. package/dist/core/review/types.d.ts +130 -0
  431. package/dist/core/review/types.js +23 -0
  432. package/dist/core/runtime/__tests__/session-visibility.test.d.ts +1 -0
  433. package/dist/core/runtime/__tests__/session-visibility.test.js +119 -0
  434. package/dist/core/runtime/bearings.d.ts +5 -17
  435. package/dist/core/runtime/bearings.js +88 -71
  436. package/dist/core/runtime/broker-protocol.d.ts +31 -8
  437. package/dist/core/runtime/broker.d.ts +15 -1
  438. package/dist/core/runtime/broker.js +573 -141
  439. package/dist/core/runtime/canvas-extensions.d.ts +4 -5
  440. package/dist/core/runtime/canvas-extensions.js +5 -5
  441. package/dist/core/runtime/front-door.js +4 -4
  442. package/dist/core/runtime/headless-pi.d.ts +24 -0
  443. package/dist/core/runtime/headless-pi.js +152 -0
  444. package/dist/core/runtime/kickoff.d.ts +1 -5
  445. package/dist/core/runtime/kickoff.js +1 -5
  446. package/dist/core/runtime/model-registry.d.ts +3 -0
  447. package/dist/core/runtime/model-registry.js +13 -0
  448. package/dist/core/runtime/naming-persist.d.ts +4 -0
  449. package/dist/core/runtime/naming-persist.js +27 -1
  450. package/dist/core/runtime/naming.d.ts +17 -12
  451. package/dist/core/runtime/naming.js +91 -140
  452. package/dist/core/runtime/node-read.js +19 -10
  453. package/dist/core/runtime/nodes.d.ts +9 -14
  454. package/dist/core/runtime/nodes.js +3 -53
  455. package/dist/core/runtime/package-health.d.ts +6 -21
  456. package/dist/core/runtime/package-health.js +12 -123
  457. package/dist/core/runtime/placement-tmux.d.ts +2 -1
  458. package/dist/core/runtime/placement-tmux.js +6 -4
  459. package/dist/core/runtime/placement.d.ts +1 -0
  460. package/dist/core/runtime/placement.js +4 -8
  461. package/dist/core/runtime/promote.js +3 -0
  462. package/dist/core/runtime/recap.d.ts +2 -7
  463. package/dist/core/runtime/recap.js +35 -69
  464. package/dist/core/runtime/revive.js +34 -77
  465. package/dist/core/runtime/session-cycles.d.ts +4 -0
  466. package/dist/core/runtime/session-cycles.js +14 -1
  467. package/dist/core/runtime/session-visibility.d.ts +37 -0
  468. package/dist/core/runtime/session-visibility.js +83 -0
  469. package/dist/core/runtime/spawn.js +10 -22
  470. package/dist/core/runtime/stop-guard.d.ts +1 -1
  471. package/dist/core/runtime/stop-guard.js +18 -13
  472. package/dist/core/runtime/surface-bg.js +1 -1
  473. package/dist/core/runtime/tmux-chrome.d.ts +1 -1
  474. package/dist/core/runtime/tmux-chrome.js +1 -1
  475. package/dist/core/runtime/tmux.d.ts +35 -7
  476. package/dist/core/runtime/tmux.js +439 -62
  477. package/dist/core/runtime/tool-group-summary.d.ts +19 -0
  478. package/dist/core/runtime/tool-group-summary.js +88 -0
  479. package/dist/core/scope.d.ts +2 -7
  480. package/dist/core/scope.js +9 -35
  481. package/dist/core/substrate/schema.d.ts +2 -2
  482. package/dist/core/termrender/code-doc.d.ts +12 -0
  483. package/dist/core/termrender/code-doc.js +92 -0
  484. package/dist/core/termrender/display.d.ts +12 -0
  485. package/dist/core/termrender/display.js +19 -0
  486. package/dist/core/termrender/termrender.d.ts +73 -0
  487. package/dist/core/termrender/termrender.js +795 -0
  488. package/dist/core/termrender/version.d.ts +1 -0
  489. package/dist/core/termrender/version.js +1 -0
  490. package/dist/core/tui/terminal.js +1 -2
  491. package/dist/core/user-settings.d.ts +71 -3
  492. package/dist/core/user-settings.js +83 -1
  493. package/dist/daemon/api/__tests__/inbox.test.js +44 -133
  494. package/dist/daemon/api/handlers/human.d.ts +0 -10
  495. package/dist/daemon/api/handlers/human.js +104 -702
  496. package/dist/daemon/api/handlers/inbox.js +113 -142
  497. package/dist/daemon/api/handlers/review-comments.d.ts +2 -0
  498. package/dist/daemon/api/handlers/review-comments.js +500 -0
  499. package/dist/daemon/api/handlers/reviews.d.ts +2 -0
  500. package/dist/daemon/api/handlers/reviews.js +287 -0
  501. package/dist/daemon/api/map.d.ts +22 -7
  502. package/dist/daemon/api/map.js +159 -11
  503. package/dist/daemon/api/server.js +4 -0
  504. package/dist/daemon/cron-run.js +6 -9
  505. package/dist/daemon/crtrd.js +17 -33
  506. package/dist/daemon/fleet.d.ts +1 -8
  507. package/dist/daemon/fleet.js +4 -30
  508. package/dist/daemon/human/finish.d.ts +14 -0
  509. package/dist/daemon/human/finish.js +115 -0
  510. package/dist/daemon/human/sweep.d.ts +6 -0
  511. package/dist/daemon/human/sweep.js +49 -0
  512. package/dist/daemon/messaging/node-message.d.ts +18 -0
  513. package/dist/daemon/messaging/node-message.js +70 -0
  514. package/dist/daemon/review/comment-notify.d.ts +26 -0
  515. package/dist/daemon/review/comment-notify.js +107 -0
  516. package/dist/daemon/review/deliver.d.ts +7 -0
  517. package/dist/daemon/review/deliver.js +56 -0
  518. package/dist/daemon/review/finish.d.ts +22 -0
  519. package/dist/daemon/review/finish.js +254 -0
  520. package/dist/daemon/review/sweep.d.ts +11 -0
  521. package/dist/daemon/review/sweep.js +120 -0
  522. package/dist/index.d.ts +2 -0
  523. package/dist/index.js +2 -2
  524. package/dist/pi-extensions/__tests__/canvas-context-intro.test.js +42 -64
  525. package/dist/pi-extensions/__tests__/canvas-recap.test.d.ts +1 -0
  526. package/dist/pi-extensions/__tests__/canvas-recap.test.js +159 -0
  527. package/dist/pi-extensions/__tests__/canvas-stophook-agentend.test.js +16 -38
  528. package/dist/pi-extensions/__tests__/canvas-structured-output.test.d.ts +1 -0
  529. package/dist/pi-extensions/__tests__/canvas-structured-output.test.js +63 -0
  530. package/dist/pi-extensions/canvas-context-intro.d.ts +8 -11
  531. package/dist/pi-extensions/canvas-context-intro.js +50 -104
  532. package/dist/pi-extensions/canvas-recap.d.ts +15 -2
  533. package/dist/pi-extensions/canvas-recap.js +192 -69
  534. package/dist/pi-extensions/canvas-review-boundary.d.ts +59 -0
  535. package/dist/pi-extensions/canvas-review-boundary.js +81 -0
  536. package/dist/pi-extensions/canvas-stophook.d.ts +3 -3
  537. package/dist/pi-extensions/canvas-stophook.js +78 -128
  538. package/dist/pi-extensions/canvas-structured-output.js +52 -8
  539. package/dist/shared/generated-context.d.ts +29 -4
  540. package/dist/shared/generated-context.js +122 -7
  541. package/dist/shared/tool-groups.d.ts +23 -0
  542. package/dist/shared/tool-groups.js +60 -0
  543. package/dist/shared/working-activity.d.ts +8 -0
  544. package/dist/shared/working-activity.js +26 -0
  545. package/dist/types.d.ts +36 -2
  546. package/dist/types.js +51 -0
  547. package/dist/web-client/assets/index-CCPaLeW7.css +2 -0
  548. package/dist/web-client/assets/index-kyOWsnZK.js +86 -0
  549. package/dist/web-client/index.html +2 -2
  550. package/dist/web-client/sw.js +1 -1
  551. package/docs/compat/hearth-crtr-v4.md +2 -2
  552. package/docs/compat/hearth-crtr-v5.md +4 -2
  553. package/docs/public-api.md +3 -3
  554. package/package.json +21 -14
  555. package/runtime.lock.json +4661 -1453
  556. package/scripts/postinstall.mjs +15 -2
  557. package/dist/builtin-pi-packages/pi-mode-switch/README.md +0 -36
  558. package/dist/builtin-pi-packages/pi-mode-switch/bin/mode +0 -36
  559. package/dist/builtin-pi-packages/pi-mode-switch/extensions/index.ts +0 -449
  560. package/dist/builtin-pi-packages/pi-mode-switch/package.json +0 -14
  561. package/dist/builtin-pi-packages/pi-mode-switch/tsconfig.json +0 -9
  562. package/dist/builtin-views/_lib/states.mjs +0 -161
  563. package/dist/builtin-views/canvas/core.mjs +0 -657
  564. package/dist/builtin-views/canvas/text.mjs +0 -58
  565. package/dist/builtin-views/canvas/tui.mjs +0 -168
  566. package/dist/builtin-views/canvas/web.jsx +0 -121
  567. package/dist/builtin-views/chat/core.mjs +0 -684
  568. package/dist/builtin-views/chat/text.mjs +0 -101
  569. package/dist/builtin-views/chat/tui.mjs +0 -360
  570. package/dist/builtin-views/chat/web.jsx +0 -362
  571. package/dist/builtin-views/git-pr/core.mjs +0 -673
  572. package/dist/builtin-views/git-pr/text.mjs +0 -84
  573. package/dist/builtin-views/git-pr/tui.mjs +0 -301
  574. package/dist/builtin-views/git-pr/web.jsx +0 -216
  575. package/dist/builtin-views/inbox/_lib/render.mjs +0 -175
  576. package/dist/builtin-views/inbox/core.mjs +0 -1273
  577. package/dist/builtin-views/inbox/text.mjs +0 -73
  578. package/dist/builtin-views/inbox/tui.mjs +0 -314
  579. package/dist/builtin-views/inbox/web.jsx +0 -188
  580. package/dist/builtin-views/linkedin/core.mjs +0 -906
  581. package/dist/builtin-views/linkedin/text.mjs +0 -69
  582. package/dist/builtin-views/linkedin/tui.mjs +0 -413
  583. package/dist/builtin-views/linkedin/web.jsx +0 -206
  584. package/dist/builtin-views/prompt-review/core.mjs +0 -735
  585. package/dist/builtin-views/prompt-review/text.mjs +0 -15
  586. package/dist/builtin-views/prompt-review/tui.mjs +0 -196
  587. package/dist/builtin-views/prompt-review/web.jsx +0 -484
  588. package/dist/builtin-views/workspace-sidebar/__tests__/core.test.js +0 -50
  589. package/dist/builtin-views/workspace-sidebar/__tests__/core.test.ts +0 -53
  590. package/dist/builtin-views/workspace-sidebar/core.mjs +0 -670
  591. package/dist/builtin-views/workspace-sidebar/text.mjs +0 -53
  592. package/dist/builtin-views/workspace-sidebar/tui.mjs +0 -141
  593. package/dist/builtin-views/workspace-sidebar/web.jsx +0 -109
  594. package/dist/clients/attach/session/mode.d.ts +0 -25
  595. package/dist/clients/attach/session/mode.js +0 -97
  596. package/dist/commands/sys/promptstudio.d.ts +0 -2
  597. package/dist/commands/sys/promptstudio.js +0 -65
  598. package/dist/commands/view-cycle.d.ts +0 -2
  599. package/dist/commands/view-cycle.js +0 -130
  600. package/dist/commands/view-list.d.ts +0 -2
  601. package/dist/commands/view-list.js +0 -66
  602. package/dist/commands/view-new.d.ts +0 -2
  603. package/dist/commands/view-new.js +0 -74
  604. package/dist/commands/view-pick.d.ts +0 -6
  605. package/dist/commands/view-pick.js +0 -129
  606. package/dist/commands/view-run.d.ts +0 -2
  607. package/dist/commands/view-run.js +0 -214
  608. package/dist/commands/view.d.ts +0 -2
  609. package/dist/commands/view.js +0 -31
  610. package/dist/commands/workspace.d.ts +0 -2
  611. package/dist/commands/workspace.js +0 -165
  612. package/dist/core/__tests__/chat-view-reconnect.test.js +0 -105
  613. package/dist/core/__tests__/full/consult-startup-failure.test.js +0 -124
  614. package/dist/core/canvas/__tests__/human-work-outbox.test.js +0 -123
  615. package/dist/core/canvas/human-work-outbox.d.ts +0 -73
  616. package/dist/core/canvas/human-work-outbox.js +0 -261
  617. package/dist/core/command-plugins/store.d.ts +0 -16
  618. package/dist/core/command-plugins/store.js +0 -64
  619. package/dist/core/keybindings/__tests__/bespoke-consumers.test.js +0 -40
  620. package/dist/core/runtime/fault-recovery-nudge.d.ts +0 -3
  621. package/dist/core/runtime/fault-recovery-nudge.js +0 -3
  622. package/dist/core/tui/__tests__/host-keybindings.test.js +0 -113
  623. package/dist/core/tui/host.d.ts +0 -66
  624. package/dist/core/view/__tests__/transport-cache.test.js +0 -62
  625. package/dist/core/view/bridge.d.ts +0 -10
  626. package/dist/core/view/bridge.js +0 -31
  627. package/dist/core/view/chrome.d.ts +0 -9
  628. package/dist/core/view/chrome.js +0 -22
  629. package/dist/core/view/contract.d.ts +0 -205
  630. package/dist/core/view/contract.js +0 -23
  631. package/dist/core/view/loader.d.ts +0 -31
  632. package/dist/core/view/loader.js +0 -188
  633. package/dist/core/view/stream-local.d.ts +0 -3
  634. package/dist/core/view/stream-local.js +0 -231
  635. package/dist/core/view/transport-cache.d.ts +0 -8
  636. package/dist/core/view/transport-cache.js +0 -38
  637. package/dist/core/view/transport-local.d.ts +0 -7
  638. package/dist/core/view/transport-local.js +0 -83
  639. package/dist/core/view/transport-remote.d.ts +0 -2
  640. package/dist/core/view/transport.d.ts +0 -10
  641. package/dist/core/view/transport.js +0 -15
  642. package/dist/pi-extensions/naming-tool.d.ts +0 -24
  643. package/dist/pi-extensions/naming-tool.js +0 -67
  644. package/dist/prompts/view.d.ts +0 -7
  645. package/dist/prompts/view.js +0 -172
  646. package/dist/web/ViewChrome.d.ts +0 -7
  647. package/dist/web/ViewChrome.js +0 -28
  648. package/dist/web/ViewPane.d.ts +0 -39
  649. package/dist/web/ViewPane.js +0 -48
  650. package/dist/web/index.d.ts +0 -8
  651. package/dist/web/index.js +0 -22
  652. package/dist/web/runtime.d.ts +0 -39
  653. package/dist/web/runtime.js +0 -232
  654. package/dist/web/states.d.ts +0 -24
  655. package/dist/web/states.js +0 -24
  656. package/dist/web/transport-http.d.ts +0 -5
  657. package/dist/web/transport-http.js +0 -34
  658. package/dist/web/transport-stream.d.ts +0 -3
  659. package/dist/web/transport-stream.js +0 -204
  660. package/dist/web-client/assets/index-U_NZ66VE.js +0 -79
  661. package/dist/web-client/assets/index-aaeu4adv.css +0 -2
  662. /package/dist/{builtin-views/workspace-sidebar/__tests__/core.test.d.ts → clients/attach/__tests__/file-review-focus.test.d.ts} +0 -0
  663. /package/dist/clients/attach/__tests__/{visible-paths.test.d.ts → group-activity.test.d.ts} +0 -0
  664. /package/dist/{core/__tests__/chat-view-reconnect.test.d.ts → clients/attach/__tests__/input-controller-extension-command.test.d.ts} +0 -0
  665. /package/dist/{core/__tests__/full/consult-startup-failure.test.d.ts → clients/attach/__tests__/session-identity-refresh.test.d.ts} +0 -0
  666. /package/dist/{core/canvas/__tests__/human-work-outbox.test.d.ts → clients/inbox/__tests__/inbox-controller.test.d.ts} +0 -0
  667. /package/dist/{core/keybindings/__tests__/bespoke-consumers.test.d.ts → clients/inbox/__tests__/mount-panel.test.d.ts} +0 -0
  668. /package/dist/{core/tui/__tests__/host-keybindings.test.d.ts → clients/inbox/review/__tests__/editor-roundtrip.test.d.ts} +0 -0
  669. /package/dist/{core/view/__tests__/transport-cache.test.d.ts → clients/inbox/review/__tests__/remap.test.d.ts} +0 -0
  670. /package/dist/{core/view/__tests__/transport-remote.test.d.ts → clients/inbox/review/__tests__/review-core.test.d.ts} +0 -0
@@ -32,6 +32,8 @@ import { operationIdContext } from '../events/operation-id.js';
32
32
  import { findMissingPackagePaths } from './package-health.js';
33
33
  import { SessionListCache } from './session-list-cache.js';
34
34
  import { getNode, updateNode } from '../canvas/index.js';
35
+ import { readConfig } from '../config.js';
36
+ import { atomicWriteJson, readJsonIfExists } from '../fs-utils.js';
35
37
  import { buildRefInventory } from '../memory/inline-ref-inventory.js';
36
38
  import { buildGuidance } from '../memory/inline-ref-guidance.js';
37
39
  import { clearSessionCache } from '../substrate/session-cache.js';
@@ -44,16 +46,33 @@ import { PoolCredentialStore } from '../pool-credential-store.js';
44
46
  import { clearFault, isModelNotFoundError, isProviderUnconfiguredError, readFault, recordFault, recordFaultForCurrentNode } from './fault.js';
45
47
  import { clearCleanAbort, markBusy, markCleanAbort } from './busy.js';
46
48
  import { extractCoolingDeadline } from './managed-provider-cooling.js';
47
- import { AUTH_FAULT_RECOVERY_BODY, CONNECTION_FAULT_RECOVERY_BODY, PROVIDER_FAULT_RECOVERY_BODY } from './fault-recovery-nudge.js';
49
+ import { AUTH_FAULT_RECOVERY_BODY, CONNECTION_FAULT_RECOVERY_BODY, PROVIDER_FAULT_RECOVERY_BODY, formatModelFallbackRecovery, generatedContextText, } from '../../shared/generated-context.js';
50
+ import { assistantVisibleText, isTrueUserMessage, normalizeToolGroupSummaryRecord, } from '../../shared/tool-groups.js';
51
+ import { headlessToolGroupSummary, serializeToolGroup } from './tool-group-summary.js';
48
52
  import { markFatalOnExhaust, nextFaultRetry, policyFor } from './fault-recovery.js';
49
53
  import { probeOnline as probeOnlineReal } from './connectivity.js';
50
- import { CRTR_CYCLE_CUSTOM_TYPE, cycleAwareMessages } from './session-cycles.js';
54
+ import { CRTR_CYCLE_CUSTOM_TYPE, cycleAwareMessages, withoutYieldAbort } from './session-cycles.js';
55
+ import { boundaryRootedTree, REVIEW_BOUNDARY_CUSTOM_TYPE, visibleMessages } from './session-visibility.js';
51
56
  import { BUILTIN_SLASH_COMMANDS, piSessionsRoot } from './pi-vendored.js';
52
57
  import { StreamWatchdog, resolveStreamWatchdogMs, releaseKnownStreamWait, KNOWN_STREAM_WAIT_UI } from './stream-watchdog.js';
53
58
  import { isDeterministicCommandExpansion } from './command-expansion.js';
59
+ import { DEFAULT_WORKING_ACTIVITY, randomWorkingActivity } from '../../shared/working-activity.js';
54
60
  import { isManagedProvider, readSubscriptionPool, resolveProviderCandidates } from '../subscription-state.js';
55
- import { expandModelCandidates, modelRequestFromConfig, probeRouteAvailability } from '../model-routes.js';
61
+ import { expandModelCandidates, modelRequestFromConfig, probeRouteAvailability, selectInitialModelRoute } from '../model-routes.js';
56
62
  import { encodeFrame, FrameDecoder, FrameOverflowError, BROKER_READ_CAPS, } from './broker-protocol.js';
63
+ /** A review companion may only navigate or fork within a branch whose ancestry
64
+ * has crossed its durable transcript boundary. The broker sends this typed local
65
+ * failure as a dedicated error frame without invoking pi's tree mutation. */
66
+ export const REVIEW_BOUNDARY_NAVIGATION_CODE = 'review_boundary_navigation';
67
+ export class ReviewBoundaryNavigationError extends Error {
68
+ targetId;
69
+ code = REVIEW_BOUNDARY_NAVIGATION_CODE;
70
+ constructor(targetId) {
71
+ super(`review companion cannot navigate before the visible transcript boundary (target: ${targetId})`);
72
+ this.targetId = targetId;
73
+ this.name = 'ReviewBoundaryNavigationError';
74
+ }
75
+ }
57
76
  const THINKING_LEVELS = new Set(['off', 'minimal', 'low', 'medium', 'high', 'xhigh', 'max']);
58
77
  /**
59
78
  * pi 0.82 made `ModelRuntime` the canonical model/auth surface and demoted
@@ -174,6 +193,13 @@ export function isLeadingEngineCommand(text, session) {
174
193
  }
175
194
  return false;
176
195
  }
196
+ /** A `steer` frame normally maps directly to pi's steer queue. Engine commands,
197
+ * skills, and templates must instead pass through prompt()'s leading-command
198
+ * preflight; with streamingBehavior:'steer', ordinary expanded text still lands
199
+ * in the same queue. */
200
+ export function resolveSteerCall(text, session) {
201
+ return isLeadingEngineCommand(text, session) ? 'prompt' : 'steer';
202
+ }
177
203
  /**
178
204
  * Enumerate every leading-token STRING this session's engine would
179
205
  * independently consume as a leading command/skill/template dispatch BEFORE
@@ -399,6 +425,23 @@ const REPLACEMENT_DEADLINE_MS = 30_000;
399
425
  * the budget elapses before the runtime promise settles — never compared via
400
426
  * string matching. */
401
427
  const REPLACEMENT_DEADLINE_EXCEEDED = Symbol('replacement-deadline-exceeded');
428
+ const PENDING_TOOL_SUMMARY_KEY_MAX_CHARS = 1_000;
429
+ const PENDING_TOOL_SUMMARY_SEGMENT_MAX_CHARS = 10_000;
430
+ function normalizePendingToolGroupSummary(value) {
431
+ if (value === null || typeof value !== 'object' || Array.isArray(value))
432
+ return null;
433
+ const record = value;
434
+ if (Object.keys(record).sort().join(',') !== 'key,segment,version')
435
+ return null;
436
+ if (record.version !== 1 || typeof record.key !== 'string' || typeof record.segment !== 'string')
437
+ return null;
438
+ if (record.key.length === 0 ||
439
+ record.key.length > PENDING_TOOL_SUMMARY_KEY_MAX_CHARS ||
440
+ record.segment.length === 0 ||
441
+ record.segment.length > PENDING_TOOL_SUMMARY_SEGMENT_MAX_CHARS)
442
+ return null;
443
+ return { version: 1, key: record.key, segment: record.segment };
444
+ }
402
445
  // ---------------------------------------------------------------------------
403
446
  // M3 (scout mq5thyli): the active engine session, exposed so broker-cli's FATAL
404
447
  // handlers (uncaughtException / unhandledRejection / the runBroker reject) can
@@ -521,6 +564,10 @@ export async function runBroker(nodeId, startupAt = process.hrtime.bigint(), sta
521
564
  // that does not expose the replacement API (the fake-engine fixture).
522
565
  // eslint-disable-next-line prefer-const
523
566
  let { session, services, resuming, runtime } = await buildBrokerSession(engine, cfg);
567
+ // Review identity is immutable for the broker's lifetime. Snapshot producers
568
+ // close over this one construction-time classification rather than re-reading
569
+ // canvas metadata for every welcome or session replacement.
570
+ const reviewCompanion = getNode(nodeId)?.review_binding != null;
524
571
  // M3: name the boot session as the process-fatal disposal target immediately —
525
572
  // enqueueRebind reassigns this on every later replacement, but without this
526
573
  // initial assignment `activeSession` stays null (and disposeActiveSession() a
@@ -588,7 +635,28 @@ export async function runBroker(nodeId, startupAt = process.hrtime.bigint(), sta
588
635
  const displayStatuses = new Map();
589
636
  const displayWidgets = new Map();
590
637
  let displayTitle;
638
+ let workingActivity = DEFAULT_WORKING_ACTIVITY;
639
+ const toolSummariesPath = join(jobDir(nodeId), 'tool-summaries.json');
640
+ const pendingToolSummaryPath = join(jobDir(nodeId), 'pending-tool-summary.json');
641
+ const toolGroupSummaries = new Map();
642
+ try {
643
+ const persisted = readJsonIfExists(toolSummariesPath);
644
+ for (const [key, summary] of Object.entries(normalizeToolGroupSummaryRecord(persisted))) {
645
+ toolGroupSummaries.set(key, summary);
646
+ }
647
+ }
648
+ catch {
649
+ // A malformed sidecar merely withholds summaries; transcript tools remain visible.
650
+ }
651
+ const summaryAttempted = new Set(toolGroupSummaries.keys());
652
+ const generatingToolGroups = new Map();
653
+ const toolGroupsByCallId = new Map();
654
+ const summaryTasks = new Set();
655
+ let openToolGroup;
656
+ let lastToolGroupBoundaryProse = '';
657
+ let sawAssistantProse = false;
591
658
  let disposed = false;
659
+ let disposePending = false;
592
660
  let agentRunActive = false;
593
661
  let shutdownRequested = false;
594
662
  let server;
@@ -729,6 +797,224 @@ export async function runBroker(nodeId, startupAt = process.hrtime.bigint(), sta
729
797
  }
730
798
  broadcast(frame);
731
799
  };
800
+ /** Record and asynchronously summarize the maximal non-boundary region between
801
+ * visible prose messages. A boundary can land while a tool is still running,
802
+ * so closed groups retain their call ids until every result has joined the
803
+ * same serialized segment; only then may they become collapsible. */
804
+ const ensureOpenToolGroup = () => {
805
+ if (openToolGroup === undefined) {
806
+ openToolGroup = {
807
+ toolCount: 0,
808
+ pieces: [],
809
+ boundaryProse: lastToolGroupBoundaryProse,
810
+ pendingToolIds: new Set(),
811
+ closed: false,
812
+ summarizeWhenSettled: false,
813
+ };
814
+ }
815
+ return openToolGroup;
816
+ };
817
+ const clearPendingToolGroupSummary = (input) => {
818
+ try {
819
+ const persisted = normalizePendingToolGroupSummary(readJsonIfExists(pendingToolSummaryPath));
820
+ if (persisted?.key === input.key && persisted.segment === input.segment)
821
+ unlinkSync(pendingToolSummaryPath);
822
+ }
823
+ catch {
824
+ // The durable summary is already authoritative. A stale handoff may cost a
825
+ // no-op read on the next boot, but must not revoke the recap just written.
826
+ }
827
+ };
828
+ const generateToolGroupSummary = (input) => {
829
+ const { key, segment } = input;
830
+ if (summaryAttempted.has(key) || generatingToolGroups.has(key))
831
+ return;
832
+ summaryAttempted.add(key);
833
+ generatingToolGroups.set(key, input);
834
+ let task;
835
+ task = headlessToolGroupSummary(segment)
836
+ .then((summary) => {
837
+ if (summary === null)
838
+ return;
839
+ toolGroupSummaries.set(key, summary);
840
+ try {
841
+ atomicWriteJson(toolSummariesPath, Object.fromEntries(toolGroupSummaries));
842
+ }
843
+ catch {
844
+ // A delivery without durable state would disappear on reattach, so do
845
+ // not advertise it. The calls remain folded normally instead.
846
+ toolGroupSummaries.delete(key);
847
+ return;
848
+ }
849
+ clearPendingToolGroupSummary(input);
850
+ broadcast({ type: 'tool_group_summary', key, summary });
851
+ })
852
+ .catch(() => undefined)
853
+ .finally(() => {
854
+ generatingToolGroups.delete(key);
855
+ summaryTasks.delete(task);
856
+ });
857
+ summaryTasks.add(task);
858
+ };
859
+ const generateSettledToolGroup = (group) => {
860
+ if (!group.closed || group.pendingToolIds.size > 0 || group.key === undefined || !group.summarizeWhenSettled)
861
+ return;
862
+ generateToolGroupSummary({
863
+ version: 1,
864
+ key: group.key,
865
+ segment: serializeToolGroup(group.boundaryProse, group.pieces),
866
+ });
867
+ };
868
+ try {
869
+ const pending = normalizePendingToolGroupSummary(readJsonIfExists(pendingToolSummaryPath));
870
+ if (pending === null) {
871
+ if (existsSync(pendingToolSummaryPath)) {
872
+ unlinkSync(pendingToolSummaryPath);
873
+ emitEvent({
874
+ level: 'warn',
875
+ event: 'broker.startup.warning',
876
+ fields: { stage: 'pending_tool_summary', message: 'removed an invalid pending tool summary' },
877
+ });
878
+ }
879
+ }
880
+ else if (toolGroupSummaries.has(pending.key)) {
881
+ unlinkSync(pendingToolSummaryPath);
882
+ }
883
+ else {
884
+ generateToolGroupSummary(pending);
885
+ }
886
+ }
887
+ catch (error) {
888
+ emitEvent({
889
+ level: 'warn',
890
+ event: 'broker.startup.warning',
891
+ fields: {
892
+ stage: 'pending_tool_summary',
893
+ message: error instanceof Error ? error.message : String(error),
894
+ },
895
+ });
896
+ }
897
+ const finishOpenToolGroup = (settleInterruptedTools = false) => {
898
+ const group = openToolGroup;
899
+ openToolGroup = undefined;
900
+ if (group === undefined)
901
+ return;
902
+ group.closed = true;
903
+ // An abort is itself the visible evidence that any pending calls did not
904
+ // finish. Their recorded arguments still belong to the recap, and Ctrl+O
905
+ // retains the full rows, so let the completed pre-abort span summarize.
906
+ if (settleInterruptedTools) {
907
+ for (const id of group.pendingToolIds)
908
+ toolGroupsByCallId.delete(id);
909
+ group.pendingToolIds.clear();
910
+ }
911
+ // Configuration is read at the boundary, not when a slow tool later settles.
912
+ group.summarizeWhenSettled = readConfig('user').summarize_tool_calls;
913
+ generateSettledToolGroup(group);
914
+ };
915
+ const visibleBoundary = (prose) => {
916
+ finishOpenToolGroup();
917
+ lastToolGroupBoundaryProse = prose;
918
+ };
919
+ const observeToolGroupEvent = (event) => {
920
+ // The viewer turns every compaction_end into a summary, cancellation, or
921
+ // error line, each of which is a hard transcript seam.
922
+ if (event.type === 'compaction_end') {
923
+ finishOpenToolGroup();
924
+ return;
925
+ }
926
+ // A failed automatic retry is rendered as ChatView's visible error line.
927
+ // Successful retries only clear activity and remain within the same group.
928
+ if (event.type === 'auto_retry_end' && event.success !== true) {
929
+ finishOpenToolGroup();
930
+ return;
931
+ }
932
+ // The attach viewer renders aborted and errored assistant endings as visible
933
+ // transcript seams. Close and settle the pre-abort group here so pending tool
934
+ // arguments can be summarized without pulling the seam into the recap.
935
+ if (event.type === 'message_end') {
936
+ const message = event.message;
937
+ if (message !== null && typeof message === 'object') {
938
+ const item = message;
939
+ if (item.role === 'assistant' && (item.stopReason === 'aborted' || item.stopReason === 'error')) {
940
+ finishOpenToolGroup(true);
941
+ }
942
+ }
943
+ return;
944
+ }
945
+ if (event.type === 'tool_execution_start') {
946
+ const group = ensureOpenToolGroup();
947
+ const id = typeof event.toolCallId === 'string' ? event.toolCallId : '';
948
+ if (group.key === undefined && id !== '')
949
+ group.key = id;
950
+ if (id !== '') {
951
+ group.pendingToolIds.add(id);
952
+ toolGroupsByCallId.set(id, group);
953
+ }
954
+ group.toolCount += 1;
955
+ group.pieces.push({ type: 'tool', name: typeof event.toolName === 'string' ? event.toolName : 'tool', args: event.args });
956
+ return;
957
+ }
958
+ if (event.type === 'tool_execution_end') {
959
+ const id = typeof event.toolCallId === 'string' ? event.toolCallId : '';
960
+ const group = id === '' ? openToolGroup : toolGroupsByCallId.get(id);
961
+ if (group !== undefined) {
962
+ group.pieces.push({
963
+ type: 'result',
964
+ name: typeof event.toolName === 'string' ? event.toolName : 'tool',
965
+ result: event.result,
966
+ isError: event.isError === true,
967
+ });
968
+ if (id !== '') {
969
+ group.pendingToolIds.delete(id);
970
+ toolGroupsByCallId.delete(id);
971
+ }
972
+ generateSettledToolGroup(group);
973
+ }
974
+ return;
975
+ }
976
+ if (event.type !== 'message_start' && event.type !== 'message_update')
977
+ return;
978
+ const message = event.message;
979
+ if (message === null || typeof message !== 'object')
980
+ return;
981
+ const item = message;
982
+ const role = item.role;
983
+ if (role === 'assistant') {
984
+ if (event.type === 'message_start')
985
+ sawAssistantProse = false;
986
+ const prose = assistantVisibleText(item);
987
+ if (!sawAssistantProse && prose.trim() !== '') {
988
+ sawAssistantProse = true;
989
+ visibleBoundary(prose);
990
+ }
991
+ return;
992
+ }
993
+ if (event.type !== 'message_start')
994
+ return;
995
+ if (role === 'compactionSummary' || role === 'branchSummary' || role === 'bashExecution' || role === 'status' || role === 'error' || item.customType === 'crtr-cycle-divider') {
996
+ finishOpenToolGroup();
997
+ return;
998
+ }
999
+ if (isTrueUserMessage(item)) {
1000
+ visibleBoundary(generatedContextText(item));
1001
+ return;
1002
+ }
1003
+ if (item.display === false)
1004
+ return;
1005
+ const text = generatedContextText(item);
1006
+ const isBearingsOrRevive = item.customType === 'crtr-context' || (role === 'user' && text.startsWith('You have been revived fresh after a context refresh'));
1007
+ if (isBearingsOrRevive) {
1008
+ if (openToolGroup !== undefined && text !== '')
1009
+ openToolGroup.pieces.push({ type: 'text', text });
1010
+ return;
1011
+ }
1012
+ // Generated inbox/situational/nudge and visible custom output are part of
1013
+ // the between-message activity and may begin a group before its first tool.
1014
+ const group = ensureOpenToolGroup();
1015
+ if (text !== '')
1016
+ group.pieces.push({ type: 'text', text });
1017
+ };
732
1018
  // ---------------------------------------------------------------------------
733
1019
  // F2: message_update coalescing. `relayEvent` is the single entry point for
734
1020
  // engine events headed to viewers: a message_update is held (latest wins) and
@@ -797,9 +1083,9 @@ export async function runBroker(nodeId, startupAt = process.hrtime.bigint(), sta
797
1083
  // behavior.
798
1084
  let kickoffPending;
799
1085
  let kickoffInFlight = false;
800
- // Guards the reload_auth re-drive (below) against overlapping /login frames
801
- // re-running resolveLaunchModel + setModel + kickoff concurrently; the
802
- // reload/refresh/ack/broadcast ahead of it stay idempotent and always run.
1086
+ // One /login can fan the same reload into this broker through several clients.
1087
+ // Coalesce those frames onto one local auth refresh + route reconciliation so
1088
+ // an idle prompt can await the exact work promised by the reload ack.
803
1089
  let reloadAuthInFlight = null;
804
1090
  // A fresh auth reload is also the explicit recovery trigger for a turn parked
805
1091
  // on a fatal provider-auth fault. Keep at most one rewind + prompt in flight
@@ -951,9 +1237,7 @@ export async function runBroker(nodeId, startupAt = process.hrtime.bigint(), sta
951
1237
  // node from reviving straight back into fault-recovery chrome.
952
1238
  if (unconfigured)
953
1239
  clearFault(nodeId, { link: 'pi→provider' });
954
- await operationIdContext.fresh(() => session.prompt(unconfigured
955
- ? `The previous model (${currentSpec}) had no usable provider credential, so you were automatically switched to ${resolved}. Continue the task from where the failed turn left off.`
956
- : `The previous model (${currentSpec}) was unavailable (provider 404 not_found), so you were automatically switched to ${resolved}. Continue the task from where the failed turn left off.`));
1240
+ await operationIdContext.fresh(() => session.prompt(formatModelFallbackRecovery(currentSpec, resolved, unconfigured ? 'credential' : 'not-found')));
957
1241
  })()
958
1242
  .catch((err) => {
959
1243
  emitEvent({
@@ -1158,6 +1442,15 @@ export async function runBroker(nodeId, startupAt = process.hrtime.bigint(), sta
1158
1442
  maybeFallbackOnUnusableModel(event);
1159
1443
  const type = event.type;
1160
1444
  if (type === 'agent_start') {
1445
+ try {
1446
+ workingActivity = randomWorkingActivity(readConfig('user').working_gerunds);
1447
+ }
1448
+ catch {
1449
+ workingActivity = DEFAULT_WORKING_ACTIVITY;
1450
+ }
1451
+ // Announce the selected label before relaying pi's verbatim agent_start so
1452
+ // every viewer paints this turn with the same random choice.
1453
+ broadcast({ type: 'working_activity', activity: workingActivity });
1161
1454
  notifyTurnAccepted();
1162
1455
  emitStartupMilestone('broker.startup.agent_start', formatModelSpec(session.model, session.thinkingLevel));
1163
1456
  }
@@ -1208,24 +1501,33 @@ export async function runBroker(nodeId, startupAt = process.hrtime.bigint(), sta
1208
1501
  return;
1209
1502
  }
1210
1503
  // Any non-update event: flush the held update FIRST so a viewer never sees
1211
- // (e.g.) message_end before the update it supersedes — then relay verbatim.
1504
+ // (e.g.) message_end before the update it supersedes. A refresh-yield aborts
1505
+ // its own turn; the visible cycle divider names that boundary, so do not also
1506
+ // render it as an operation failure.
1212
1507
  flushPendingUpdate();
1508
+ if (type === 'message_end' && getNode(nodeId)?.intent === 'refresh') {
1509
+ const messageEnd = event;
1510
+ broadcast({ ...messageEnd, message: withoutYieldAbort(messageEnd.message) });
1511
+ return;
1512
+ }
1213
1513
  broadcast(event);
1214
1514
  };
1215
- // Persist a live model switch into the node's durable launch recipe so it
1216
- // survives a yield/revive. pi's `/model` (→ set_model/cycle_model) only
1217
- // mutates the in-memory engine; without this the node reverts to its
1218
- // persona/spawn model on the next revive. We write the full `provider/id`
1219
- // (which normalizeModel passes through unchanged) to BOTH model_override (so
1515
+ // Persist the live model + thinking level into the node's durable launch
1516
+ // recipe so a daemon restart/revive recreates the settings the user sees.
1517
+ // pi's model/thinking controls only mutate the in-memory engine; without this
1518
+ // the node reverts to its persona/spawn settings on the next broker boot. We
1519
+ // write the full `provider/id:thinking` spec to BOTH model_override (so
1220
1520
  // polymorphs preserve it via buildLaunchSpec) and launch.model (the recipe
1221
1521
  // buildPiArgv replays on revive). Best-effort: a degenerate/ephemeral node
1222
1522
  // with no canvas row just skips persistence. CRTR_NODE_ID is set in the
1223
1523
  // broker's own env (merged by piInvocationToSdkConfig at boot).
1224
1524
  // `pinnedOverride`, when passed, is the durable launch-recipe decision for
1225
- // an explicit model change, derived from its raw pre-normalize token. Omitted
1226
- // algorithmic changes retain the existing recipe field through the spread;
1227
- // route expansion, rather than a mutable broker pin, selects retry recovery.
1228
- const persistModelChoice = (pinnedOverride, explicit = false) => {
1525
+ // an explicit model change, derived from its raw pre-normalize token. A
1526
+ // `userSelected` change also clears the older logical route intent: otherwise
1527
+ // resolveLaunchModel ignores the newly persisted concrete spec at reboot and
1528
+ // resolves the persona's original family/strength again. Algorithmic provider
1529
+ // fallback leaves that intent intact.
1530
+ const persistModelChoice = (pinnedOverride, userSelected = false) => {
1229
1531
  const nodeId = process.env['CRTR_NODE_ID'];
1230
1532
  const spec = formatModelSpec(session.model, session.thinkingLevel);
1231
1533
  if (nodeId === undefined || nodeId === '' || spec === null)
@@ -1236,7 +1538,7 @@ export async function runBroker(nodeId, startupAt = process.hrtime.bigint(), sta
1236
1538
  return;
1237
1539
  const launch = meta.launch === undefined
1238
1540
  ? undefined
1239
- : explicit
1541
+ : userSelected
1240
1542
  ? (() => {
1241
1543
  const { modelIntent: _modelIntent, env, ...rest } = meta.launch;
1242
1544
  const { CRTR_MODEL_INTENT: _intentEnv, ...remainingEnv } = env;
@@ -1248,7 +1550,7 @@ export async function runBroker(nodeId, startupAt = process.hrtime.bigint(), sta
1248
1550
  };
1249
1551
  })()
1250
1552
  : { ...meta.launch, model: spec, ...(pinnedOverride !== undefined ? { modelProviderPinned: pinnedOverride } : {}) };
1251
- if (explicit) {
1553
+ if (userSelected) {
1252
1554
  delete process.env['CRTR_MODEL_INTENT'];
1253
1555
  cfg.model = spec;
1254
1556
  }
@@ -1262,13 +1564,13 @@ export async function runBroker(nodeId, startupAt = process.hrtime.bigint(), sta
1262
1564
  // set_model/cycle_model the broker announces it itself — otherwise the new
1263
1565
  // model reaches no viewer (the requester gets only a bare ack) and every
1264
1566
  // footer shows the stale model until the next unrelated event.
1265
- const broadcastModelChanged = (persist = true, pinnedOverride, explicit = false) => {
1567
+ const broadcastModelChanged = (persist = true, pinnedOverride, userSelected = false) => {
1266
1568
  if (persist)
1267
- persistModelChoice(pinnedOverride, explicit);
1569
+ persistModelChoice(pinnedOverride, userSelected);
1268
1570
  broadcast({ type: 'model_changed', model: isUnknownModel(session.model) ? undefined : session.model, spec: formatModelSpec(session.model, session.thinkingLevel) });
1269
1571
  };
1270
1572
  const buildSnapshot = () => ({
1271
- messages: snapshotMessages(session),
1573
+ messages: snapshotMessages(session, reviewCompanion),
1272
1574
  stats: session.getSessionStats(),
1273
1575
  // pi's `get_state` (rpc-mode.js), field for field, from the same getters.
1274
1576
  // The ONE divergence: a model-less session (undefined OR the SDK
@@ -1295,6 +1597,8 @@ export async function runBroker(nodeId, startupAt = process.hrtime.bigint(), sta
1295
1597
  widgets: Object.fromEntries([...displayWidgets].map(([k, w]) => [k, { lines: [...w.lines], placement: w.placement }])),
1296
1598
  title: displayTitle,
1297
1599
  },
1600
+ toolGroupSummaries: Object.fromEntries(toolGroupSummaries),
1601
+ workingActivity,
1298
1602
  });
1299
1603
  // Send a client its catch-up snapshot. welcome.pending_dialog carries a single
1300
1604
  // still-in-flight dialog to a WRITABLE client attaching mid-dialog; an observer
@@ -1337,10 +1641,28 @@ export async function runBroker(nodeId, startupAt = process.hrtime.bigint(), sta
1337
1641
  // The single exit helper (plan §0): dispose the engine, close + unlink the
1338
1642
  // socket, exit 0. Idempotent — every trigger converges here.
1339
1643
  // -------------------------------------------------------------------------
1340
- const disposeAndExit = (_reason, status = 0) => {
1644
+ const completeExit = (status) => {
1341
1645
  if (disposed)
1342
1646
  return;
1343
1647
  disposed = true;
1648
+ // Daemon teardown cannot wait for a recap's 45-second model timeout. Preserve
1649
+ // exactly the newest already-capped input so the successor broker can finish
1650
+ // it, persist the normal summary, and remove the handoff record.
1651
+ const pendingSummary = [...generatingToolGroups.values()].at(-1);
1652
+ if (pendingSummary !== undefined) {
1653
+ try {
1654
+ atomicWriteJson(pendingToolSummaryPath, pendingSummary);
1655
+ }
1656
+ catch (error) {
1657
+ emitEvent({
1658
+ level: 'warn',
1659
+ event: 'broker.runtime.diagnostic',
1660
+ fields: {
1661
+ message: `failed to persist pending tool summary: ${error instanceof Error ? error.message : String(error)}`,
1662
+ },
1663
+ });
1664
+ }
1665
+ }
1344
1666
  streamWatchdog.reset();
1345
1667
  // disposeActiveSession atomically clears the process-global pointer before
1346
1668
  // disposing — whichever engine session (committed or an in-flight rebind
@@ -1381,6 +1703,34 @@ export async function runBroker(nodeId, startupAt = process.hrtime.bigint(), sta
1381
1703
  }
1382
1704
  process.exit(status);
1383
1705
  };
1706
+ const disposeAndExit = (reason, status = 0) => {
1707
+ if (disposed)
1708
+ return;
1709
+ const graceful = reason === 'shutdown-hook' || reason === 'shutdown-after-settle';
1710
+ // A graceful shutdown (yield, dormancy, daemon restart) is the final
1711
+ // boundary of the conversation's last tool group. Group state lives only in
1712
+ // this process, so a group left open here is unsummarizable forever and its
1713
+ // calls render raw in every future view of the transcript. Close it now —
1714
+ // generateSettledToolGroup registers its task synchronously, so the wait
1715
+ // below covers the summary it launches. Any pending tool arguments are
1716
+ // included without inventing a result; the visible abort remains outside
1717
+ // the recap and Ctrl+O retains every underlying call row.
1718
+ if (graceful)
1719
+ finishOpenToolGroup(true);
1720
+ // A normal terminal shutdown follows a visible boundary that may have just
1721
+ // launched a capped (45s) headless summary. Keep the broker and its child
1722
+ // alive until those already-started tasks persist/broadcast, rather than
1723
+ // process.exit() orphaning pi and losing the sidecar. Abrupt failures and
1724
+ // signals retain their existing immediate teardown behavior.
1725
+ if (graceful && summaryTasks.size > 0) {
1726
+ if (disposePending)
1727
+ return;
1728
+ disposePending = true;
1729
+ void Promise.allSettled([...summaryTasks]).finally(() => completeExit(status));
1730
+ return;
1731
+ }
1732
+ completeExit(status);
1733
+ };
1384
1734
  // A stophook can request shutdown while pi is still unwinding a completed
1385
1735
  // turn. Let that turn reach agent_settled first: inbox delivery commits its
1386
1736
  // durable cursor there, and only there. Explicit operator shutdown and OS
@@ -1491,6 +1841,7 @@ export async function runBroker(nodeId, startupAt = process.hrtime.bigint(), sta
1491
1841
  return;
1492
1842
  }
1493
1843
  }
1844
+ observeToolGroupEvent(event);
1494
1845
  relayEvent(event, generation);
1495
1846
  if (event.type === 'agent_settled')
1496
1847
  scheduleFaultRetry();
@@ -1526,6 +1877,9 @@ export async function runBroker(nodeId, startupAt = process.hrtime.bigint(), sta
1526
1877
  providerFallbackInFlight = null;
1527
1878
  notFoundFallbackInFlight = null;
1528
1879
  reloadAuthInFlight = null;
1880
+ openToolGroup = undefined;
1881
+ toolGroupsByCallId.clear();
1882
+ sawAssistantProse = false;
1529
1883
  await candidateSession.bindExtensions({
1530
1884
  uiContext,
1531
1885
  // 'rpc' — NOT 'print'. Per pi docs/rpc.md:1078 this is exactly our shape:
@@ -1689,6 +2043,36 @@ export async function runBroker(nodeId, startupAt = process.hrtime.bigint(), sta
1689
2043
  failure = err;
1690
2044
  sendFrame(client, { type: 'error', code: 'engine_error', message: String(err) });
1691
2045
  };
2046
+ // `prompt { interrupt: true }` is one atomic viewer intention: end whatever
2047
+ // is producing now, then start this as a new turn. A normal mid-stream
2048
+ // prompt deliberately steers into pi's queue; the whip must not.
2049
+ if (frame.type === 'prompt' && frame.interrupt === true) {
2050
+ return (async () => {
2051
+ if (session.isBashRunning) {
2052
+ session.abortBash();
2053
+ }
2054
+ else {
2055
+ // A prompt can be in pi's async setup before isStreaming flips. Match
2056
+ // the correlated interrupt's guard so a whip cannot silently miss it.
2057
+ if (!session.isStreaming && pendingPromptDispatches > 0) {
2058
+ const deadline = Date.now() + 5_000;
2059
+ while (Date.now() < deadline && !session.isStreaming && pendingPromptDispatches > 0) {
2060
+ await new Promise((resolve) => setTimeout(resolve, 25));
2061
+ }
2062
+ }
2063
+ if (session.isStreaming)
2064
+ await session.abort();
2065
+ }
2066
+ return driveEngine(client, {
2067
+ type: 'prompt',
2068
+ text: frame.text,
2069
+ ...(frame.images !== undefined ? { images: frame.images } : {}),
2070
+ });
2071
+ })().catch((err) => {
2072
+ relayError(err);
2073
+ return failure;
2074
+ });
2075
+ }
1692
2076
  // Hidden `crtr-memory-refs` guidance rides ALONGSIDE the human frame's own
1693
2077
  // engine call, never rewriting `frame.text` — see resolveFrameGuidance /
1694
2078
  // chooseGuidanceDeliveryMode above. A failed SEND (as opposed to a failed
@@ -1710,38 +2094,65 @@ export async function runBroker(nodeId, startupAt = process.hrtime.bigint(), sta
1710
2094
  // before the caught promise resolves, so reading it after the chain is
1711
2095
  // the call's verdict.
1712
2096
  const settle = (p) => p.then(() => failure);
1713
- const route = resolveEngineRoute(frame, session.isStreaming);
1714
- const guidance = resolveFrameGuidance(frame.text);
1715
- if (guidance === null) {
2097
+ const dispatch = () => {
2098
+ const route = resolveEngineRoute(frame, session.isStreaming);
2099
+ const guidance = resolveFrameGuidance(frame.text);
2100
+ if (guidance === null) {
2101
+ if (route.call === 'prompt') {
2102
+ // A routed prompt STARTS a turn — including an idle follow_up
2103
+ // escalated by m-C — so it gets a fresh operation id. A true
2104
+ // (mid-stream) followUp rides the current turn's operation, like
2105
+ // steer.
2106
+ return settle(trackPromptDispatch(operationIdContext.fresh(() => session.prompt(frame.text, route.options).catch(relayError))));
2107
+ }
2108
+ // C1: pi's followUp() takes images as a POSITIONAL 2nd arg (`followUp(text, images?)`).
2109
+ return settle(session.followUp(frame.text, frame.images).catch(relayError));
2110
+ }
2111
+ const deliverAs = chooseGuidanceDeliveryMode(frame.type, route.call, session.isStreaming);
1716
2112
  if (route.call === 'prompt') {
1717
- // A routed prompt STARTS a turn — including an idle follow_up
1718
- // escalated by m-C — so it gets a fresh operation id. A true
1719
- // (mid-stream) followUp rides the current turn's operation, like
1720
- // steer.
1721
- return settle(trackPromptDispatch(operationIdContext.fresh(() => session.prompt(frame.text, route.options).catch(relayError))));
2113
+ if (deliverAs === 'nextTurn') {
2114
+ // The ONLY ordering where guidance precedes the human call — pi's
2115
+ // prompt() reads+clears its pending-nextTurn queue synchronously
2116
+ // at call time, injecting it into the SAME turn as the new user
2117
+ // message. promptWithNextTurnGuidance queues the guidance and then
2118
+ // ATOMICALLY clears its own pending entry if prompt() settles
2119
+ // without ever reaching that drain (an early throw/return leaves
2120
+ // nothing stranded to leak into a later, unrelated turn).
2121
+ return settle(trackPromptDispatch(promptWithNextTurnGuidance(session, guidance, () => operationIdContext.fresh(() => session.prompt(frame.text, route.options))).catch(relayError)));
2122
+ }
2123
+ return settle(trackPromptDispatch(operationIdContext.fresh(() => session.prompt(frame.text, route.options).catch(relayError).then(() => sendGuidance(guidance, deliverAs)))));
1722
2124
  }
1723
- // C1: pi's followUp() takes images as a POSITIONAL 2nd arg (`followUp(text, images?)`).
1724
- return settle(session.followUp(frame.text, frame.images).catch(relayError));
1725
- }
1726
- const deliverAs = chooseGuidanceDeliveryMode(frame.type, route.call, session.isStreaming);
1727
- if (route.call === 'prompt') {
1728
- if (deliverAs === 'nextTurn') {
1729
- // The ONLY ordering where guidance precedes the human call — pi's
1730
- // prompt() reads+clears its pending-nextTurn queue synchronously
1731
- // at call time, injecting it into the SAME turn as the new user
1732
- // message. promptWithNextTurnGuidance queues the guidance and then
1733
- // ATOMICALLY clears its own pending entry if prompt() settles
1734
- // without ever reaching that drain (an early throw/return leaves
1735
- // nothing stranded to leak into a later, unrelated turn).
1736
- return settle(trackPromptDispatch(promptWithNextTurnGuidance(session, guidance, () => operationIdContext.fresh(() => session.prompt(frame.text, route.options))).catch(relayError)));
2125
+ // deliverAs is always 'followUp' here per chooseGuidanceDeliveryMode's
2126
+ // contract (a followUp routeCall never yields 'nextTurn').
2127
+ return settle(session.followUp(frame.text, frame.images).catch(relayError).then(() => sendGuidance(guidance, deliverAs)));
2128
+ };
2129
+ // /login acknowledges before its asynchronous local refresh completes.
2130
+ // An idle turn must not overtake that refresh and launch on the stale
2131
+ // route; true mid-stream follow-ups keep their immediate queue behavior.
2132
+ const authReload = session.isStreaming ? undefined : reloadAuthInFlight?.promise;
2133
+ if (authReload === undefined)
2134
+ return dispatch();
2135
+ const waitingGeneration = installedGeneration;
2136
+ const waitingSession = session;
2137
+ return authReload.then(() => {
2138
+ if (installedGeneration !== waitingGeneration || session !== waitingSession) {
2139
+ relayError(new Error('session changed while prompt waited for authentication reload'));
2140
+ return failure;
1737
2141
  }
1738
- return settle(trackPromptDispatch(operationIdContext.fresh(() => session.prompt(frame.text, route.options).catch(relayError).then(() => sendGuidance(guidance, deliverAs)))));
1739
- }
1740
- // deliverAs is always 'followUp' here per chooseGuidanceDeliveryMode's
1741
- // contract (a followUp routeCall never yields 'nextTurn').
1742
- return settle(session.followUp(frame.text, frame.images).catch(relayError).then(() => sendGuidance(guidance, deliverAs)));
2142
+ return dispatch();
2143
+ });
1743
2144
  }
1744
2145
  case 'steer': {
2146
+ // A viewer submits ordinary text as `steer` while the agent is running,
2147
+ // including a slash command injected by the tmux prefix menu. pi's
2148
+ // steer() deliberately rejects registered extension commands because
2149
+ // they cannot be queued; prompt() performs the command preflight first
2150
+ // and executes it immediately even while streaming. It also expands a
2151
+ // matching skill/template before applying the same steer behavior.
2152
+ if (resolveSteerCall(frame.text, session) === 'prompt') {
2153
+ void session.prompt(frame.text, { images: frame.images, streamingBehavior: 'steer' }).catch(relayError);
2154
+ break;
2155
+ }
1745
2156
  const guidance = resolveFrameGuidance(frame.text);
1746
2157
  // C1: pi's steer() takes images as a POSITIONAL 2nd arg (`steer(text, images?)`).
1747
2158
  if (guidance === null) {
@@ -1810,6 +2221,9 @@ export async function runBroker(nodeId, startupAt = process.hrtime.bigint(), sta
1810
2221
  // Order before any held assistant message_update, so a `!` run mid-stream
1811
2222
  // never renders ahead of the update it follows.
1812
2223
  flushPendingUpdate();
2224
+ // The live bash component is a visible transcript boundary, even though
2225
+ // its persisted bashExecution message arrives later via the session.
2226
+ finishOpenToolGroup();
1813
2227
  broadcast({ type: 'bash_start', command, excludeFromContext: exclude });
1814
2228
  void session
1815
2229
  .executeBash(command, (chunk) => broadcast({ type: 'bash_output', chunk }), {
@@ -1867,6 +2281,16 @@ export async function runBroker(nodeId, startupAt = process.hrtime.bigint(), sta
1867
2281
  // pass no id (unchanged behavior).
1868
2282
  ...(id !== undefined ? { id } : {}),
1869
2283
  });
2284
+ const isPreBoundaryReviewEntry = (entryId) => reviewCompanion &&
2285
+ !session.sessionManager.getBranch(entryId).some((entry) => entry.type === 'custom_message' && entry.customType === REVIEW_BOUNDARY_CUSTOM_TYPE);
2286
+ const reviewBoundaryRefusalTo = (client, targetId) => {
2287
+ const refusal = new ReviewBoundaryNavigationError(targetId);
2288
+ sendFrame(client, {
2289
+ type: 'error',
2290
+ code: refusal.code,
2291
+ message: refusal.message,
2292
+ });
2293
+ };
1870
2294
  /** Resolve a `provider/id` model spec against the LIVE services registry (which
1871
2295
  * carries any extension-registered providers). Pure: returns undefined on a
1872
2296
  * malformed spec or an unknown model. */
@@ -2007,17 +2431,29 @@ export async function runBroker(nodeId, startupAt = process.hrtime.bigint(), sta
2007
2431
  });
2008
2432
  const buildGetTreeData = (id) => {
2009
2433
  const sm = session.sessionManager;
2010
- const forkPoints = session.getUserMessagesForForking().map((u) => ({
2434
+ const tree = reviewCompanion ? boundaryRootedTree(sm.getTree()) : sm.getTree();
2435
+ const visibleEntryIds = new Set();
2436
+ const collectEntryIds = (nodes) => {
2437
+ for (const node of nodes) {
2438
+ visibleEntryIds.add(node.entry.id);
2439
+ collectEntryIds(node.children);
2440
+ }
2441
+ };
2442
+ collectEntryIds(tree);
2443
+ const forkPoints = session.getUserMessagesForForking()
2444
+ .filter((u) => visibleEntryIds.has(u.entryId))
2445
+ .map((u) => ({
2011
2446
  id: u.entryId,
2012
2447
  text: u.text,
2013
2448
  timestamp: sm.getEntry(u.entryId)?.timestamp,
2014
2449
  }));
2450
+ const currentLeafId = sm.getLeafId();
2015
2451
  return {
2016
2452
  type: 'data',
2017
2453
  id,
2018
2454
  kind: 'get_tree',
2019
- tree: sm.getTree(),
2020
- currentLeafId: sm.getLeafId(),
2455
+ tree,
2456
+ currentLeafId: currentLeafId !== null && visibleEntryIds.has(currentLeafId) ? currentLeafId : null,
2021
2457
  forkPoints,
2022
2458
  };
2023
2459
  };
@@ -2365,7 +2801,7 @@ export async function runBroker(nodeId, startupAt = process.hrtime.bigint(), sta
2365
2801
  .cycleModel(frame.direction)
2366
2802
  .then(() => {
2367
2803
  ackTo(client, 'cycle_model');
2368
- broadcastModelChanged();
2804
+ broadcastModelChanged(true, undefined, true);
2369
2805
  })
2370
2806
  .catch(engineErrorTo(client));
2371
2807
  break;
@@ -2414,7 +2850,7 @@ export async function runBroker(nodeId, startupAt = process.hrtime.bigint(), sta
2414
2850
  if (nextSpec.thinkingLevel !== undefined)
2415
2851
  session.setThinkingLevel(nextSpec.thinkingLevel);
2416
2852
  ackTo(client, 'cycle_ladder');
2417
- broadcastModelChanged();
2853
+ broadcastModelChanged(true, undefined, true);
2418
2854
  })
2419
2855
  .catch(engineErrorTo(client));
2420
2856
  break;
@@ -2424,6 +2860,7 @@ export async function runBroker(nodeId, startupAt = process.hrtime.bigint(), sta
2424
2860
  break;
2425
2861
  try {
2426
2862
  session.cycleThinkingLevel();
2863
+ persistModelChoice(undefined, true);
2427
2864
  ackTo(client, 'cycle_thinking');
2428
2865
  }
2429
2866
  catch (err) {
@@ -2448,6 +2885,7 @@ export async function runBroker(nodeId, startupAt = process.hrtime.bigint(), sta
2448
2885
  break;
2449
2886
  try {
2450
2887
  session.setThinkingLevel(frame.level);
2888
+ persistModelChoice(undefined, true);
2451
2889
  ackTo(client, 'set_thinking_level');
2452
2890
  }
2453
2891
  catch (err) {
@@ -2602,6 +3040,10 @@ export async function runBroker(nodeId, startupAt = process.hrtime.bigint(), sta
2602
3040
  case 'navigate_tree': {
2603
3041
  if (notWritable(client, 'navigate the session tree'))
2604
3042
  break;
3043
+ if (isPreBoundaryReviewEntry(frame.targetId)) {
3044
+ reviewBoundaryRefusalTo(client, frame.targetId);
3045
+ break;
3046
+ }
2605
3047
  // navigateTree rewinds IN-PLACE (same session file, new leaf) and emits no
2606
3048
  // relayed event, so every viewer must be re-snapshotted onto the rewound
2607
3049
  // transcript — same reWelcomeAll the session-replacing ops use. The ack's
@@ -2672,6 +3114,10 @@ export async function runBroker(nodeId, startupAt = process.hrtime.bigint(), sta
2672
3114
  case 'fork': {
2673
3115
  if (notWritable(client, 'fork the session'))
2674
3116
  break;
3117
+ if (isPreBoundaryReviewEntry(frame.entryId)) {
3118
+ reviewBoundaryRefusalTo(client, frame.entryId);
3119
+ break;
3120
+ }
2675
3121
  runReplacement(client, 'fork', (rt) => rt.fork(frame.entryId));
2676
3122
  break;
2677
3123
  }
@@ -2724,58 +3170,61 @@ export async function runBroker(nodeId, startupAt = process.hrtime.bigint(), sta
2724
3170
  }
2725
3171
  case 'reload_auth': {
2726
3172
  // Open to any client, observers included: reload_auth is an idempotent
2727
- // local re-read of the shared auth.json + a model-registry refresh. It
2728
- // does NOT steer the conversation, so there is nothing to role-gate —
2729
- // which is what lets the daemon's canvas-wide fan (one /login propagated
2730
- // to every live broker) ride an observer connection.
3173
+ // local re-read of shared credentials + the runtime's availability
3174
+ // snapshot. It does not steer the conversation, so the daemon's
3175
+ // canvas-wide /login fan can ride an observer connection.
2731
3176
  try {
2732
- // pi 0.82: crouter's CredentialStore reads auth.json fresh under the
2733
- // lock on every call, so there is no cached credential view left to
2734
- // reload — only the runtime's models/availability snapshot has to be
2735
- // recomputed, and that is asynchronous. Every consumer below awaits
2736
- // the same promise so none of them reads a pre-login snapshot.
2737
- const authRefreshed = registryOf(services).refresh().catch((err) => {
2738
- emitEvent({ level: 'warn', event: 'broker.auth_reload.refresh_failed', error: err });
2739
- });
2740
3177
  ackTo(client, 'reload_auth');
2741
- void authRefreshed.then(() => broadcastModelChanged());
2742
- // Tier 3 re-drive: a model-less boot (no viable candidate at launch)
2743
- // stays live so the user can /login; once auth actually changes,
2744
- // re-run the SAME launch policy against the now-refreshed services
2745
- // and, if a model now resolves, adopt it and fire the deferred
2746
- // kickoff. handleFrame is not async (broker.ts) so this runs as a
2747
- // detached IIFE, the same async-in-sync-switch pattern used above
2748
- // for provider/not-found fallback.
2749
3178
  const authGeneration = installedGeneration;
2750
- if (sessionHasNoModel() && authGeneration !== undefined && reloadAuthInFlight === null) {
3179
+ const existingAuthReload = reloadAuthInFlight;
3180
+ let authReloadPromise;
3181
+ if (existingAuthReload !== null && existingAuthReload.generation === authGeneration) {
3182
+ authReloadPromise = existingAuthReload.promise;
3183
+ }
3184
+ if (authGeneration !== undefined && authReloadPromise === undefined) {
2751
3185
  const authSession = session;
2752
3186
  const authServices = services;
2753
- reloadAuthInFlight = authGeneration;
2754
- void (async () => {
2755
- try {
2756
- await authRefreshed;
3187
+ authReloadPromise = (async () => {
3188
+ // Credentials are read fresh by PoolCredentialStore; only the
3189
+ // ModelRuntime snapshot needs rebuilding. Authentication reloads
3190
+ // never fetch provider catalogs over the network.
3191
+ await authServices.modelRuntime.refresh({ allowNetwork: false });
3192
+ if (installedGeneration !== authGeneration || session !== authSession)
3193
+ return;
3194
+ const modelLess = authSession.model === undefined || isUnknownModel(authSession.model);
3195
+ const reconcilesLogicalRoute = cfg.modelExact !== true
3196
+ && cfg.modelProviderPinned !== true
3197
+ && (process.env['CRTR_MODEL_INTENT'] ?? '').trim() !== '';
3198
+ if (modelLess || reconcilesLogicalRoute) {
2757
3199
  const resolved = await resolveLaunchModel(authServices, cfg);
3200
+ if (installedGeneration !== authGeneration || session !== authSession)
3201
+ return;
2758
3202
  if (resolved !== undefined) {
2759
- await authSession.setModel(resolved.model);
2760
- if (installedGeneration !== authGeneration || session !== authSession)
2761
- return;
2762
- authSession.setThinkingLevel((resolved.thinkingLevel ?? 'off'));
2763
- broadcastModelChanged();
2764
- drivePendingKickoff();
3203
+ const currentSpec = formatModelSpec(authSession.model, authSession.thinkingLevel);
3204
+ const resolvedSpec = formatModelSpec(resolved.model, resolved.thinkingLevel);
3205
+ if (resolvedSpec !== null && resolvedSpec !== currentSpec) {
3206
+ await authSession.setModel(resolved.model);
3207
+ if (installedGeneration !== authGeneration || session !== authSession)
3208
+ return;
3209
+ authSession.setThinkingLevel((resolved.thinkingLevel ?? 'off'));
3210
+ }
2765
3211
  }
2766
3212
  }
2767
- catch (err) {
2768
- emitEvent({
2769
- level: 'error',
2770
- event: 'broker.auth_reload.redrive_failed',
2771
- error: err,
2772
- });
2773
- }
2774
- finally {
2775
- if (reloadAuthInFlight === authGeneration)
2776
- reloadAuthInFlight = null;
2777
- }
2778
- })();
3213
+ if (installedGeneration !== authGeneration || session !== authSession)
3214
+ return;
3215
+ // Persist an algorithmic route choice without clearing the logical
3216
+ // intent that makes future reconciliation possible.
3217
+ broadcastModelChanged();
3218
+ drivePendingKickoff();
3219
+ })().catch((err) => {
3220
+ emitEvent({ level: 'warn', event: 'broker.auth_reload.reconcile_failed', error: err });
3221
+ });
3222
+ const work = { generation: authGeneration, promise: authReloadPromise };
3223
+ reloadAuthInFlight = work;
3224
+ void authReloadPromise.finally(() => {
3225
+ if (reloadAuthInFlight === work)
3226
+ reloadAuthInFlight = null;
3227
+ });
2779
3228
  }
2780
3229
  const authFault = readFault(nodeId);
2781
3230
  const authFaultGeneration = installedGeneration;
@@ -2787,6 +3236,10 @@ export async function runBroker(nodeId, startupAt = process.hrtime.bigint(), sta
2787
3236
  authFaultRedriveInFlight = true;
2788
3237
  void (async () => {
2789
3238
  try {
3239
+ if (authReloadPromise !== undefined)
3240
+ await authReloadPromise;
3241
+ if (installedGeneration !== authFaultGeneration || session !== authFaultSession)
3242
+ return;
2790
3243
  const anchorEntryId = authFault.anchorEntryId;
2791
3244
  if (anchorEntryId !== undefined) {
2792
3245
  try {
@@ -3000,12 +3453,13 @@ export async function runBroker(nodeId, startupAt = process.hrtime.bigint(), sta
3000
3453
  // conversation instead of just the post-yield window; single-cycle sessions and
3001
3454
  // the fake-engine fixture (no tree accessors) fall through to plain
3002
3455
  // `buildSessionContext().messages` unchanged.
3003
- export function snapshotMessages(session) {
3456
+ export function snapshotMessages(session, reviewCompanion = false) {
3004
3457
  // Cast: the real SDK's `SessionEntry.data` is `unknown` (untyped custom-entry
3005
3458
  // payload), narrower than `CycleSessionManagerLike`'s structural read of the
3006
3459
  // `crtr-cycle` marker's `{cycle, fromLeaf}` shape — the fields we read off it
3007
3460
  // are ones ONLY this module's own `appendCustomEntry` call (above) ever writes.
3008
- return cycleAwareMessages(session.sessionManager);
3461
+ const messages = cycleAwareMessages(session.sessionManager);
3462
+ return visibleMessages(messages, { reviewCompanion });
3009
3463
  }
3010
3464
  // ---------------------------------------------------------------------------
3011
3465
  // resolveLaunchModel — the SOLE launch model-selection policy, module-scoped
@@ -3018,48 +3472,26 @@ export function snapshotMessages(session) {
3018
3472
  // alone on a failed re-drive attempt).
3019
3473
  // ---------------------------------------------------------------------------
3020
3474
  async function resolveLaunchModel(services, cfg) {
3021
- // Route declarations own preference order; the raw registry remains only a
3022
- // legacy tail for models a user did not declare.
3023
3475
  const routeRequest = modelRequestFromConfig(cfg.model, process.env['CRTR_MODEL_INTENT'], cfg.modelExact === true);
3024
- if (routeRequest !== undefined) {
3025
- const registry = registryOf(services);
3026
- const routes = expandModelCandidates(routeRequest, process.env['CRTR_NODE_CWD'] ?? cfg.cwd, process.env['CRTR_PROFILE_ID']);
3027
- const routeKeys = new Set(routes.map((candidate) => `${candidate.providerId}/${candidate.modelId}`));
3028
- // A concrete request may advance only through its declared same-model
3029
- // credential-source routes. The raw registry tail is logical-request legacy.
3030
- const tail = routeRequest.kind === 'logical'
3031
- ? registry.getAll()
3032
- .filter((model) => !routeKeys.has(`${model.provider}/${model.id}`))
3033
- .map((model, index) => ({ routeId: `registry:${model.provider}/${model.id}`, credentialSource: 'registry', family: model.provider, strength: 'strong', providerId: model.provider, modelId: model.id, rank: routes.length + index }))
3034
- : [];
3035
- const hasManagedAuthenticated = [...routes, ...tail].some((candidate) => {
3036
- const availability = probeRouteAvailability(candidate, registry);
3037
- return isManagedProvider(candidate.providerId) && availability.registered && availability.authenticated;
3038
- });
3039
- const selection = resolveProviderCandidates([...routes, ...tail].map((candidate) => ({
3040
- ...candidate,
3041
- ...probeRouteAvailability(candidate, registry),
3042
- automaticFallback: candidate.routeId.startsWith('registry:')
3043
- ? (isManagedProvider(candidate.providerId) || !hasManagedAuthenticated)
3044
- : true,
3045
- })), Date.now(), { pinnedToFirst: routeRequest.kind === 'exact' });
3046
- const selected = selection.candidates[0];
3047
- if (selected !== undefined) {
3048
- const model = registry.find(selected.providerId, selected.modelId);
3049
- if (model !== undefined) {
3050
- if ((selected.rank ?? 0) > 0)
3051
- emitEvent({ level: 'warn', event: 'broker.model.launch_fallback', fields: { requested: cfg.model, selected: `${selected.providerId}/${selected.modelId}`, route_id: selected.routeId, credential_source: selected.credentialSource, rank: selected.rank } });
3052
- return { model, thinkingLevel: selected.thinkingLevel };
3053
- }
3054
- }
3055
- if (cfg.modelExact === true) {
3056
- const rejection = selection.rejected.map(({ candidate, reason }) => `'${candidate.providerId}/${candidate.modelId}' ${reason}`).join('; ');
3057
- recordFaultForCurrentNode({ link: 'crtr→pi', op: 'model resolution at launch', kind: 'model-not-found', retry: { disposition: 'fatal' }, message: `exact-consult model '${cfg.model}' is unavailable — refusing to substitute (${rejection})` });
3058
- throw new Error(`[broker] FATAL: exact-consult model '${cfg.model}' is unavailable`);
3059
- }
3060
- emitEvent({ level: 'warn', event: 'broker.model.none_viable', fields: { rejected_candidates: selection.rejected.slice(0, 16).map(({ candidate, reason }) => ({ provider: candidate.providerId, model: candidate.modelId, reason, route_id: candidate.routeId, credential_source: candidate.credentialSource })), rejected_candidates_omitted: Math.max(0, selection.rejected.length - 16) } });
3476
+ if (routeRequest === undefined)
3061
3477
  return undefined;
3478
+ const selection = selectInitialModelRoute(routeRequest, registryOf(services), {
3479
+ cwd: process.env['CRTR_NODE_CWD'] ?? cfg.cwd,
3480
+ profileId: process.env['CRTR_PROFILE_ID'],
3481
+ });
3482
+ const selected = selection.selected;
3483
+ if (selected !== undefined) {
3484
+ const candidate = selected.candidate;
3485
+ if ((candidate.rank ?? 0) > 0)
3486
+ emitEvent({ level: 'warn', event: 'broker.model.launch_fallback', fields: { requested: cfg.model, selected: `${candidate.providerId}/${candidate.modelId}`, route_id: candidate.routeId, credential_source: candidate.credentialSource, rank: candidate.rank } });
3487
+ return { model: selected.model, thinkingLevel: candidate.thinkingLevel };
3488
+ }
3489
+ if (cfg.modelExact === true) {
3490
+ const rejection = selection.rejected.map(({ candidate, reason }) => `'${candidate.providerId}/${candidate.modelId}' ${reason}`).join('; ');
3491
+ recordFaultForCurrentNode({ link: 'crtr→pi', op: 'model resolution at launch', kind: 'model-not-found', retry: { disposition: 'fatal' }, message: `exact-consult model '${cfg.model}' is unavailable — refusing to substitute (${rejection})` });
3492
+ throw new Error(`[broker] FATAL: exact-consult model '${cfg.model}' is unavailable`);
3062
3493
  }
3494
+ emitEvent({ level: 'warn', event: 'broker.model.none_viable', fields: { rejected_candidates: selection.rejected.slice(0, 16).map(({ candidate, reason }) => ({ provider: candidate.providerId, model: candidate.modelId, reason, route_id: candidate.routeId, credential_source: candidate.credentialSource })), rejected_candidates_omitted: Math.max(0, selection.rejected.length - 16) } });
3063
3495
  return undefined;
3064
3496
  }
3065
3497
  /**