@north-light/crouter 0.3.172 → 0.3.174

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 (673) hide show
  1. package/bin/runtime-selector.mjs +8 -1
  2. package/dist/api/__tests__/{client.test.js → serial/client.test.js} +2 -135
  3. package/dist/api/client.d.ts +12 -21
  4. package/dist/api/client.js +22 -31
  5. package/dist/api/dto/broker-ops.d.ts +158 -0
  6. package/dist/api/dto/broker-ops.js +6 -0
  7. package/dist/api/dto/crons.d.ts +6 -0
  8. package/dist/api/dto/human.d.ts +7 -1
  9. package/dist/api/dto/inbox.d.ts +1 -71
  10. package/dist/api/dto/inbox.js +1 -9
  11. package/dist/api/dto/nodes.d.ts +6 -7
  12. package/dist/api/dto/reviews.d.ts +1 -11
  13. package/dist/api/routes.d.ts +7 -4
  14. package/dist/api/routes.js +8 -5
  15. package/dist/build-root.js +3 -3
  16. package/dist/builtin-memory/04-base-worker.md +1 -4
  17. package/dist/builtin-memory/05-kinds/advisor/00-base.md +1 -5
  18. package/dist/builtin-memory/05-kinds/advisor/01-orchestrator.md +2 -7
  19. package/dist/builtin-memory/05-kinds/design/00-base.md +1 -6
  20. package/dist/builtin-memory/05-kinds/developer/00-base.md +1 -6
  21. package/dist/builtin-memory/05-kinds/explore/00-base.md +1 -4
  22. package/dist/builtin-memory/05-kinds/explore/01-orchestrator.md +1 -2
  23. package/dist/builtin-memory/05-kinds/general/00-base.md +1 -3
  24. package/dist/builtin-memory/05-kinds/plan/00-base.md +1 -6
  25. package/dist/builtin-memory/05-kinds/plan/reviewers/00-base.md +1 -2
  26. package/dist/builtin-memory/05-kinds/plan/reviewers/architecture-fit.md +1 -3
  27. package/dist/builtin-memory/05-kinds/plan/reviewers/code-smells.md +1 -3
  28. package/dist/builtin-memory/05-kinds/plan/reviewers/pattern-consistency.md +1 -3
  29. package/dist/builtin-memory/05-kinds/plan/reviewers/requirements-coverage.md +1 -3
  30. package/dist/builtin-memory/05-kinds/plan/reviewers/security.md +1 -4
  31. package/dist/builtin-memory/05-kinds/review/companion/00-base.md +2 -3
  32. package/dist/builtin-memory/05-kinds/spec/00-base.md +1 -2
  33. package/dist/builtin-memory/advisor/council.md +1 -6
  34. package/dist/builtin-memory/design.md +2 -7
  35. package/dist/builtin-memory/development.md +9 -14
  36. package/dist/builtin-memory/internal/marketplaces.md +2 -6
  37. package/dist/builtin-memory/internal/nodes-and-canvas.md +1 -1
  38. package/dist/builtin-memory/internal/plugins.md +2 -8
  39. package/dist/builtin-memory/internal/storage-tiers.md +1 -2
  40. package/dist/builtin-memory/testing.md +41 -0
  41. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/__tests__/{provider-rotation.test.ts → serial/provider-rotation.test.ts} +106 -177
  42. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/claude-plugin-commands.ts +5 -15
  43. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/frontmatter-rules/index.ts +1 -28
  44. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/memory-slash-commands.ts +43 -88
  45. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/provider-rotation.js +55 -0
  46. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/provider-rotation.ts +60 -0
  47. package/dist/cli.js +2 -2
  48. package/dist/clients/attach/__tests__/attach-remote-readonly.test.js +37 -131
  49. package/dist/clients/attach/__tests__/chat-view-snapshot-ordering.test.js +71 -152
  50. package/dist/clients/attach/__tests__/context-message.test.js +16 -174
  51. package/dist/clients/attach/__tests__/crtr-output.test.js +36 -2069
  52. package/dist/clients/attach/__tests__/diagram.test.js +2 -2
  53. package/dist/clients/attach/__tests__/edit-diff.test.js +0 -4
  54. package/dist/clients/attach/__tests__/file-review-focus.test.js +2 -1
  55. package/dist/clients/attach/__tests__/group-activity.test.js +40 -229
  56. package/dist/clients/attach/__tests__/oauth-dialog-lifecycle.test.js +26 -76
  57. package/dist/clients/attach/__tests__/prompt-preview.test.js +4 -70
  58. package/dist/clients/attach/__tests__/ref-autocomplete.test.js +11 -125
  59. package/dist/clients/attach/__tests__/titled-editor-preview.test.js +14 -205
  60. package/dist/clients/attach/command.d.ts +2 -1
  61. package/dist/clients/attach/command.js +81 -76
  62. package/dist/clients/attach/input/capabilities.d.ts +27 -0
  63. package/dist/clients/attach/input/capabilities.js +96 -0
  64. package/dist/clients/attach/input/controller.d.ts +20 -182
  65. package/dist/clients/attach/input/controller.js +128 -537
  66. package/dist/clients/attach/input/overlay-owner.d.ts +41 -0
  67. package/dist/clients/attach/input/overlay-owner.js +199 -0
  68. package/dist/clients/attach/overlays/file-review.d.ts +0 -4
  69. package/dist/clients/attach/overlays/file-review.js +13 -5
  70. package/dist/clients/attach/overlays/graph.js +2 -10
  71. package/dist/clients/attach/render/chat-view.d.ts +4 -3
  72. package/dist/clients/attach/render/chat-view.js +39 -33
  73. package/dist/clients/attach/render/group-activity.d.ts +1 -0
  74. package/dist/clients/attach/render/group-activity.js +20 -13
  75. package/dist/clients/attach/render/group-recap.js +12 -3
  76. package/dist/clients/attach/session/connection.js +9 -15
  77. package/dist/clients/attach/session/editor-inventory.d.ts +6 -5
  78. package/dist/clients/attach/session/editor-inventory.js +7 -8
  79. package/dist/clients/attach/session/file-links.d.ts +13 -0
  80. package/dist/clients/attach/session/file-links.js +107 -0
  81. package/dist/clients/attach/session/frame.d.ts +3 -0
  82. package/dist/clients/attach/session/frame.js +10 -1
  83. package/dist/clients/attach/session/frames.js +2 -3
  84. package/dist/clients/attach/session/input-wiring.d.ts +4 -1
  85. package/dist/clients/attach/session/input-wiring.js +15 -14
  86. package/dist/clients/attach/session/mouse.d.ts +6 -1
  87. package/dist/clients/attach/session/mouse.js +8 -5
  88. package/dist/clients/attach/session/profile-files.js +18 -12
  89. package/dist/clients/attach/session/reconnect.js +1 -1
  90. package/dist/clients/attach/session/state-sync.js +1 -2
  91. package/dist/clients/attach/session/surface-host.d.ts +3 -2
  92. package/dist/clients/attach/session/surface-host.js +4 -4
  93. package/dist/clients/attach/slash/dispatch.d.ts +8 -31
  94. package/dist/clients/attach/slash/dispatch.js +34 -81
  95. package/dist/clients/attach/viewer.js +820 -860
  96. package/dist/clients/conversation/projection.d.ts +79 -0
  97. package/dist/clients/conversation/projection.js +341 -0
  98. package/dist/clients/inbox/__tests__/serial/inbox-controller.test.js +159 -0
  99. package/dist/clients/inbox/__tests__/serial/mount-panel.test.js +151 -0
  100. package/dist/clients/inbox/controller.d.ts +0 -4
  101. package/dist/clients/inbox/controller.js +0 -2
  102. package/dist/clients/inbox/review/__tests__/review-core.test.js +4 -293
  103. package/dist/clients/inbox/review/__tests__/teardown-notice.test.js +100 -0
  104. package/dist/clients/inbox/review/comments-client.d.ts +3 -2
  105. package/dist/clients/inbox/review/comments-client.js +12 -12
  106. package/dist/clients/inbox/review/companion-pane.d.ts +30 -0
  107. package/dist/clients/inbox/review/companion-pane.js +86 -0
  108. package/dist/clients/inbox/review/document-surface.d.ts +182 -0
  109. package/dist/clients/inbox/review/{document-region.js → document-surface.js} +252 -48
  110. package/dist/clients/inbox/review/frame.js +5 -4
  111. package/dist/clients/inbox/review/launch.d.ts +8 -8
  112. package/dist/clients/inbox/review/launch.js +52 -92
  113. package/dist/clients/inbox/review/nvim.d.ts +2 -0
  114. package/dist/clients/inbox/review/nvim.js +14 -12
  115. package/dist/clients/inbox/review/review-client.d.ts +12 -8
  116. package/dist/clients/inbox/review/review-client.js +46 -35
  117. package/dist/clients/inbox/review-adapter.d.ts +3 -10
  118. package/dist/clients/inbox/review-adapter.js +27 -19
  119. package/dist/clients/inbox/surface.d.ts +0 -4
  120. package/dist/clients/inbox/surface.js +0 -2
  121. package/dist/clients/surfaces/host.d.ts +11 -3
  122. package/dist/commands/__tests__/api-client-cold-start-diagnostic.test.js +7 -34
  123. package/dist/commands/__tests__/human.test.js +11 -322
  124. package/dist/commands/api-client.d.ts +5 -5
  125. package/dist/commands/api-client.js +6 -85
  126. package/dist/commands/canvas-browse.js +3 -29
  127. package/dist/commands/canvas-config.js +1 -1
  128. package/dist/commands/canvas-use.js +3 -3
  129. package/dist/commands/canvas.js +1 -1
  130. package/dist/commands/cron.js +5 -1
  131. package/dist/commands/human/queue.d.ts +0 -4
  132. package/dist/commands/human/queue.js +7 -40
  133. package/dist/commands/memory/list.d.ts +14 -0
  134. package/dist/commands/memory/list.js +35 -31
  135. package/dist/commands/memory/read.d.ts +7 -0
  136. package/dist/commands/memory/read.js +14 -8
  137. package/dist/commands/node/bash.d.ts +1 -0
  138. package/dist/commands/node/bash.js +198 -0
  139. package/dist/commands/node/create.d.ts +23 -0
  140. package/dist/commands/node/create.js +207 -0
  141. package/dist/commands/node/inspect.d.ts +2 -0
  142. package/dist/commands/node/inspect.js +302 -0
  143. package/dist/commands/node/lifecycle.d.ts +4 -0
  144. package/dist/commands/node/lifecycle.js +415 -0
  145. package/dist/commands/node/message.d.ts +1 -0
  146. package/dist/commands/node/message.js +164 -0
  147. package/dist/commands/node/pyramid.d.ts +2 -0
  148. package/dist/commands/node/pyramid.js +79 -0
  149. package/dist/commands/node/subscription.d.ts +1 -0
  150. package/dist/commands/node/subscription.js +82 -0
  151. package/dist/commands/node/wait.d.ts +1 -0
  152. package/dist/commands/node/wait.js +159 -0
  153. package/dist/commands/node-lifecycle-revive.js +10 -57
  154. package/dist/commands/node.d.ts +0 -42
  155. package/dist/commands/node.js +11 -2065
  156. package/dist/commands/search.js +1 -1
  157. package/dist/commands/surface/node/cycle.d.ts +1 -0
  158. package/dist/commands/surface/node/cycle.js +83 -0
  159. package/dist/commands/surface/node/focus.d.ts +1 -0
  160. package/dist/commands/surface/node/focus.js +67 -0
  161. package/dist/commands/surface/node/id.d.ts +1 -0
  162. package/dist/commands/surface/node/id.js +38 -0
  163. package/dist/commands/surface/node/pin.d.ts +1 -0
  164. package/dist/commands/surface/node/pin.js +42 -0
  165. package/dist/commands/surface/node/placement.d.ts +46 -0
  166. package/dist/commands/surface/node/placement.js +124 -0
  167. package/dist/commands/surface-node.js +4 -1
  168. package/dist/commands/surface-reload.js +1 -2
  169. package/dist/commands/surface-tmux-spread.js +1 -1
  170. package/dist/commands/surface.js +5 -20
  171. package/dist/commands/sys/__tests__/setup-core.test.js +18 -271
  172. package/dist/commands/sys/config.js +6 -6
  173. package/dist/commands/sys/logs.js +1 -1
  174. package/dist/commands/sys/panels/broker-limits-panel.d.ts +17 -0
  175. package/dist/commands/sys/panels/broker-limits-panel.js +104 -0
  176. package/dist/commands/sys/panels/dependencies-panel.d.ts +14 -0
  177. package/dist/commands/sys/panels/dependencies-panel.js +80 -0
  178. package/dist/commands/sys/panels/keybindings-panel.d.ts +56 -0
  179. package/dist/commands/sys/panels/keybindings-panel.js +703 -0
  180. package/dist/commands/sys/panels/keys-panel.d.ts +16 -0
  181. package/dist/commands/sys/panels/keys-panel.js +60 -0
  182. package/dist/commands/sys/panels/models-panel.d.ts +77 -0
  183. package/dist/commands/sys/panels/models-panel.js +647 -0
  184. package/dist/commands/sys/panels/panel.d.ts +45 -0
  185. package/dist/commands/sys/panels/panel.js +137 -0
  186. package/dist/commands/sys/panels/plugins-panel.d.ts +16 -0
  187. package/dist/commands/sys/panels/plugins-panel.js +80 -0
  188. package/dist/commands/sys/panels/prefs-panel.d.ts +21 -0
  189. package/dist/commands/sys/panels/prefs-panel.js +85 -0
  190. package/dist/commands/sys/panels/provider-panel.d.ts +62 -0
  191. package/dist/commands/sys/panels/provider-panel.js +538 -0
  192. package/dist/commands/sys/settings-shell.d.ts +55 -0
  193. package/dist/commands/sys/settings-shell.js +400 -0
  194. package/dist/commands/sys/settings.d.ts +2 -0
  195. package/dist/commands/sys/settings.js +31 -0
  196. package/dist/commands/sys/setup-core.d.ts +3 -33
  197. package/dist/commands/sys/setup-core.js +27 -78
  198. package/dist/commands/sys/setup-settings-runner.d.ts +24 -0
  199. package/dist/commands/sys/setup-settings-runner.js +63 -0
  200. package/dist/commands/sys/setup-wizard.d.ts +26 -125
  201. package/dist/commands/sys/setup-wizard.js +209 -2473
  202. package/dist/commands/sys/setup.js +5 -7
  203. package/dist/commands/sys.js +3 -2
  204. package/dist/core/__tests__/broker-extension-canvas-db-boundary.test.js +50 -0
  205. package/dist/core/__tests__/broker-launch-candidates.test.js +20 -9
  206. package/dist/core/__tests__/broker-preflight.test.js +1 -1
  207. package/dist/core/__tests__/canvas-inbox-watcher-hold.test.js +108 -27
  208. package/dist/core/__tests__/canvas-inbox-watcher.test.js +14 -0
  209. package/dist/core/__tests__/canvas.test.js +2 -36
  210. package/dist/core/__tests__/close.test.js +14 -3
  211. package/dist/core/__tests__/context-intro.test.js +32 -143
  212. package/dist/core/__tests__/cron-broker-capacity.test.js +22 -3
  213. package/dist/core/__tests__/daemon-boot.test.js +2 -20
  214. package/dist/core/__tests__/daemon-wedge.test.js +1 -1
  215. package/dist/core/__tests__/dead-node-policy-table.test.js +1 -18
  216. package/dist/core/__tests__/fixtures/fake-engine.d.ts +1 -1
  217. package/dist/core/__tests__/fixtures/fake-engine.js +2 -2
  218. package/dist/core/__tests__/fixtures/fake-pi-host.js +2 -2
  219. package/dist/core/__tests__/focuses.test.js +4 -98
  220. package/dist/core/__tests__/helpers/broker-clients.d.ts +0 -3
  221. package/dist/core/__tests__/helpers/broker-clients.js +0 -14
  222. package/dist/core/__tests__/helpers/harness.d.ts +0 -8
  223. package/dist/core/__tests__/helpers/harness.js +7 -87
  224. package/dist/core/__tests__/helpers/viewer-panes.js +1 -1
  225. package/dist/core/__tests__/inline-memory-refs.test.js +5 -134
  226. package/dist/core/__tests__/kickoff.test.js +1 -1
  227. package/dist/core/__tests__/memory-resolver-precedence.test.js +29 -1
  228. package/dist/core/__tests__/migration.test.js +35 -48
  229. package/dist/core/__tests__/on-read-dedup-resume.test.js +22 -12
  230. package/dist/core/__tests__/passive-subscription.test.js +26 -49
  231. package/dist/core/__tests__/preview-registry-sync.test.js +1 -2
  232. package/dist/core/__tests__/prune.test.js +94 -1
  233. package/dist/core/__tests__/review-model-floor.test.js +0 -32
  234. package/dist/core/__tests__/seam/broker-attach-multiclient.test.js +255 -0
  235. package/dist/core/__tests__/{full → seam}/broker-attach-stream.test.js +39 -7
  236. package/dist/core/__tests__/{full → seam}/broker-crash-teardown.test.js +30 -29
  237. package/dist/core/__tests__/{full → seam}/broker-pane-resolution.test.js +54 -5
  238. package/dist/core/__tests__/{full → seam}/broker-provider-retry.test.js +134 -82
  239. package/dist/core/__tests__/{full → seam}/broker-whip-interrupt.test.js +15 -4
  240. package/dist/core/__tests__/{full/idle-release-settlement.test.js → seam/dormancy-release.test.js} +69 -17
  241. package/dist/core/__tests__/{full/yield-message-transaction.test.js → seam/yield-refresh-transaction.test.js} +31 -9
  242. package/dist/core/__tests__/secrets.test.js +1 -7
  243. package/dist/core/__tests__/{broker-fork-seam.test.js → serial/broker-fork-seam.test.js} +58 -7
  244. package/dist/core/__tests__/{broker-sdk-wiring.test.js → serial/broker-sdk-wiring.test.js} +18 -162
  245. package/dist/core/__tests__/{broker-snapshot-history.test.js → serial/broker-snapshot-history.test.js} +9 -9
  246. package/dist/core/__tests__/{command-plugins.test.js → serial/command-plugins.test.js} +105 -15
  247. package/dist/core/__tests__/{deferred-no-wake.test.js → serial/deferred-no-wake.test.js} +23 -20
  248. package/dist/core/__tests__/{flagship-lifecycle.test.js → serial/flagship-lifecycle.test.js} +7 -7
  249. package/dist/core/__tests__/{host-teardown-process-group.test.js → serial/host-teardown-process-group.test.js} +6 -6
  250. package/dist/core/__tests__/{human-deliver-e2e.test.js → serial/human-deliver-e2e.test.js} +12 -12
  251. package/dist/core/__tests__/{live-mutation.test.js → serial/live-mutation.test.js} +5 -5
  252. package/dist/core/__tests__/{refresh-stall-recycle.test.js → serial/refresh-stall-recycle.test.js} +5 -14
  253. package/dist/core/__tests__/{revive.test.js → serial/revive.test.js} +15 -100
  254. package/dist/core/__tests__/{spawn-root.test.js → serial/spawn-root.test.js} +32 -15
  255. package/dist/core/__tests__/serial/subscription-delivery.test.js +126 -0
  256. package/dist/core/__tests__/serial/tmux-surface.test.js +256 -0
  257. package/dist/core/__tests__/{worktree.test.js → serial/worktree.test.js} +15 -15
  258. package/dist/core/__tests__/session-model.test.js +0 -18
  259. package/dist/core/__tests__/spawn-worktree-id-conflict.test.js +9 -0
  260. package/dist/core/__tests__/watchdog-abort-arms-retry.test.js +50 -0
  261. package/dist/core/broker-client/__tests__/transport-relay.test.js +15 -16
  262. package/dist/core/broker-client/__tests__/transport-socket.test.js +28 -0
  263. package/dist/core/broker-client/client.d.ts +2 -2
  264. package/dist/core/broker-client/client.js +4 -7
  265. package/dist/core/broker-client/transport-relay.d.ts +13 -19
  266. package/dist/core/broker-client/transport-relay.js +19 -34
  267. package/dist/core/broker-client/transport-socket.d.ts +2 -2
  268. package/dist/core/broker-client/transport-socket.js +9 -2
  269. package/dist/core/broker-client/transport.d.ts +7 -3
  270. package/dist/core/broker-client/transport.js +4 -4
  271. package/dist/core/canvas/__tests__/attention.test.js +1 -18
  272. package/dist/core/canvas/__tests__/remote-canvas-source.test.js +7 -64
  273. package/dist/core/canvas/__tests__/remote-transport.test.js +2 -3
  274. package/dist/core/canvas/__tests__/render-remote.test.js +7 -52
  275. package/dist/core/canvas/browse/app.d.ts +2 -17
  276. package/dist/core/canvas/browse/app.js +16 -88
  277. package/dist/core/canvas/browse/model.d.ts +0 -4
  278. package/dist/core/canvas/browse/model.js +1 -12
  279. package/dist/core/canvas/browse/render.js +6 -3
  280. package/dist/core/canvas/crons.d.ts +8 -5
  281. package/dist/core/canvas/crons.js +7 -6
  282. package/dist/core/canvas/extensions.d.ts +7 -3
  283. package/dist/core/canvas/extensions.js +16 -4
  284. package/dist/core/canvas/nav-model.d.ts +0 -4
  285. package/dist/core/canvas/nav-model.js +2 -14
  286. package/dist/core/canvas/node-order.d.ts +3 -0
  287. package/dist/core/canvas/node-order.js +12 -0
  288. package/dist/core/canvas/remote-canvas-source.d.ts +3 -6
  289. package/dist/core/canvas/remote-canvas-source.js +8 -15
  290. package/dist/core/command.js +11 -4
  291. package/dist/core/config.js +3 -1
  292. package/dist/core/events/envelope.js +1 -1
  293. package/dist/core/events/read.js +7 -21
  294. package/dist/core/events/source.d.ts +0 -2
  295. package/dist/core/events/source.js +0 -16
  296. package/dist/core/events/types.d.ts +1 -1
  297. package/dist/core/fault-classifier.d.ts +1 -1
  298. package/dist/core/feed/passive.d.ts +0 -11
  299. package/dist/core/feed/passive.js +1 -62
  300. package/dist/core/human/__tests__/{inbox-core.test.js → serial/inbox-core.test.js} +6 -6
  301. package/dist/core/keybindings/__tests__/resolve.test.js +1 -96
  302. package/dist/core/keybindings/background-bash.js +3 -4
  303. package/dist/core/keybindings/catalog.js +1 -1
  304. package/dist/core/memory/inline-ref-grammar.js +3 -3
  305. package/dist/core/memory-resolver.d.ts +11 -25
  306. package/dist/core/memory-resolver.js +51 -33
  307. package/dist/core/model-routes.d.ts +15 -0
  308. package/dist/core/model-routes.js +12 -0
  309. package/dist/core/pool-credential-store.js +1 -1
  310. package/dist/core/preview-registry.js +8 -9
  311. package/dist/core/profiles/__tests__/root-profile.test.js +4 -14
  312. package/dist/core/review/realize.d.ts +0 -13
  313. package/dist/core/review/realize.js +0 -40
  314. package/dist/core/review/types.d.ts +1 -1
  315. package/dist/core/runtime/__tests__/engine-route.test.js +2 -2
  316. package/dist/core/runtime/bearings.js +1 -11
  317. package/dist/core/runtime/broker/client-registry.d.ts +33 -0
  318. package/dist/core/runtime/broker/client-registry.js +164 -0
  319. package/dist/core/runtime/broker/daemon-ops.d.ts +19 -0
  320. package/dist/core/runtime/broker/daemon-ops.js +49 -0
  321. package/dist/core/runtime/broker/event-projection.d.ts +42 -0
  322. package/dist/core/runtime/broker/event-projection.js +178 -0
  323. package/dist/core/runtime/broker/fault-retry.d.ts +39 -0
  324. package/dist/core/runtime/broker/fault-retry.js +277 -0
  325. package/dist/core/runtime/broker/frame-dispatch.d.ts +238 -0
  326. package/dist/core/runtime/broker/frame-dispatch.js +1537 -0
  327. package/dist/core/runtime/broker/inbox.d.ts +22 -0
  328. package/dist/core/runtime/broker/inbox.js +167 -0
  329. package/dist/core/runtime/broker/passive.d.ts +16 -0
  330. package/dist/core/runtime/broker/passive.js +44 -0
  331. package/dist/core/runtime/broker/read-ops.d.ts +23 -0
  332. package/dist/core/runtime/broker/read-ops.js +150 -0
  333. package/dist/core/runtime/broker/rebind.d.ts +74 -0
  334. package/dist/core/runtime/broker/rebind.js +211 -0
  335. package/dist/core/runtime/broker/tool-groups.d.ts +39 -0
  336. package/dist/core/runtime/broker/tool-groups.js +244 -0
  337. package/dist/core/runtime/broker-extension-render.d.ts +11 -0
  338. package/dist/core/runtime/broker-extension-render.js +181 -0
  339. package/dist/core/runtime/broker-persona-guidance.d.ts +13 -0
  340. package/dist/core/runtime/broker-persona-guidance.js +58 -0
  341. package/dist/core/runtime/broker-protocol.d.ts +1 -1
  342. package/dist/core/runtime/broker-protocol.js +1 -1
  343. package/dist/core/runtime/broker.d.ts +5 -196
  344. package/dist/core/runtime/broker.js +276 -3059
  345. package/dist/core/runtime/canvas-extensions.js +11 -2
  346. package/dist/core/runtime/fault.js +1 -1
  347. package/dist/core/runtime/model-selection.d.ts +9 -0
  348. package/dist/core/runtime/model-selection.js +12 -0
  349. package/dist/core/runtime/model-swap.d.ts +3 -3
  350. package/dist/core/runtime/model-swap.js +33 -25
  351. package/dist/core/runtime/placement-tmux.d.ts +3 -14
  352. package/dist/core/runtime/placement-tmux.js +11 -80
  353. package/dist/core/runtime/placement.d.ts +4 -2
  354. package/dist/core/runtime/placement.js +7 -12
  355. package/dist/core/runtime/promote.d.ts +14 -1
  356. package/dist/core/runtime/promote.js +1 -1
  357. package/dist/core/runtime/reset.d.ts +1 -1
  358. package/dist/core/runtime/roadmap.js +1 -1
  359. package/dist/core/runtime/spawn.d.ts +8 -0
  360. package/dist/core/runtime/spawn.js +36 -6
  361. package/dist/core/runtime/stop-guard.d.ts +4 -3
  362. package/dist/core/runtime/stop-guard.js +36 -18
  363. package/dist/core/runtime/stop-signals.d.ts +4 -1
  364. package/dist/core/runtime/stream-watchdog.js +4 -5
  365. package/dist/core/runtime/structured-output.d.ts +15 -2
  366. package/dist/core/runtime/structured-output.js +35 -12
  367. package/dist/core/runtime/tmux-bindings.d.ts +25 -0
  368. package/dist/core/runtime/tmux-bindings.js +682 -0
  369. package/dist/core/runtime/tmux-chrome.d.ts +2 -1
  370. package/dist/core/runtime/tmux-chrome.js +3 -2
  371. package/dist/core/runtime/{tmux.d.ts → tmux-driver.d.ts} +18 -26
  372. package/dist/core/runtime/tmux-driver.js +598 -0
  373. package/dist/core/runtime/view-socket.d.ts +14 -0
  374. package/dist/core/runtime/view-socket.js +83 -0
  375. package/dist/core/session-model/session-state.d.ts +1 -2
  376. package/dist/core/session-model/session-state.js +5 -8
  377. package/dist/core/spawn.d.ts +1 -1
  378. package/dist/core/spawn.js +2 -2
  379. package/dist/core/substrate/gate.d.ts +1 -1
  380. package/dist/core/substrate/on-read-pure.d.ts +7 -0
  381. package/dist/core/substrate/on-read-pure.js +272 -0
  382. package/dist/core/termrender/termrender.js +32 -2
  383. package/dist/core/user-settings.d.ts +23 -1
  384. package/dist/core/user-settings.js +18 -1
  385. package/dist/daemon/api/__tests__/node-model-validation.test.js +134 -0
  386. package/dist/daemon/api/__tests__/review-terminal-conflict.test.js +98 -0
  387. package/dist/daemon/api/__tests__/{full → seam}/api-server.test.js +6 -48
  388. package/dist/daemon/api/__tests__/{full → seam}/b10-attach-modelauth.test.js +17 -44
  389. package/dist/daemon/api/__tests__/{full/push-parity.test.js → seam/leaf-api-parity.test.js} +61 -42
  390. package/dist/daemon/api/bridge.js +40 -81
  391. package/dist/daemon/api/handlers/broker-ops.d.ts +2 -0
  392. package/dist/daemon/api/handlers/broker-ops.js +383 -0
  393. package/dist/daemon/api/handlers/crons.js +9 -4
  394. package/dist/daemon/api/handlers/files.js +6 -7
  395. package/dist/daemon/api/handlers/inbox.js +2 -340
  396. package/dist/daemon/api/handlers/nodes.d.ts +10 -1
  397. package/dist/daemon/api/handlers/nodes.js +74 -39
  398. package/dist/daemon/api/handlers/review-comments.js +5 -1
  399. package/dist/daemon/api/handlers/reviews.js +0 -19
  400. package/dist/daemon/api/map.js +25 -4
  401. package/dist/daemon/api/router.js +2 -3
  402. package/dist/daemon/api/server.js +5 -7
  403. package/dist/daemon/cron-run.d.ts +4 -0
  404. package/dist/daemon/cron-run.js +30 -13
  405. package/dist/daemon/crtrd.d.ts +3 -58
  406. package/dist/daemon/crtrd.js +28 -1244
  407. package/dist/daemon/manage.js +0 -1
  408. package/dist/daemon/reconcilers/broker-supervision.d.ts +50 -0
  409. package/dist/daemon/reconcilers/broker-supervision.js +549 -0
  410. package/dist/daemon/reconcilers/cron-lane.d.ts +10 -0
  411. package/dist/daemon/reconcilers/cron-lane.js +32 -0
  412. package/dist/daemon/reconcilers/dormant-inbox.d.ts +18 -0
  413. package/dist/daemon/reconcilers/dormant-inbox.js +143 -0
  414. package/dist/daemon/reconcilers/storage-maintenance.d.ts +22 -0
  415. package/dist/daemon/reconcilers/storage-maintenance.js +213 -0
  416. package/dist/daemon/review/finish.js +2 -5
  417. package/dist/pi-extensions/__tests__/canvas-context-intro.test.js +34 -53
  418. package/dist/pi-extensions/__tests__/canvas-recap.test.js +16 -113
  419. package/dist/pi-extensions/__tests__/canvas-stophook-agentend.test.js +72 -99
  420. package/dist/pi-extensions/__tests__/canvas-stophook-context-nudge.test.js +29 -12
  421. package/dist/pi-extensions/broker-local.d.ts +20 -0
  422. package/dist/pi-extensions/broker-local.js +89 -0
  423. package/dist/pi-extensions/canvas-context-intro.d.ts +3 -3
  424. package/dist/pi-extensions/canvas-context-intro.js +22 -11
  425. package/dist/pi-extensions/canvas-doc-substrate.js +17 -34
  426. package/dist/pi-extensions/canvas-goal-capture.js +7 -12
  427. package/dist/pi-extensions/canvas-inbox-watcher.js +124 -139
  428. package/dist/pi-extensions/canvas-passive-context.d.ts +3 -3
  429. package/dist/pi-extensions/canvas-passive-context.js +22 -18
  430. package/dist/pi-extensions/canvas-recap.d.ts +2 -2
  431. package/dist/pi-extensions/canvas-recap.js +48 -21
  432. package/dist/pi-extensions/canvas-review-boundary.js +10 -4
  433. package/dist/pi-extensions/canvas-stophook.d.ts +0 -5
  434. package/dist/pi-extensions/canvas-stophook.js +149 -431
  435. package/dist/pi-extensions/canvas-structured-output.js +52 -30
  436. package/dist/pi-extensions/envelope-merge.d.ts +13 -0
  437. package/dist/pi-extensions/envelope-merge.js +29 -0
  438. package/dist/shared/working-activity.d.ts +3 -2
  439. package/dist/shared/working-activity.js +3 -2
  440. package/dist/types.d.ts +6 -5
  441. package/dist/types.js +2 -2
  442. package/docs/compat/hearth-crtr-v1.md +4 -12
  443. package/docs/compat/hearth-crtr-v2.md +3 -11
  444. package/docs/compat/hearth-crtr-v3.md +4 -12
  445. package/docs/compat/hearth-crtr-v4.md +3 -11
  446. package/docs/compat/hearth-crtr-v5.md +3 -11
  447. package/docs/public-api.md +3 -7
  448. package/package.json +7 -49
  449. package/runtime.lock.json +2766 -14533
  450. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/__tests__/claude-plugin-commands.test.ts +0 -49
  451. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/__tests__/memory-slash-commands.test.ts +0 -194
  452. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/__tests__/statusline.test.ts +0 -68
  453. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/frontmatter-rules/test.ts +0 -119
  454. package/dist/clients/attach/__tests__/attach-chrome-remote.test.js +0 -439
  455. package/dist/clients/attach/__tests__/attach-keybindings.test.js +0 -170
  456. package/dist/clients/attach/__tests__/crtr-output-coverage.test.js +0 -57
  457. package/dist/clients/attach/__tests__/editor-frame-title.test.js +0 -19
  458. package/dist/clients/attach/__tests__/input-controller-extension-command.test.js +0 -32
  459. package/dist/clients/attach/__tests__/markdown-headings.test.js +0 -35
  460. package/dist/clients/attach/__tests__/session-identity-refresh.test.js +0 -32
  461. package/dist/clients/conversation/conversation.d.ts +0 -65
  462. package/dist/clients/conversation/conversation.js +0 -318
  463. package/dist/clients/conversation/frames.d.ts +0 -30
  464. package/dist/clients/conversation/frames.js +0 -52
  465. package/dist/clients/conversation/region.d.ts +0 -51
  466. package/dist/clients/conversation/region.js +0 -265
  467. package/dist/clients/inbox/__tests__/inbox-controller.test.js +0 -247
  468. package/dist/clients/inbox/__tests__/mount-panel.test.js +0 -776
  469. package/dist/clients/inbox/review/__tests__/remap.test.js +0 -196
  470. package/dist/clients/inbox/review/columns.d.ts +0 -22
  471. package/dist/clients/inbox/review/columns.js +0 -40
  472. package/dist/clients/inbox/review/composition.d.ts +0 -60
  473. package/dist/clients/inbox/review/composition.js +0 -278
  474. package/dist/clients/inbox/review/document-region.d.ts +0 -120
  475. package/dist/clients/surfaces/region.d.ts +0 -16
  476. package/dist/clients/web/__tests__/push-engine.test.js +0 -158
  477. package/dist/clients/web/__tests__/push-registry.test.d.ts +0 -1
  478. package/dist/clients/web/__tests__/push-registry.test.js +0 -177
  479. package/dist/clients/web/dev-server.d.ts +0 -7
  480. package/dist/clients/web/dev-server.js +0 -46
  481. package/dist/clients/web/events.d.ts +0 -19
  482. package/dist/clients/web/events.js +0 -163
  483. package/dist/clients/web/push-engine.d.ts +0 -44
  484. package/dist/clients/web/push-engine.js +0 -125
  485. package/dist/clients/web/push-registry.d.ts +0 -85
  486. package/dist/clients/web/push-registry.js +0 -373
  487. package/dist/clients/web/server.d.ts +0 -19
  488. package/dist/clients/web/server.js +0 -661
  489. package/dist/clients/web/web-client/shared/protocol.d.ts +0 -214
  490. package/dist/clients/web/web-client/shared/protocol.js +0 -8
  491. package/dist/clients/web/web-cmd.d.ts +0 -2
  492. package/dist/clients/web/web-cmd.js +0 -141
  493. package/dist/commands/__tests__/api-canvas-source.test.d.ts +0 -1
  494. package/dist/commands/__tests__/api-canvas-source.test.js +0 -37
  495. package/dist/commands/__tests__/canvas-config.test.d.ts +0 -1
  496. package/dist/commands/__tests__/canvas-config.test.js +0 -107
  497. package/dist/commands/__tests__/node-no-kickoff-flag.test.d.ts +0 -1
  498. package/dist/commands/__tests__/node-no-kickoff-flag.test.js +0 -73
  499. package/dist/commands/__tests__/node-worktree-flag.test.d.ts +0 -1
  500. package/dist/commands/__tests__/node-worktree-flag.test.js +0 -49
  501. package/dist/commands/__tests__/search-contents.test.d.ts +0 -1
  502. package/dist/commands/__tests__/search-contents.test.js +0 -52
  503. package/dist/commands/memory/__tests__/lint.test.d.ts +0 -1
  504. package/dist/commands/memory/__tests__/lint.test.js +0 -15
  505. package/dist/commands/sys/__tests__/config-broker-thresholds.test.d.ts +0 -1
  506. package/dist/commands/sys/__tests__/config-broker-thresholds.test.js +0 -95
  507. package/dist/commands/sys/__tests__/config-keybindings.test.d.ts +0 -1
  508. package/dist/commands/sys/__tests__/config-keybindings.test.js +0 -55
  509. package/dist/commands/sys/__tests__/helpers/setup-runtime.d.ts +0 -5
  510. package/dist/commands/sys/__tests__/helpers/setup-runtime.js +0 -19
  511. package/dist/commands/sys/__tests__/setup-front-door.test.d.ts +0 -1
  512. package/dist/commands/sys/__tests__/setup-front-door.test.js +0 -102
  513. package/dist/commands/sys/__tests__/setup-models.test.d.ts +0 -1
  514. package/dist/commands/sys/__tests__/setup-models.test.js +0 -205
  515. package/dist/commands/sys/__tests__/setup-providers-viewport.test.d.ts +0 -1
  516. package/dist/commands/sys/__tests__/setup-providers-viewport.test.js +0 -155
  517. package/dist/commands/sys/__tests__/sync-deps.test.d.ts +0 -1
  518. package/dist/commands/sys/__tests__/sync-deps.test.js +0 -179
  519. package/dist/commands/sys/__tests__/sync-import.test.d.ts +0 -1
  520. package/dist/commands/sys/__tests__/sync-import.test.js +0 -315
  521. package/dist/core/__tests__/base-worker-prompt.test.d.ts +0 -1
  522. package/dist/core/__tests__/base-worker-prompt.test.js +0 -57
  523. package/dist/core/__tests__/command-plugins-surfaces.test.d.ts +0 -1
  524. package/dist/core/__tests__/command-plugins-surfaces.test.js +0 -359
  525. package/dist/core/__tests__/connection-reconnect-resume.test.d.ts +0 -1
  526. package/dist/core/__tests__/connection-reconnect-resume.test.js +0 -36
  527. package/dist/core/__tests__/finalization-migration.test.d.ts +0 -1
  528. package/dist/core/__tests__/finalization-migration.test.js +0 -38
  529. package/dist/core/__tests__/fixtures/extra-extension.d.ts +0 -1
  530. package/dist/core/__tests__/fixtures/extra-extension.js +0 -3
  531. package/dist/core/__tests__/fixtures/spawn-proof-target.test.d.ts +0 -1
  532. package/dist/core/__tests__/fixtures/spawn-proof-target.test.js +0 -15
  533. package/dist/core/__tests__/fork.test.d.ts +0 -1
  534. package/dist/core/__tests__/fork.test.js +0 -108
  535. package/dist/core/__tests__/full/broker-attach-limits.test.d.ts +0 -1
  536. package/dist/core/__tests__/full/broker-attach-limits.test.js +0 -147
  537. package/dist/core/__tests__/full/broker-deliver-ack.test.d.ts +0 -1
  538. package/dist/core/__tests__/full/broker-deliver-ack.test.js +0 -73
  539. package/dist/core/__tests__/full/broker-dialogs.test.d.ts +0 -1
  540. package/dist/core/__tests__/full/broker-dialogs.test.js +0 -180
  541. package/dist/core/__tests__/full/broker-display-snapshot.test.d.ts +0 -1
  542. package/dist/core/__tests__/full/broker-display-snapshot.test.js +0 -75
  543. package/dist/core/__tests__/full/broker-dormant-wake.test.d.ts +0 -1
  544. package/dist/core/__tests__/full/broker-dormant-wake.test.js +0 -130
  545. package/dist/core/__tests__/full/broker-model-changed-broadcast.test.d.ts +0 -1
  546. package/dist/core/__tests__/full/broker-model-changed-broadcast.test.js +0 -57
  547. package/dist/core/__tests__/full/broker-navigate-tree-rewelcome.test.d.ts +0 -1
  548. package/dist/core/__tests__/full/broker-navigate-tree-rewelcome.test.js +0 -80
  549. package/dist/core/__tests__/full/cascade-close.test.d.ts +0 -1
  550. package/dist/core/__tests__/full/cascade-close.test.js +0 -240
  551. package/dist/core/__tests__/full/detach-focus.test.d.ts +0 -1
  552. package/dist/core/__tests__/full/detach-focus.test.js +0 -126
  553. package/dist/core/__tests__/full/idle-release-settlement.test.d.ts +0 -1
  554. package/dist/core/__tests__/full/issue-146-yield-spawn-focus.test.d.ts +0 -1
  555. package/dist/core/__tests__/full/issue-146-yield-spawn-focus.test.js +0 -107
  556. package/dist/core/__tests__/full/park-clock-release.test.d.ts +0 -1
  557. package/dist/core/__tests__/full/park-clock-release.test.js +0 -56
  558. package/dist/core/__tests__/full/yield-message-transaction.test.d.ts +0 -1
  559. package/dist/core/__tests__/memory-index-render.test.d.ts +0 -1
  560. package/dist/core/__tests__/memory-index-render.test.js +0 -60
  561. package/dist/core/__tests__/model-routes.test.d.ts +0 -1
  562. package/dist/core/__tests__/model-routes.test.js +0 -41
  563. package/dist/core/__tests__/on-read-identity.test.d.ts +0 -1
  564. package/dist/core/__tests__/on-read-identity.test.js +0 -61
  565. package/dist/core/__tests__/phase4-review-migration.test.d.ts +0 -1
  566. package/dist/core/__tests__/phase4-review-migration.test.js +0 -100
  567. package/dist/core/__tests__/prune-to-limit.test.d.ts +0 -1
  568. package/dist/core/__tests__/prune-to-limit.test.js +0 -126
  569. package/dist/core/__tests__/reset.test.d.ts +0 -1
  570. package/dist/core/__tests__/reset.test.js +0 -69
  571. package/dist/core/__tests__/spawn-no-kickoff.test.d.ts +0 -1
  572. package/dist/core/__tests__/spawn-no-kickoff.test.js +0 -115
  573. package/dist/core/__tests__/steer-note.test.d.ts +0 -1
  574. package/dist/core/__tests__/steer-note.test.js +0 -41
  575. package/dist/core/__tests__/subcommand-tier.test.d.ts +0 -1
  576. package/dist/core/__tests__/subcommand-tier.test.js +0 -99
  577. package/dist/core/__tests__/subscription-delivery.test.js +0 -211
  578. package/dist/core/__tests__/test-runner-cheap-sharding.test.d.ts +0 -1
  579. package/dist/core/__tests__/test-runner-cheap-sharding.test.js +0 -97
  580. package/dist/core/__tests__/tmux-surface.test.js +0 -484
  581. package/dist/core/__tests__/wake-bearings.test.d.ts +0 -1
  582. package/dist/core/__tests__/wake-bearings.test.js +0 -102
  583. package/dist/core/canvas/__tests__/remote-event-stream.test.d.ts +0 -1
  584. package/dist/core/canvas/__tests__/remote-event-stream.test.js +0 -146
  585. package/dist/core/canvas/__tests__/source-resolve.test.d.ts +0 -1
  586. package/dist/core/canvas/__tests__/source-resolve.test.js +0 -77
  587. package/dist/core/canvas/browse/__tests__/model.test.d.ts +0 -1
  588. package/dist/core/canvas/browse/__tests__/model.test.js +0 -290
  589. package/dist/core/canvas/browse/__tests__/rebuild-coalescer.test.d.ts +0 -1
  590. package/dist/core/canvas/browse/__tests__/rebuild-coalescer.test.js +0 -134
  591. package/dist/core/canvas/browse/__tests__/render.test.d.ts +0 -1
  592. package/dist/core/canvas/browse/__tests__/render.test.js +0 -111
  593. package/dist/core/canvas/browse/rebuild-coalescer.d.ts +0 -7
  594. package/dist/core/canvas/browse/rebuild-coalescer.js +0 -61
  595. package/dist/core/canvas/remote-event-stream.d.ts +0 -24
  596. package/dist/core/canvas/remote-event-stream.js +0 -94
  597. package/dist/core/feed/__tests__/coalesce-human.test.d.ts +0 -1
  598. package/dist/core/feed/__tests__/coalesce-human.test.js +0 -36
  599. package/dist/core/feed/__tests__/empty-node-guard.test.d.ts +0 -1
  600. package/dist/core/feed/__tests__/empty-node-guard.test.js +0 -45
  601. package/dist/core/host-exports/__tests__/export-prunes-boot-skill.test.d.ts +0 -1
  602. package/dist/core/host-exports/__tests__/export-prunes-boot-skill.test.js +0 -84
  603. package/dist/core/human/__tests__/visible-paths.test.d.ts +0 -1
  604. package/dist/core/human/__tests__/visible-paths.test.js +0 -28
  605. package/dist/core/keybindings/__tests__/inbox-affordance.test.d.ts +0 -1
  606. package/dist/core/keybindings/__tests__/inbox-affordance.test.js +0 -38
  607. package/dist/core/review/__tests__/project.test.d.ts +0 -1
  608. package/dist/core/review/__tests__/project.test.js +0 -71
  609. package/dist/core/runtime/__tests__/command-expansion.test.d.ts +0 -1
  610. package/dist/core/runtime/__tests__/command-expansion.test.js +0 -25
  611. package/dist/core/runtime/__tests__/session-visibility.test.d.ts +0 -1
  612. package/dist/core/runtime/__tests__/session-visibility.test.js +0 -119
  613. package/dist/core/runtime/__tests__/spawn-at-given-id.test.d.ts +0 -1
  614. package/dist/core/runtime/__tests__/spawn-at-given-id.test.js +0 -34
  615. package/dist/core/runtime/tmux.js +0 -1314
  616. package/dist/daemon/api/__tests__/full/push-parity.test.d.ts +0 -1
  617. package/dist/daemon/api/__tests__/full/worktree-parity.test.d.ts +0 -1
  618. package/dist/daemon/api/__tests__/full/worktree-parity.test.js +0 -160
  619. package/dist/daemon/api/__tests__/inbox.test.d.ts +0 -1
  620. package/dist/daemon/api/__tests__/inbox.test.js +0 -489
  621. package/dist/web-client/apple-touch-icon.png +0 -0
  622. package/dist/web-client/assets/fragment-mono-latin-400-normal-BYwT3kSJ.woff +0 -0
  623. package/dist/web-client/assets/fragment-mono-latin-400-normal-yxdJ5AmL.woff2 +0 -0
  624. package/dist/web-client/assets/fragment-mono-latin-ext-400-normal-BbKYyvR9.woff2 +0 -0
  625. package/dist/web-client/assets/fragment-mono-latin-ext-400-normal-CT4YFKeK.woff +0 -0
  626. package/dist/web-client/assets/index-CCPaLeW7.css +0 -2
  627. package/dist/web-client/assets/index-kyOWsnZK.js +0 -86
  628. package/dist/web-client/assets/martian-mono-cyrillic-wght-normal-B84CD5C_.woff2 +0 -0
  629. package/dist/web-client/assets/martian-mono-latin-ext-wght-normal-DlL6xMw5.woff2 +0 -0
  630. package/dist/web-client/assets/martian-mono-latin-wght-normal-5W32yIyr.woff2 +0 -0
  631. package/dist/web-client/assets/workbox-window.prod.es5-Bd17z0YL.js +0 -2
  632. package/dist/web-client/icon-192.png +0 -0
  633. package/dist/web-client/icon-512.png +0 -0
  634. package/dist/web-client/icon-maskable-512.png +0 -0
  635. package/dist/web-client/index.html +0 -17
  636. package/dist/web-client/manifest.webmanifest +0 -1
  637. package/dist/web-client/sw.js +0 -1
  638. /package/dist/api/__tests__/{client.test.d.ts → serial/client.test.d.ts} +0 -0
  639. /package/dist/clients/inbox/__tests__/{inbox-controller.test.d.ts → serial/inbox-controller.test.d.ts} +0 -0
  640. /package/dist/clients/inbox/__tests__/{mount-panel.test.d.ts → serial/mount-panel.test.d.ts} +0 -0
  641. /package/dist/clients/{attach/__tests__/attach-chrome-remote.test.d.ts → inbox/review/__tests__/teardown-notice.test.d.ts} +0 -0
  642. /package/dist/{clients/attach/__tests__/attach-keybindings.test.d.ts → core/__tests__/broker-extension-canvas-db-boundary.test.d.ts} +0 -0
  643. /package/dist/{clients/attach/__tests__/crtr-output-coverage.test.d.ts → core/__tests__/seam/broker-attach-multiclient.test.d.ts} +0 -0
  644. /package/dist/core/__tests__/{full → seam}/broker-attach-stream.test.d.ts +0 -0
  645. /package/dist/core/__tests__/{full → seam}/broker-crash-teardown.test.d.ts +0 -0
  646. /package/dist/core/__tests__/{full → seam}/broker-pane-resolution.test.d.ts +0 -0
  647. /package/dist/core/__tests__/{full → seam}/broker-provider-retry.test.d.ts +0 -0
  648. /package/dist/core/__tests__/{full → seam}/broker-whip-interrupt.test.d.ts +0 -0
  649. /package/dist/{clients/attach/__tests__/editor-frame-title.test.d.ts → core/__tests__/seam/dormancy-release.test.d.ts} +0 -0
  650. /package/dist/{clients/attach/__tests__/input-controller-extension-command.test.d.ts → core/__tests__/seam/yield-refresh-transaction.test.d.ts} +0 -0
  651. /package/dist/core/__tests__/{broker-fork-seam.test.d.ts → serial/broker-fork-seam.test.d.ts} +0 -0
  652. /package/dist/core/__tests__/{broker-sdk-wiring.test.d.ts → serial/broker-sdk-wiring.test.d.ts} +0 -0
  653. /package/dist/core/__tests__/{broker-snapshot-history.test.d.ts → serial/broker-snapshot-history.test.d.ts} +0 -0
  654. /package/dist/core/__tests__/{command-plugins.test.d.ts → serial/command-plugins.test.d.ts} +0 -0
  655. /package/dist/core/__tests__/{deferred-no-wake.test.d.ts → serial/deferred-no-wake.test.d.ts} +0 -0
  656. /package/dist/core/__tests__/{flagship-lifecycle.test.d.ts → serial/flagship-lifecycle.test.d.ts} +0 -0
  657. /package/dist/core/__tests__/{host-teardown-process-group.test.d.ts → serial/host-teardown-process-group.test.d.ts} +0 -0
  658. /package/dist/core/__tests__/{human-deliver-e2e.test.d.ts → serial/human-deliver-e2e.test.d.ts} +0 -0
  659. /package/dist/core/__tests__/{live-mutation.test.d.ts → serial/live-mutation.test.d.ts} +0 -0
  660. /package/dist/core/__tests__/{refresh-stall-recycle.test.d.ts → serial/refresh-stall-recycle.test.d.ts} +0 -0
  661. /package/dist/core/__tests__/{revive.test.d.ts → serial/revive.test.d.ts} +0 -0
  662. /package/dist/core/__tests__/{spawn-root.test.d.ts → serial/spawn-root.test.d.ts} +0 -0
  663. /package/dist/core/__tests__/{subscription-delivery.test.d.ts → serial/subscription-delivery.test.d.ts} +0 -0
  664. /package/dist/core/__tests__/{tmux-surface.test.d.ts → serial/tmux-surface.test.d.ts} +0 -0
  665. /package/dist/core/__tests__/{worktree.test.d.ts → serial/worktree.test.d.ts} +0 -0
  666. /package/dist/{clients/attach/__tests__/markdown-headings.test.d.ts → core/__tests__/watchdog-abort-arms-retry.test.d.ts} +0 -0
  667. /package/dist/{clients/attach/__tests__/session-identity-refresh.test.d.ts → core/broker-client/__tests__/transport-socket.test.d.ts} +0 -0
  668. /package/dist/core/human/__tests__/{inbox-core.test.d.ts → serial/inbox-core.test.d.ts} +0 -0
  669. /package/dist/{clients/inbox/review/__tests__/remap.test.d.ts → daemon/api/__tests__/node-model-validation.test.d.ts} +0 -0
  670. /package/dist/{clients/surfaces/region.js → daemon/api/__tests__/review-terminal-conflict.test.d.ts} +0 -0
  671. /package/dist/daemon/api/__tests__/{full → seam}/api-server.test.d.ts +0 -0
  672. /package/dist/daemon/api/__tests__/{full → seam}/b10-attach-modelauth.test.d.ts +0 -0
  673. /package/dist/{clients/web/__tests__/push-engine.test.d.ts → daemon/api/__tests__/seam/leaf-api-parity.test.d.ts} +0 -0
@@ -0,0 +1,1537 @@
1
+ import { execFile } from 'node:child_process';
2
+ import { existsSync, readFileSync, unlinkSync, writeFileSync } from 'node:fs';
3
+ import { randomUUID } from 'node:crypto';
4
+ import { tmpdir } from 'node:os';
5
+ import { join } from 'node:path';
6
+ import { AUTH_FAULT_RECOVERY_BODY } from '../../../shared/generated-context.js';
7
+ import { emitEvent } from '../../events/emit.js';
8
+ import { operationIdContext } from '../../events/operation-id.js';
9
+ import { buildRefInventory } from '../../memory/inline-ref-inventory.js';
10
+ import { buildGuidance } from '../../memory/inline-ref-guidance.js';
11
+ import { clearSessionCache } from '../../substrate/session-cache.js';
12
+ import { configuredLadderProviders, isProviderPinnedModelToken, nextLadderModel, normalizeModel, } from '../launch.js';
13
+ import { clearFault, readFault } from '../fault.js';
14
+ import { isManagedProvider, readSubscriptionPool, } from '../../subscription-state.js';
15
+ import { SessionListCache } from '../session-list-cache.js';
16
+ import { sessionListCachePath } from '../../canvas/paths.js';
17
+ import { REVIEW_BOUNDARY_CUSTOM_TYPE } from '../session-visibility.js';
18
+ import { isUnknownModel, parseModelSpec } from '../model-selection.js';
19
+ import { buildCommandList, buildGetTreeData, buildListMemoryRefsData, buildListModelsData, buildScopedModelsData, buildSessionsData, buildSettingsData, modelKey, } from './read-ops.js';
20
+ export const REVIEW_BOUNDARY_NAVIGATION_CODE = 'review_boundary_navigation';
21
+ export class ReviewBoundaryNavigationError extends Error {
22
+ targetId;
23
+ code = REVIEW_BOUNDARY_NAVIGATION_CODE;
24
+ constructor(targetId) {
25
+ super(`review companion cannot navigate before the visible transcript boundary (target: ${targetId})`);
26
+ this.targetId = targetId;
27
+ this.name = 'ReviewBoundaryNavigationError';
28
+ }
29
+ }
30
+ /**
31
+ * Route a writable client's `prompt`/`follow_up` frame against the LIVE session
32
+ * state. The client picks its frame type off a possibly-STALE `isStreaming`
33
+ * snapshot, so the broker is authoritative and the client's choice is a HINT:
34
+ *
35
+ * - m-B (streaming-safe prompt): a `prompt` arriving mid-stream needs
36
+ * PromptOptions.streamingBehavior ("Required if streaming"; prompt() throws
37
+ * without it). "steer" mirrors pi interactive-mode's own
38
+ * Enter-while-streaming submit (`prompt(text, { streamingBehavior: "steer" })`).
39
+ * - m-C (idle-safe follow_up): pi's followUp() only enqueues into a RUNNING
40
+ * agent loop — on an idle session nothing ever drains the queue and the text
41
+ * is silently lost. An idle `follow_up` therefore runs as a prompt now.
42
+ *
43
+ * driveEngine calls this synchronously with `session.isStreaming`, so the
44
+ * routed state cannot go stale between the check and the engine call.
45
+ * C1: images ride as a field on PromptOptions (pi: `prompt(text, { images })`).
46
+ */
47
+ export function resolveEngineRoute(frame, isStreaming) {
48
+ if (frame.type === 'follow_up' && isStreaming)
49
+ return { call: 'followUp' };
50
+ const options = {};
51
+ if (frame.images !== undefined)
52
+ options.images = frame.images;
53
+ if (isStreaming)
54
+ options.streamingBehavior = 'steer';
55
+ return { call: 'prompt', options };
56
+ }
57
+ /**
58
+ * True iff `text` would be consumed by pi's OWN leading-name preflight before
59
+ * `session.prompt/steer/followUp` ever sees it as prose — an extension command
60
+ * (`session.extensionRunner.getCommand`), a `/skill:name` skill, or a `/name`
61
+ * prompt template (mirrors `_tryExecuteExtensionCommand` / `_expandSkillCommand`
62
+ * / `expandPromptTemplate` in the pinned SDK verbatim, including the exact
63
+ * name-parsing substrings). Inline memory-reference guidance must NEVER fire
64
+ * for these — the leading token routes to the engine's own command surface,
65
+ * not prose referencing a memory doc. Each lookup is independently wrapped so
66
+ * an engine missing one accessor (the fake, or a degraded session) degrades
67
+ * that check to "not a match" rather than aborting the whole classification —
68
+ * matching `buildCommandList`'s existing "no live extensionRunner" tolerance.
69
+ */
70
+ export function isLeadingEngineCommand(text, session) {
71
+ if (text.startsWith('/')) {
72
+ const spaceIndex = text.indexOf(' ');
73
+ const commandName = spaceIndex === -1 ? text.slice(1) : text.slice(1, spaceIndex);
74
+ try {
75
+ if (session.extensionRunner?.getCommand(commandName))
76
+ return true;
77
+ }
78
+ catch {
79
+ /* engine without a live extensionRunner (e.g. the fake) — not a match */
80
+ }
81
+ }
82
+ if (text.startsWith('/skill:')) {
83
+ const spaceIndex = text.indexOf(' ');
84
+ const skillName = spaceIndex === -1 ? text.slice(7) : text.slice(7, spaceIndex);
85
+ try {
86
+ if (session.resourceLoader?.getSkills().skills.some((s) => s.name === skillName))
87
+ return true;
88
+ }
89
+ catch {
90
+ /* engine without a live resourceLoader — not a match */
91
+ }
92
+ }
93
+ if (text.startsWith('/')) {
94
+ const match = text.match(/^\/([^\s]+)(?:\s+([\s\S]*))?$/);
95
+ const templateName = match?.[1];
96
+ if (templateName !== undefined) {
97
+ try {
98
+ if (session.promptTemplates?.some((t) => t.name === templateName))
99
+ return true;
100
+ }
101
+ catch {
102
+ /* engine without a live promptTemplates getter — not a match */
103
+ }
104
+ }
105
+ }
106
+ return false;
107
+ }
108
+ /** A `steer` frame normally maps directly to pi's steer queue. Engine commands,
109
+ * skills, and templates must instead pass through prompt()'s leading-command
110
+ * preflight; with streamingBehavior:'steer', ordinary expanded text still lands
111
+ * in the same queue. */
112
+ export function resolveSteerCall(text, session) {
113
+ return isLeadingEngineCommand(text, session) ? 'prompt' : 'steer';
114
+ }
115
+ /**
116
+ * Enumerate every leading-token STRING this session's engine would
117
+ * independently consume as a leading command/skill/template dispatch BEFORE
118
+ * inline memory-reference guidance ever applies — the read-op sibling of
119
+ * `isLeadingEngineCommand`'s point-lookup, gathered as a full enumeration for
120
+ * viewer-side suppression instead of a per-submission membership check. Same
121
+ * three sources, same shape of the leading match `isLeadingEngineCommand`
122
+ * checks (bare extension-command invocation names and prompt-template names,
123
+ * `skill:<name>` for skills), so `/${token}` round-trips through
124
+ * `isLeadingEngineCommand(text, session)` for every emitted token (locked by
125
+ * a test). Kept STRICTLY separate from `buildCommandList` (command
126
+ * completion) and the ref inventory (`RefMeta` rows) — this is neither.
127
+ */
128
+ export function engineLeadingCommandTokens(session) {
129
+ const out = [];
130
+ try {
131
+ for (const c of session.extensionRunner?.getRegisteredCommands() ?? [])
132
+ out.push(c.invocationName);
133
+ }
134
+ catch {
135
+ /* engine without a live extensionRunner (e.g. the fake) — none */
136
+ }
137
+ try {
138
+ for (const s of session.resourceLoader?.getSkills().skills ?? [])
139
+ out.push(`skill:${s.name}`);
140
+ }
141
+ catch {
142
+ /* engine without a live resourceLoader — none */
143
+ }
144
+ try {
145
+ for (const t of session.promptTemplates ?? [])
146
+ out.push(t.name);
147
+ }
148
+ catch {
149
+ /* engine without a live promptTemplates getter — none */
150
+ }
151
+ return out;
152
+ }
153
+ /**
154
+ * Choose how the hidden `crtr-memory-refs` guidance custom message rides
155
+ * alongside a human frame's own engine call (design Flow B). Pure over the
156
+ * frame's routing outcome — never reads live session state itself, so a
157
+ * caller can decide the delivery mode from the exact same `route`
158
+ * `resolveEngineRoute` already computed for the human call.
159
+ *
160
+ * - `steer` frame -> 'steer' (always; no route call exists).
161
+ * - a TRUE mid-stream follow_up (`routeCall === 'followUp'`) -> 'followUp'.
162
+ * - every other `prompt`/`follow_up` routed as `routeCall === 'prompt'`:
163
+ * idle -> 'nextTurn' (the ONLY case guidance precedes the human call —
164
+ * pi's `prompt()` reads+clears its pending-nextTurn queue synchronously
165
+ * into the SAME turn as the new user message); streaming (m-B
166
+ * steer-while-streaming) -> 'steer', never 'nextTurn' (that would attach
167
+ * to a later, unrelated turn).
168
+ */
169
+ export function chooseGuidanceDeliveryMode(frameType, routeCall, isStreaming) {
170
+ if (frameType === 'steer')
171
+ return 'steer';
172
+ if (routeCall === 'followUp')
173
+ return 'followUp';
174
+ return isStreaming ? 'steer' : 'nextTurn';
175
+ }
176
+ /**
177
+ * Deliver a `nextTurn` guidance custom message ATOMICALLY around the idle-
178
+ * prompt call it's meant to ride alongside. Pi's `_pendingNextTurnMessages`
179
+ * queue (the only public seam for `deliverAs:'nextTurn'`) is drained ONLY deep
180
+ * inside `session.prompt()`, well after several await points — extension-
181
+ * command interception, the `input` extension transform, a streaming reroute,
182
+ * model/auth validation, pre-turn compaction — that can return or throw BEFORE
183
+ * the drain ever runs. `sendCustomMessage`'s nextTurn branch pushes
184
+ * synchronously and unconditionally, so any of those early exits strands the
185
+ * pushed guidance in the queue to attach itself to whichever LATER, unrelated
186
+ * prompt happens to reach the drain first.
187
+ *
188
+ * There is no public SDK seam to observe or cancel a pending nextTurn message
189
+ * (`_pendingNextTurnMessages` is TS-`private` only — a plain runtime field, not
190
+ * `#private`), so this reaches into that one field and removes EXACTLY the
191
+ * entry this call pushed, identified by object identity via a locally-
192
+ * generated marker riding the message's own `details` field (never surfaced to
193
+ * the model, and irrelevant if the message WAS consumed normally — `details`
194
+ * is just carried through like any other field once folded into a real turn).
195
+ * Runs UNCONDITIONALLY after the human call settles, success or failure/early-
196
+ * return alike — a "successful" `prompt()` promise can just as easily be an
197
+ * early return that skipped the drain (an intercepted extension command, an
198
+ * `input` transform, a streaming reroute) as a genuine consumed turn, so only
199
+ * checking `.catch` is not enough.
200
+ */
201
+ export async function promptWithNextTurnGuidance(session, guidance, runPrompt) {
202
+ // Must survive Pi's structured-clone step when the queued custom message is
203
+ // folded into the turn. A Symbol reaches that step in `details` and aborts
204
+ // the real prompt with "could not be cloned" before the provider runs.
205
+ const marker = `crtr-memory-refs-pending:${randomUUID()}`;
206
+ let queued = false;
207
+ try {
208
+ await session.sendCustomMessage({ customType: 'crtr-memory-refs', content: guidance, display: false, details: marker }, { deliverAs: 'nextTurn' });
209
+ queued = true;
210
+ }
211
+ catch (err) {
212
+ emitEvent({
213
+ level: 'warn',
214
+ event: 'broker.memory_refs.guidance_send_failed',
215
+ error: err,
216
+ });
217
+ }
218
+ try {
219
+ await runPrompt();
220
+ }
221
+ finally {
222
+ if (queued)
223
+ clearOwnPendingNextTurnGuidance(session, marker);
224
+ }
225
+ }
226
+ /** Splice out exactly the pending-nextTurn entry `marker` identifies (object-
227
+ * identity match on `details`), if it is still present — a no-op (and safe)
228
+ * if `session.prompt()` already drained it normally, or if the session/field
229
+ * shape doesn't match (defensive against an engine without the field, e.g. a
230
+ * future SDK bump or a structurally-different test double). */
231
+ function clearOwnPendingNextTurnGuidance(session, marker) {
232
+ const pending = session._pendingNextTurnMessages;
233
+ if (!Array.isArray(pending))
234
+ return;
235
+ const idx = pending.findIndex((m) => m?.details === marker);
236
+ if (idx !== -1)
237
+ pending.splice(idx, 1);
238
+ }
239
+ /** True iff a parsed JSONL branch-entry line is a hidden (`display:false`)
240
+ * custom message — the general display boundary crossed if left in an
241
+ * exported branch (not just `crtr-memory-refs` guidance; any hidden custom
242
+ * content). Covers both persisted shapes a branch entry can take: a
243
+ * `type:'custom_message'` entry (display at the entry's own top level) and a
244
+ * `type:'message'` entry wrapping a `role:'custom'` message (display nested
245
+ * under `.message`). */
246
+ export function isHiddenJsonlBranchEntry(entry) {
247
+ if (entry === null || typeof entry !== 'object')
248
+ return false;
249
+ const e = entry;
250
+ if (e.type === 'custom_message' && e.display === false)
251
+ return true;
252
+ if (e.type === 'message' && e.message?.role === 'custom' && e.message.display === false)
253
+ return true;
254
+ return false;
255
+ }
256
+ /** Rewrite an already-exported JSONL file in place, dropping every hidden
257
+ * (`display:false`) branch entry while re-chaining `parentId` so the
258
+ * remaining entries stay a single valid linear branch — applying the general
259
+ * display:false boundary to `session.exportToJsonl`'s own output, which
260
+ * serializes every entry verbatim regardless of display. The header line
261
+ * (line 1) is copied through untouched. */
262
+ export function stripHiddenEntriesFromJsonlExport(filePath) {
263
+ const raw = readFileSync(filePath, 'utf8');
264
+ const lines = raw.split('\n').filter((l) => l !== '');
265
+ if (lines.length === 0)
266
+ return;
267
+ const [headerLine, ...entryLines] = lines;
268
+ let prevId = null;
269
+ const kept = [];
270
+ for (const line of entryLines) {
271
+ const entry = JSON.parse(line);
272
+ if (isHiddenJsonlBranchEntry(entry))
273
+ continue;
274
+ kept.push(JSON.stringify({ ...entry, parentId: prevId }));
275
+ if (typeof entry.id === 'string')
276
+ prevId = entry.id;
277
+ }
278
+ writeFileSync(filePath, `${[headerLine, ...kept].join('\n')}\n`);
279
+ }
280
+ /** Owns frame-local input state while dispatching through explicit broker collaborators. */
281
+ export function createFrameDispatchContext(deps) {
282
+ const { nodeId, cfg, registry, toolGroups, projection, rebind, reviewCompanion, pendingDialogs, sendWelcome, replayExtraPendingDialogsTo, reWelcomeAll, disposeAndExit, persistModelChoice, broadcastModelChanged, registryOf, formatModelSpec, formatExactModelSpec, resolveLaunchModel, } = deps;
283
+ const currentSession = () => rebind.session();
284
+ const currentServices = () => rebind.services();
285
+ let kickoffPending;
286
+ let kickoffInFlight = false;
287
+ let reloadAuthInFlight = null;
288
+ let authFaultRedriveInFlight = false;
289
+ const turnAcceptedWaiters = new Set();
290
+ const notifyTurnAccepted = () => {
291
+ const waiters = [...turnAcceptedWaiters];
292
+ turnAcceptedWaiters.clear();
293
+ for (const waiter of waiters)
294
+ waiter();
295
+ };
296
+ const sessionHasNoModel = () => currentSession().model === undefined ||
297
+ isUnknownModel(currentSession().model);
298
+ const drivePendingKickoff = () => {
299
+ if (kickoffPending === undefined || kickoffInFlight || sessionHasNoModel())
300
+ return;
301
+ const prompt = kickoffPending;
302
+ kickoffPending = undefined;
303
+ kickoffInFlight = true;
304
+ trackPromptDispatch(operationIdContext.fresh(() => currentSession()
305
+ .prompt(prompt)
306
+ .catch((err) => {
307
+ emitEvent({
308
+ level: 'error',
309
+ event: 'broker.prompt.first_failed',
310
+ error: err,
311
+ });
312
+ })
313
+ .finally(() => {
314
+ kickoffInFlight = false;
315
+ })));
316
+ };
317
+ // Drive the engine on behalf of a writable client.
318
+ // -------------------------------------------------------------------------
319
+ // ---------------------------------------------------------------------------
320
+ // Inline memory-reference inventory (design: inline memory references) — a
321
+ // single broker-level memo shared by BOTH the `list_memory_refs` read-op
322
+ // reply and per-submission guidance resolution below, so a burst of reads +
323
+ // submissions between reloads never re-walks the corpus per call. Cleared
324
+ // (work item 3) only on a SUCCESSFUL `currentSession().reload()` — a failed reload
325
+ // leaves the prior memo intact, since nothing about the corpus is known to
326
+ // have changed.
327
+ // ---------------------------------------------------------------------------
328
+ let memoizedRefInventory = null;
329
+ const getRefInventory = () => {
330
+ if (memoizedRefInventory === null)
331
+ memoizedRefInventory = buildRefInventory();
332
+ return memoizedRefInventory;
333
+ };
334
+ /** Resolve the hidden guidance text for a human frame's verbatim text, or
335
+ * `null` when no guidance should be sent (a leading engine command/skill/
336
+ * template, no resolvable refs, or a degraded inventory/guidance build —
337
+ * work item 6: any failure here degrades to "no guidance", logged once as a
338
+ * structured warning, and NEVER blocks/alters the human delivery below). */
339
+ const resolveFrameGuidance = (text) => {
340
+ try {
341
+ if (isLeadingEngineCommand(text, currentSession()))
342
+ return null;
343
+ return buildGuidance(text, getRefInventory().names);
344
+ }
345
+ catch (err) {
346
+ emitEvent({
347
+ level: 'warn',
348
+ event: 'broker.memory_refs.guidance_resolve_failed',
349
+ error: err,
350
+ });
351
+ return null;
352
+ }
353
+ };
354
+ // Prompt dispatches whose promise has not yet settled. `currentSession().prompt()`
355
+ // does async setup (input transforms, model/auth validation, compaction)
356
+ // BEFORE `_isAgentRunActive` flips, so for a short window after a routed
357
+ // deliver the engine reads idle and `currentSession().abort()` is a silent no-op — an
358
+ // id-bearing abort racing that window would miss the turn entirely (verified
359
+ // live). The counter lets it distinguish "genuinely idle" from "turn still
360
+ // igniting" and wait for the run to become abortable.
361
+ let pendingPromptDispatches = 0;
362
+ const trackPromptDispatch = (p) => {
363
+ pendingPromptDispatches++;
364
+ const tracked = Promise.resolve(p);
365
+ void tracked.finally(() => {
366
+ pendingPromptDispatches--;
367
+ });
368
+ return tracked;
369
+ };
370
+ /**
371
+ * Drive the engine for one client frame. For a prompt/follow_up frame this
372
+ * returns a promise that settles with the engine call's OUTCOME — `undefined`
373
+ * when the call succeeded, the rejection value when it did not — so a caller
374
+ * that must report success (the `deliver` ack) can read it instead of assuming
375
+ * the dispatch worked. Every other frame type keeps the fire-and-forget shape
376
+ * and returns `undefined`.
377
+ */
378
+ const driveEngine = (client, frame) => {
379
+ // The engine call's failure, captured for the returned outcome. relayError
380
+ // still relays the error frame; recording it is what lets the caller ack
381
+ // from the outcome rather than from the fact that a call was made at all.
382
+ let failure;
383
+ const relayError = (err) => {
384
+ failure = err;
385
+ registry.sendFrame(client, {
386
+ type: 'error',
387
+ code: 'engine_error',
388
+ message: String(err),
389
+ });
390
+ };
391
+ // `prompt { interrupt: true }` is one atomic viewer intention: end whatever
392
+ // is producing now, then start this as a new turn. A normal mid-stream
393
+ // prompt deliberately steers into pi's queue; the whip must not.
394
+ if (frame.type === 'prompt' && frame.interrupt === true) {
395
+ return (async () => {
396
+ if (currentSession().isBashRunning) {
397
+ currentSession().abortBash();
398
+ }
399
+ else {
400
+ // A prompt can be in pi's async setup before isStreaming flips. Match
401
+ // the correlated interrupt's guard so a whip cannot silently miss it.
402
+ if (!currentSession().isStreaming && pendingPromptDispatches > 0) {
403
+ const deadline = Date.now() + 5_000;
404
+ while (Date.now() < deadline &&
405
+ !currentSession().isStreaming &&
406
+ pendingPromptDispatches > 0) {
407
+ await new Promise((resolve) => setTimeout(resolve, 25));
408
+ }
409
+ }
410
+ if (currentSession().isStreaming)
411
+ await currentSession().abort();
412
+ }
413
+ return driveEngine(client, {
414
+ type: 'prompt',
415
+ text: frame.text,
416
+ ...(frame.images !== undefined ? { images: frame.images } : {}),
417
+ });
418
+ })().catch((err) => {
419
+ relayError(err);
420
+ return failure;
421
+ });
422
+ }
423
+ // Hidden `crtr-memory-refs` guidance rides ALONGSIDE the human frame's own
424
+ // engine call, never rewriting `frame.text` — see resolveFrameGuidance /
425
+ // chooseGuidanceDeliveryMode above. A failed SEND (as opposed to a failed
426
+ // resolve, handled above) is likewise logged-and-swallowed: it must never
427
+ // reach `relayError`, which is reserved for the human call's own outcome.
428
+ const sendGuidance = (guidance, deliverAs) => currentSession()
429
+ .sendCustomMessage({ customType: 'crtr-memory-refs', content: guidance, display: false }, { deliverAs })
430
+ .catch((err) => {
431
+ emitEvent({
432
+ level: 'warn',
433
+ event: 'broker.memory_refs.guidance_send_failed',
434
+ error: err,
435
+ });
436
+ });
437
+ switch (frame.type) {
438
+ case 'prompt':
439
+ case 'follow_up': {
440
+ // Settle on the engine call's outcome: `failure` is set by relayError
441
+ // before the caught promise resolves, so reading it after the chain is
442
+ // the call's verdict.
443
+ const settle = (p) => p.then(() => failure);
444
+ const dispatch = () => {
445
+ const route = resolveEngineRoute(frame, currentSession().isStreaming);
446
+ const guidance = resolveFrameGuidance(frame.text);
447
+ if (guidance === null) {
448
+ if (route.call === 'prompt') {
449
+ // A routed prompt STARTS a turn — including an idle follow_up
450
+ // escalated by m-C — so it gets a fresh operation id. A true
451
+ // (mid-stream) followUp rides the current turn's operation, like
452
+ // steer.
453
+ return settle(trackPromptDispatch(operationIdContext.fresh(() => currentSession()
454
+ .prompt(frame.text, route.options)
455
+ .catch(relayError))));
456
+ }
457
+ // C1: pi's followUp() takes images as a POSITIONAL 2nd arg (`followUp(text, images?)`).
458
+ return settle(currentSession()
459
+ .followUp(frame.text, frame.images)
460
+ .catch(relayError));
461
+ }
462
+ const deliverAs = chooseGuidanceDeliveryMode(frame.type, route.call, currentSession().isStreaming);
463
+ if (route.call === 'prompt') {
464
+ if (deliverAs === 'nextTurn') {
465
+ // The ONLY ordering where guidance precedes the human call — pi's
466
+ // prompt() reads+clears its pending-nextTurn queue synchronously
467
+ // at call time, injecting it into the SAME turn as the new user
468
+ // message. promptWithNextTurnGuidance queues the guidance and then
469
+ // ATOMICALLY clears its own pending entry if prompt() settles
470
+ // without ever reaching that drain (an early throw/return leaves
471
+ // nothing stranded to leak into a later, unrelated turn).
472
+ return settle(trackPromptDispatch(promptWithNextTurnGuidance(currentSession(), guidance, () => operationIdContext.fresh(() => currentSession().prompt(frame.text, route.options))).catch(relayError)));
473
+ }
474
+ return settle(trackPromptDispatch(operationIdContext.fresh(() => currentSession()
475
+ .prompt(frame.text, route.options)
476
+ .catch(relayError)
477
+ .then(() => sendGuidance(guidance, deliverAs)))));
478
+ }
479
+ // deliverAs is always 'followUp' here per chooseGuidanceDeliveryMode's
480
+ // contract (a followUp routeCall never yields 'nextTurn').
481
+ return settle(currentSession()
482
+ .followUp(frame.text, frame.images)
483
+ .catch(relayError)
484
+ .then(() => sendGuidance(guidance, deliverAs)));
485
+ };
486
+ // /login acknowledges before its asynchronous local refresh completes.
487
+ // An idle turn must not overtake that refresh and launch on the stale
488
+ // route; true mid-stream follow-ups keep their immediate queue behavior.
489
+ const authReload = currentSession().isStreaming
490
+ ? undefined
491
+ : reloadAuthInFlight?.promise;
492
+ if (authReload === undefined)
493
+ return dispatch();
494
+ const waitingGeneration = rebind.installedGeneration();
495
+ const waitingSession = currentSession();
496
+ return authReload.then(() => {
497
+ if (rebind.installedGeneration() !== waitingGeneration ||
498
+ currentSession() !== waitingSession) {
499
+ relayError(new Error('currentSession() changed while prompt waited for authentication reload'));
500
+ return failure;
501
+ }
502
+ return dispatch();
503
+ });
504
+ }
505
+ case 'steer': {
506
+ // A viewer submits ordinary text as `steer` while the agent is running,
507
+ // including a slash command injected by the tmux prefix menu. pi's
508
+ // steer() deliberately rejects registered extension commands because
509
+ // they cannot be queued; prompt() performs the command preflight first
510
+ // and executes it immediately even while streaming. It also expands a
511
+ // matching skill/template before applying the same steer behavior.
512
+ if (resolveSteerCall(frame.text, currentSession()) === 'prompt') {
513
+ void currentSession()
514
+ .prompt(frame.text, {
515
+ images: frame.images,
516
+ streamingBehavior: 'steer',
517
+ })
518
+ .catch(relayError);
519
+ break;
520
+ }
521
+ const guidance = resolveFrameGuidance(frame.text);
522
+ // C1: pi's steer() takes images as a POSITIONAL 2nd arg (`steer(text, images?)`).
523
+ if (guidance === null) {
524
+ void currentSession()
525
+ .steer(frame.text, frame.images)
526
+ .catch(relayError);
527
+ break;
528
+ }
529
+ void currentSession()
530
+ .steer(frame.text, frame.images)
531
+ .catch(relayError)
532
+ .then(() => sendGuidance(guidance, 'steer'));
533
+ break;
534
+ }
535
+ case 'abort': {
536
+ // esc/abort cancels whatever is live: a running `!` command (abortBash)
537
+ // takes precedence over the agent turn, mirroring pi interactive mode.
538
+ if (currentSession().isBashRunning) {
539
+ currentSession().abortBash();
540
+ if (frame.id !== undefined)
541
+ ackTo(client, 'abort', true, 'bash', frame.id);
542
+ break;
543
+ }
544
+ if (frame.id === undefined) {
545
+ // A viewer's Esc — unchanged instant semantics, no ack.
546
+ void currentSession().abort().catch(relayError);
547
+ break;
548
+ }
549
+ // An id-bearing abort (a one-shot daemon interrupt) must close the
550
+ // pre-turn window: a routed deliver whose prompt() is still in async
551
+ // setup reads as idle and abort() would silently miss it. If a prompt
552
+ // dispatch is pending, wait (bounded) for the run to become abortable
553
+ // — or for every dispatch to settle without one (an early return) —
554
+ // then abort and ack with what was actually interrupted.
555
+ void (async () => {
556
+ try {
557
+ if (!currentSession().isStreaming && pendingPromptDispatches > 0) {
558
+ const deadline = Date.now() + 5_000;
559
+ while (Date.now() < deadline &&
560
+ !currentSession().isStreaming &&
561
+ pendingPromptDispatches > 0) {
562
+ await new Promise((r) => setTimeout(r, 25));
563
+ }
564
+ }
565
+ const detail = currentSession().isStreaming ? 'turn' : 'idle';
566
+ await currentSession().abort();
567
+ ackTo(client, 'abort', true, detail, frame.id);
568
+ }
569
+ catch (err) {
570
+ engineErrorTo(client, frame.id)(err);
571
+ }
572
+ })();
573
+ break;
574
+ }
575
+ case 'bash': {
576
+ // `!`/`!!` bash: run via currentSession().executeBash (records a bashExecution
577
+ // context message, starts NO agent turn) and relay its lifecycle to ALL
578
+ // viewers as bash_start/bash_output/bash_end so each renders the live
579
+ // component. Serialized like pi's interactive mode — reject a second `!`
580
+ // while one is already running rather than racing _bashAbortController.
581
+ const command = frame.command;
582
+ if (command.trim() === '')
583
+ break;
584
+ if (currentSession().isBashRunning) {
585
+ registry.sendFrame(client, {
586
+ type: 'error',
587
+ code: 'bash_busy',
588
+ message: 'A bash command is already running',
589
+ });
590
+ break;
591
+ }
592
+ const exclude = frame.excludeFromContext === true;
593
+ // Order before any held assistant message_update, so a `!` run mid-stream
594
+ // never renders ahead of the update it follows.
595
+ projection.flush();
596
+ // The live bash component is a visible transcript boundary, even though
597
+ // its persisted bashExecution message arrives later via the currentSession().
598
+ toolGroups.finishOpenToolGroup();
599
+ registry.broadcast({
600
+ type: 'bash_start',
601
+ command,
602
+ excludeFromContext: exclude,
603
+ });
604
+ void currentSession()
605
+ .executeBash(command, (chunk) => registry.broadcast({ type: 'bash_output', chunk }), {
606
+ excludeFromContext: exclude,
607
+ })
608
+ .then((result) => {
609
+ registry.broadcast({
610
+ type: 'bash_end',
611
+ exitCode: result.exitCode,
612
+ cancelled: result.cancelled,
613
+ truncated: result.truncated,
614
+ fullOutputPath: result.fullOutputPath,
615
+ });
616
+ })
617
+ .catch(relayError);
618
+ break;
619
+ }
620
+ }
621
+ };
622
+ // -------------------------------------------------------------------------
623
+ // Command-op helpers (T3, §2.3). The write guard is hoisted here (it was
624
+ // inlined twice) and reused by all mutating ops; the ack/error replies
625
+ // and a few resolvers keep the per-op cases one-liners.
626
+ // -------------------------------------------------------------------------
627
+ /** Reject a read-only client for a mutating op. The decision reads this
628
+ * client's OWN fixed role — there is no shared writer slot, so any number of
629
+ * writable clients pass concurrently. Returns true when rejected (the caller
630
+ * should `break`). */
631
+ const notWritable = (client, what, id) => {
632
+ if (client.role === 'controller')
633
+ return false;
634
+ registry.sendFrame(client, {
635
+ type: 'error',
636
+ code: 'read_only',
637
+ message: `a read-only client may not ${what}`,
638
+ // M1: echo a correlated request's id so its pending-by-id promise rejects
639
+ // rather than hanging (e.g. a read-only `dequeue`).
640
+ ...(id !== undefined ? { id } : {}),
641
+ });
642
+ return true;
643
+ };
644
+ const ackTo = (client, op, ok = true, detail, id) => registry.sendFrame(client, {
645
+ type: 'ack',
646
+ for: op,
647
+ ok,
648
+ ...(detail !== undefined ? { detail } : {}),
649
+ ...(id !== undefined ? { id } : {}),
650
+ });
651
+ const engineErrorTo = (client, id) => (err) => registry.sendFrame(client, {
652
+ type: 'error',
653
+ code: 'engine_error',
654
+ message: String(err),
655
+ // M1: a correlated read-op / dequeue failure echoes its id so the viewer's
656
+ // pending-by-id promise rejects instead of hanging; uncorrelated callers
657
+ // pass no id (unchanged behavior).
658
+ ...(id !== undefined ? { id } : {}),
659
+ });
660
+ const isPreBoundaryReviewEntry = (entryId) => reviewCompanion &&
661
+ !currentSession()
662
+ .sessionManager.getBranch(entryId)
663
+ .some((entry) => entry.type === 'custom_message' &&
664
+ entry.customType === REVIEW_BOUNDARY_CUSTOM_TYPE);
665
+ const reviewBoundaryRefusalTo = (client, targetId) => {
666
+ const refusal = new ReviewBoundaryNavigationError(targetId);
667
+ registry.sendFrame(client, {
668
+ type: 'error',
669
+ code: refusal.code,
670
+ message: refusal.message,
671
+ });
672
+ };
673
+ /** Resolve a `provider/id` model spec against the LIVE currentServices() registry (which
674
+ * carries any extension-registered providers). Pure: returns undefined on a
675
+ * malformed spec or an unknown model. */
676
+ const findModelSpec = (spec) => {
677
+ const { modelSpec } = parseModelSpec(spec);
678
+ const slash = modelSpec.indexOf('/');
679
+ if (slash <= 0)
680
+ return undefined;
681
+ return registryOf(currentServices()).find(modelSpec.slice(0, slash), modelSpec.slice(slash + 1));
682
+ };
683
+ /** Resolve a model from a free-text query — `/model opus`, `/model fable`. Tries
684
+ * the exact `provider/id` spec first; failing that, searches the registry the
685
+ * way the picker's search box does (case-insensitive substring on the model id
686
+ * and the `provider/id` key) and returns the BEST match, preferring authed/
687
+ * available models. Returns undefined only when nothing matches. */
688
+ const resolveModelQuery = (query) => {
689
+ const exact = findModelSpec(query);
690
+ if (exact)
691
+ return exact;
692
+ const q = query.trim().toLowerCase();
693
+ if (q === '')
694
+ return undefined;
695
+ let all;
696
+ try {
697
+ all = registryOf(currentServices()).getAll();
698
+ }
699
+ catch {
700
+ return undefined;
701
+ }
702
+ let availableKeys;
703
+ try {
704
+ availableKeys = new Set(registryOf(currentServices()).getAvailable().map(modelKey));
705
+ }
706
+ catch {
707
+ availableKeys = new Set();
708
+ }
709
+ // Rank: exact id (100) > exact provider/id (90) > id prefix (80) > id
710
+ // substring (60) > provider/id substring (40); +5 if the model is available.
711
+ const rank = (m) => {
712
+ const id = m.id.toLowerCase();
713
+ const key = modelKey(m).toLowerCase();
714
+ let base = -1;
715
+ if (id === q)
716
+ base = 100;
717
+ else if (key === q)
718
+ base = 90;
719
+ else if (id.startsWith(q))
720
+ base = 80;
721
+ else if (id.includes(q))
722
+ base = 60;
723
+ else if (key.includes(q))
724
+ base = 40;
725
+ else
726
+ return -1;
727
+ return base + (availableKeys.has(modelKey(m)) ? 5 : 0);
728
+ };
729
+ let best;
730
+ let bestRank = 0;
731
+ for (const m of all) {
732
+ const r = rank(m);
733
+ if (r > bestRank) {
734
+ bestRank = r;
735
+ best = m;
736
+ }
737
+ }
738
+ return best;
739
+ };
740
+ // Cache per-currentSession() wire info keyed by (path, mtime, size) so reopening the
741
+ // picker does not re-read the whole corpus (see currentSession()-list-cache.ts).
742
+ const sessionListCache = new SessionListCache(sessionListCachePath());
743
+ sessionListCache.prewarm(currentSession().sessionManager.getSessionDir());
744
+ const handleHelloFrame = (client, frame) => {
745
+ // Admit at the REQUESTED role — unconditionally. `controller` is a
746
+ // per-client write capability, not a singleton slot, so an arriving
747
+ // writable client never demotes (or is demoted by) an already-attached
748
+ // one: attached terminal panes are all writable at once.
749
+ //
750
+ // A repeated hello on an already-helloed socket must not RE-role it: the
751
+ // role is fixed for the socket's lifetime, so every downstream gate and
752
+ // every already-fanned-out dialog keep the authority the first welcome
753
+ // stated. A duplicate hello still re-sends the catch-up snapshot.
754
+ const firstHello = !client.helloed;
755
+ client.id = frame.client_id;
756
+ if (firstHello) {
757
+ client.helloed = true;
758
+ client.role = frame.role === 'controller' ? 'controller' : 'observer';
759
+ }
760
+ sendWelcome(client);
761
+ if (firstHello)
762
+ registry.persistAttachState(); // a helloed viewer arrived
763
+ // welcome carried the FIRST pending dialog; replay any extras so a writable
764
+ // client attaching mid-dialog can answer every in-flight dialog.
765
+ if (client.role === 'controller')
766
+ replayExtraPendingDialogsTo(client);
767
+ };
768
+ const handleDriveFrame = (client, frame) => {
769
+ if (notWritable(client, 'drive the engine'))
770
+ return;
771
+ driveEngine(client, frame);
772
+ };
773
+ const handleDeliverFrame = (client, frame) => {
774
+ // Interactive delivery from a one-shot server client (crtrd's
775
+ // `POST /messages` `delivery:'interactive'` path). Routed HERE,
776
+ // broker-side, because only the broker authoritatively knows streaming
777
+ // state: the synthesized `prompt` frame reuses resolveEngineRoute's m-B
778
+ // routing (idle → prompt(); mid-stream → prompt({streamingBehavior:
779
+ // 'steer'}), pi's own Enter-while-streaming submit) plus the memory-refs
780
+ // guidance path — byte-identical behavior to a viewer's Enter. Acked
781
+ // once ACCEPTED (correlated by the frame's id) so the daemon returns
782
+ // deterministically; `detail` reports which route was taken.
783
+ //
784
+ // The ack reports the engine call's OUTCOME, never merely the fact that
785
+ // a call was made: `currentSession().prompt()` rejects outright when the model's
786
+ // provider has no credential, and acking unconditionally reported
787
+ // `delivered:true` for a message the engine had refused — the caller's
788
+ // one-shot client then closed before the error frame arrived and the
789
+ // message vanished with no trace. A prompt route therefore acks on the
790
+ // race between acceptance (agent_start) and rejection; a steer route
791
+ // joins a turn already running, so no new agent_start is coming and its
792
+ // ack stays at routing time.
793
+ if (notWritable(client, 'drive the engine', frame.id))
794
+ return;
795
+ const via = currentSession().isStreaming ? 'steer' : 'prompt';
796
+ let delivered = false;
797
+ const ackAccepted = () => {
798
+ if (delivered)
799
+ return;
800
+ delivered = true;
801
+ ackTo(client, 'deliver', true, via, frame.id);
802
+ };
803
+ if (via === 'prompt')
804
+ turnAcceptedWaiters.add(ackAccepted);
805
+ const outcome = driveEngine(client, {
806
+ type: 'prompt',
807
+ text: frame.text,
808
+ ...(frame.images !== undefined ? { images: frame.images } : {}),
809
+ });
810
+ if (via === 'steer' || outcome === undefined) {
811
+ turnAcceptedWaiters.delete(ackAccepted);
812
+ ackAccepted();
813
+ return;
814
+ }
815
+ void outcome.then((failure) => {
816
+ turnAcceptedWaiters.delete(ackAccepted);
817
+ if (failure === undefined) {
818
+ ackAccepted();
819
+ return;
820
+ }
821
+ // The error frame already went to the client, but a one-shot deliver
822
+ // client is gone by then — record it where it survives.
823
+ emitEvent({
824
+ level: 'error',
825
+ event: 'broker.prompt.deliver_failed',
826
+ error: failure,
827
+ });
828
+ if (delivered)
829
+ return;
830
+ delivered = true;
831
+ ackTo(client, 'deliver', false, String(failure), frame.id);
832
+ });
833
+ };
834
+ const handleDialogFrame = (client, frame) => {
835
+ if (notWritable(client, 'answer dialogs'))
836
+ return;
837
+ // First response for this id wins: `resolve` settles the entry and deletes
838
+ // it, so every later response from a peer that was also fanned this dialog
839
+ // finds nothing here and is a silent no-op.
840
+ pendingDialogs.get(frame.id)?.resolve(frame);
841
+ };
842
+ const handleEngineControlFrame = (client, frame) => {
843
+ switch (frame.type) {
844
+ // --- extended engine-command ops (T3, §1.2 floor set) ------------------
845
+ case 'set_model': {
846
+ if (notWritable(client, 'set the model'))
847
+ break;
848
+ const requested = parseModelSpec(frame.model);
849
+ let model;
850
+ let normalized;
851
+ try {
852
+ // Tier/alias specs (`opus`, `strong`, `anthropic/strong`, …) MUST resolve
853
+ // through the config ladder before touching the registry: the fuzzy
854
+ // registry search rank-ties `opus` between claude-3-opus-20240229 and
855
+ // claude-opus-4-8, and the deprecated 2024 model wins on catalog order.
856
+ // Concrete `provider/id` specs and genuine substrings (`fable`) pass
857
+ // through normalizeModel unchanged and take the exact/search path below.
858
+ normalized = parseModelSpec(normalizeModel(requested.modelSpec));
859
+ model = resolveModelQuery(normalized.modelSpec);
860
+ }
861
+ catch (err) {
862
+ // N2: registry.find should never throw on the real SDK, but a degenerate
863
+ // engine must still get a reply rather than a silently-dropped frame.
864
+ engineErrorTo(client)(err);
865
+ break;
866
+ }
867
+ if (model === undefined) {
868
+ registry.sendFrame(client, {
869
+ type: 'error',
870
+ code: 'engine_error',
871
+ message: `no model matching '${frame.model}' in the registry`,
872
+ });
873
+ break;
874
+ }
875
+ void currentSession()
876
+ .setModel(model)
877
+ .then(async () => {
878
+ // The caller's explicit `:thinking` wins; else the ladder cell's.
879
+ const thinking = requested.thinkingLevel ?? normalized.thinkingLevel;
880
+ if (thinking !== undefined)
881
+ currentSession().setThinkingLevel(thinking);
882
+ // frame.pinned, when the sender pre-normalized `frame.model` (e.g.
883
+ // setModelLive), carries the RAW-token pin decision explicitly.
884
+ // Otherwise frame.model itself IS the raw/explicit token (a
885
+ // slash-command arg, or a concrete picker pick) and derives it.
886
+ await broadcastModelChanged(true, frame.pinned !== undefined
887
+ ? frame.pinned
888
+ : isProviderPinnedModelToken(frame.model), true);
889
+ ackTo(client, 'set_model');
890
+ })
891
+ .catch(engineErrorTo(client));
892
+ break;
893
+ }
894
+ case 'deliver_custom_message': {
895
+ if (notWritable(client, 'deliver a custom message'))
896
+ break;
897
+ // Never triggers a turn either way. `nextTurn` folds the message into
898
+ // whatever turn comes next (situational context); omitting it pushes
899
+ // straight onto the message list so it lands FIRST in a still-empty
900
+ // currentSession() (a freshly-claimed spare's bearings). The inbox watcher's own
901
+ // situational send uses `followUp` + triggerTurn because there its job
902
+ // IS to wake the node.
903
+ void currentSession()
904
+ .sendCustomMessage({
905
+ customType: frame.customType,
906
+ content: frame.content,
907
+ display: true,
908
+ ...(frame.details !== undefined
909
+ ? { details: frame.details }
910
+ : {}),
911
+ }, ...(frame.deliverAs !== undefined
912
+ ? [{ deliverAs: frame.deliverAs }]
913
+ : []))
914
+ .then(() => ackTo(client, 'deliver_custom_message'))
915
+ .catch(engineErrorTo(client));
916
+ break;
917
+ }
918
+ case 'cycle_model': {
919
+ if (notWritable(client, 'cycle the model'))
920
+ break;
921
+ void currentSession()
922
+ .cycleModel(frame.direction)
923
+ .then(async () => {
924
+ await broadcastModelChanged(true, undefined, true);
925
+ ackTo(client, 'cycle_model');
926
+ })
927
+ .catch(engineErrorTo(client));
928
+ break;
929
+ }
930
+ case 'cycle_ladder': {
931
+ if (notWritable(client, 'cycle the model ladder'))
932
+ break;
933
+ // Resolve the next interleaved-ladder rung from the current model+thinking
934
+ // spec, then reuse the set_model path (registry resolve + thinking apply).
935
+ // The cycle only offers signed-in provider columns — same rule as the
936
+ // setup wizard's Models panel: pool occupancy for a managed provider
937
+ // (read live, so a login that landed mid-currentSession() counts immediately),
938
+ // else the registry's auth status. When nobody is signed in the helper
939
+ // returns every provider, so the cycle stays walkable pre-login.
940
+ const offeredProviders = (() => {
941
+ try {
942
+ const registry = registryOf(currentServices());
943
+ return configuredLadderProviders((providerId) => (isManagedProvider(providerId) &&
944
+ readSubscriptionPool(providerId).length > 0) ||
945
+ registry.getProviderAuthStatus(providerId).configured);
946
+ }
947
+ catch {
948
+ return undefined; // auth state unreadable — cycle the full ladder
949
+ }
950
+ })();
951
+ const currentSpec = formatModelSpec(currentSession().model, currentSession().thinkingLevel) ?? '';
952
+ const nextSpec = parseModelSpec(nextLadderModel(currentSpec, frame.direction === 'backward' ? -1 : 1, offeredProviders));
953
+ let ladderModel;
954
+ try {
955
+ ladderModel = resolveModelQuery(nextSpec.modelSpec);
956
+ }
957
+ catch (err) {
958
+ engineErrorTo(client)(err);
959
+ break;
960
+ }
961
+ if (ladderModel === undefined) {
962
+ registry.sendFrame(client, {
963
+ type: 'error',
964
+ code: 'engine_error',
965
+ message: `ladder model '${nextSpec.modelSpec}' not in the registry`,
966
+ });
967
+ break;
968
+ }
969
+ void currentSession()
970
+ .setModel(ladderModel)
971
+ .then(async () => {
972
+ if (nextSpec.thinkingLevel !== undefined)
973
+ currentSession().setThinkingLevel(nextSpec.thinkingLevel);
974
+ await broadcastModelChanged(true, undefined, true);
975
+ ackTo(client, 'cycle_ladder');
976
+ })
977
+ .catch(engineErrorTo(client));
978
+ break;
979
+ }
980
+ case 'cycle_thinking': {
981
+ if (notWritable(client, 'cycle the thinking level'))
982
+ break;
983
+ void (async () => {
984
+ currentSession().cycleThinkingLevel();
985
+ await persistModelChoice(undefined, true);
986
+ ackTo(client, 'cycle_thinking');
987
+ })().catch(engineErrorTo(client));
988
+ break;
989
+ }
990
+ case 'dequeue': {
991
+ if (notWritable(client, 'dequeue messages', frame.id))
992
+ break;
993
+ try {
994
+ const { steering, followUp } = currentSession().clearQueue();
995
+ registry.sendFrame(client, {
996
+ type: 'data',
997
+ id: frame.id,
998
+ kind: 'dequeue',
999
+ steering,
1000
+ followUp,
1001
+ });
1002
+ }
1003
+ catch (err) {
1004
+ engineErrorTo(client, frame.id)(err);
1005
+ }
1006
+ break;
1007
+ }
1008
+ case 'set_thinking_level': {
1009
+ if (notWritable(client, 'set the thinking level'))
1010
+ break;
1011
+ void (async () => {
1012
+ currentSession().setThinkingLevel(frame.level);
1013
+ await persistModelChoice(undefined, true);
1014
+ ackTo(client, 'set_thinking_level');
1015
+ })().catch(engineErrorTo(client));
1016
+ break;
1017
+ }
1018
+ case 'set_auto_retry': {
1019
+ if (notWritable(client, 'set auto-retry'))
1020
+ break;
1021
+ try {
1022
+ currentSession().setAutoRetryEnabled(frame.enabled);
1023
+ ackTo(client, 'set_auto_retry');
1024
+ }
1025
+ catch (err) {
1026
+ engineErrorTo(client)(err);
1027
+ }
1028
+ break;
1029
+ }
1030
+ case 'set_auto_compaction': {
1031
+ if (notWritable(client, 'set auto-compaction'))
1032
+ break;
1033
+ try {
1034
+ currentSession().setAutoCompactionEnabled(frame.enabled);
1035
+ ackTo(client, 'set_auto_compaction');
1036
+ }
1037
+ catch (err) {
1038
+ engineErrorTo(client)(err);
1039
+ }
1040
+ break;
1041
+ }
1042
+ case 'compact': {
1043
+ if (notWritable(client, 'compact the currentSession()'))
1044
+ break;
1045
+ void currentSession()
1046
+ .compact(frame.instructions)
1047
+ .then(() => ackTo(client, 'compact'))
1048
+ .catch(engineErrorTo(client));
1049
+ break;
1050
+ }
1051
+ case 'set_session_name': {
1052
+ if (notWritable(client, 'rename the currentSession()'))
1053
+ break;
1054
+ try {
1055
+ currentSession().setSessionName(frame.name);
1056
+ ackTo(client, 'set_session_name');
1057
+ }
1058
+ catch (err) {
1059
+ engineErrorTo(client)(err);
1060
+ }
1061
+ break;
1062
+ }
1063
+ }
1064
+ };
1065
+ const handleReadFrame = (client, frame) => {
1066
+ switch (frame.type) {
1067
+ case 'get_commands': {
1068
+ // OBSERVERS may call this: the merged command inventory is static
1069
+ // (extensions + templates + skills + builtins) and drives nothing in the
1070
+ // engine, so it is not a controller-gated op. Terminal observer
1071
+ // connections use it to populate the command palette.
1072
+ // The merged command list rides in ack.detail as JSON (the foundation's
1073
+ // AckFrame.detail field) — the viewer (T6) JSON.parses it when for ===
1074
+ // 'get_commands'. Keeps every command op a uniform ack reply.
1075
+ try {
1076
+ ackTo(client, 'get_commands', true, JSON.stringify(buildCommandList(currentSession())));
1077
+ }
1078
+ catch (err) {
1079
+ engineErrorTo(client)(err);
1080
+ }
1081
+ break;
1082
+ }
1083
+ // --- read-op picker data (§5 Unit A) ----------------------------------
1084
+ // NOT controller-gated: read-only, like get_commands. Terminal observer
1085
+ // connections populate pickers through these too.
1086
+ case 'list_models': {
1087
+ try {
1088
+ registry.sendFrame(client, buildListModelsData(frame.id, currentSession()));
1089
+ }
1090
+ catch (err) {
1091
+ engineErrorTo(client, frame.id)(err);
1092
+ }
1093
+ break;
1094
+ }
1095
+ case 'list_scoped_models': {
1096
+ try {
1097
+ registry.sendFrame(client, buildScopedModelsData(frame.id, currentSession()));
1098
+ }
1099
+ catch (err) {
1100
+ engineErrorTo(client, frame.id)(err);
1101
+ }
1102
+ break;
1103
+ }
1104
+ case 'list_memory_refs': {
1105
+ try {
1106
+ registry.sendFrame(client, buildListMemoryRefsData(frame.id, getRefInventory().refs, engineLeadingCommandTokens(currentSession())));
1107
+ }
1108
+ catch (err) {
1109
+ engineErrorTo(client, frame.id)(err);
1110
+ }
1111
+ break;
1112
+ }
1113
+ case 'get_tree': {
1114
+ try {
1115
+ registry.sendFrame(client, buildGetTreeData(frame.id, currentSession(), reviewCompanion));
1116
+ }
1117
+ catch (err) {
1118
+ engineErrorTo(client, frame.id)(err);
1119
+ }
1120
+ break;
1121
+ }
1122
+ case 'get_settings': {
1123
+ try {
1124
+ registry.sendFrame(client, buildSettingsData(frame.id, currentSession()));
1125
+ }
1126
+ catch (err) {
1127
+ engineErrorTo(client, frame.id)(err);
1128
+ }
1129
+ break;
1130
+ }
1131
+ case 'list_sessions': {
1132
+ // Session listing reads the currentSession() dir off disk — async; reply when it
1133
+ // resolves (or relay the error, correlated by id so the viewer doesn't hang).
1134
+ void buildSessionsData(frame.id, frame.scope ?? 'cwd', currentSession(), sessionListCache)
1135
+ .then((d) => registry.sendFrame(client, d))
1136
+ .catch(engineErrorTo(client, frame.id));
1137
+ break;
1138
+ }
1139
+ case 'get_human_fork_coordinates': {
1140
+ // NOT controller-gated: this is a pure observer read. Branch creation is
1141
+ // intentionally delegated to a detached SDK manager in the ask publisher;
1142
+ // calling createBranchedSession here corrupts this live manager's identity.
1143
+ try {
1144
+ const sm = currentSession().sessionManager;
1145
+ const sourceFile = sm.getSessionFile();
1146
+ const leafId = sm.getLeafId();
1147
+ if (sourceFile === undefined ||
1148
+ sourceFile === '' ||
1149
+ leafId === null) {
1150
+ engineErrorTo(client, frame.id)(new Error('no persisted source currentSession() leaf'));
1151
+ break;
1152
+ }
1153
+ registry.sendFrame(client, {
1154
+ type: 'data',
1155
+ id: frame.id,
1156
+ kind: 'human_fork_coordinates',
1157
+ sourceFile,
1158
+ sourceSessionId: sm.getSessionId(),
1159
+ leafId,
1160
+ modelSpec: formatExactModelSpec(currentSession().model, currentSession().thinkingLevel),
1161
+ });
1162
+ }
1163
+ catch (err) {
1164
+ engineErrorTo(client, frame.id)(err);
1165
+ }
1166
+ break;
1167
+ }
1168
+ }
1169
+ };
1170
+ const handleSessionFrame = (client, frame) => {
1171
+ switch (frame.type) {
1172
+ case 'navigate_tree': {
1173
+ if (notWritable(client, 'navigate the currentSession() tree'))
1174
+ break;
1175
+ if (isPreBoundaryReviewEntry(frame.targetId)) {
1176
+ reviewBoundaryRefusalTo(client, frame.targetId);
1177
+ break;
1178
+ }
1179
+ // navigateTree rewinds IN-PLACE (same currentSession() file, new leaf) and emits no
1180
+ // relayed event, so every viewer must be re-snapshotted onto the rewound
1181
+ // transcript — same reWelcomeAll the currentSession()-replacing ops use. The ack's
1182
+ // detail carries the navigated-to user message's text (pi parity: the
1183
+ // interactive tree navigator restores it to the editor for re-editing).
1184
+ void currentSession()
1185
+ .navigateTree(frame.targetId, frame.options)
1186
+ .then((r) => {
1187
+ if (!r.cancelled)
1188
+ reWelcomeAll();
1189
+ ackTo(client, 'navigate_tree', !r.cancelled, r.editorText);
1190
+ })
1191
+ .catch(engineErrorTo(client));
1192
+ break;
1193
+ }
1194
+ case 'reload': {
1195
+ if (notWritable(client, 'reload'))
1196
+ break;
1197
+ // A SUCCESSFUL reload invalidates the memoized ref inventory (work item
1198
+ // 3) so the next read-op/submission re-walks the corpus, AND clears the
1199
+ // shared substrate parse cache buildRefInventory reads through —
1200
+ // otherwise a fresh buildRefInventory() call would still see the stale
1201
+ // cached docs. A FAILED reload (`.catch`) leaves both untouched: nothing
1202
+ // about the corpus is known to have changed.
1203
+ void currentSession()
1204
+ .reload()
1205
+ .then(() => {
1206
+ memoizedRefInventory = null;
1207
+ clearSessionCache();
1208
+ ackTo(client, 'reload');
1209
+ })
1210
+ .catch(engineErrorTo(client));
1211
+ break;
1212
+ }
1213
+ case 'export': {
1214
+ if (notWritable(client, 'export the currentSession()'))
1215
+ break;
1216
+ if (frame.format === 'jsonl') {
1217
+ try {
1218
+ const outPath = currentSession().exportToJsonl(frame.path);
1219
+ stripHiddenEntriesFromJsonlExport(outPath);
1220
+ ackTo(client, 'export');
1221
+ }
1222
+ catch (err) {
1223
+ engineErrorTo(client)(err);
1224
+ }
1225
+ }
1226
+ else {
1227
+ void currentSession()
1228
+ .exportToHtml(frame.path)
1229
+ .then(() => ackTo(client, 'export'))
1230
+ .catch(engineErrorTo(client));
1231
+ }
1232
+ break;
1233
+ }
1234
+ case 'new_session': {
1235
+ if (notWritable(client, 'start a new currentSession()'))
1236
+ break;
1237
+ rebind.runReplacement(client, 'new_session', (rt) => rt.newSession());
1238
+ break;
1239
+ }
1240
+ case 'switch_session': {
1241
+ if (notWritable(client, 'switch sessions'))
1242
+ break;
1243
+ rebind.runReplacement(client, 'switch_session', (rt) => rt.switchSession(frame.path));
1244
+ break;
1245
+ }
1246
+ case 'fork': {
1247
+ if (notWritable(client, 'fork the currentSession()'))
1248
+ break;
1249
+ if (isPreBoundaryReviewEntry(frame.entryId)) {
1250
+ reviewBoundaryRefusalTo(client, frame.entryId);
1251
+ break;
1252
+ }
1253
+ rebind.runReplacement(client, 'fork', (rt) => rt.fork(frame.entryId));
1254
+ break;
1255
+ }
1256
+ case 'clone': {
1257
+ if (notWritable(client, 'clone the currentSession()'))
1258
+ break;
1259
+ rebind.runReplacement(client, 'clone', async (rt) => {
1260
+ const sm = currentSession().sessionManager;
1261
+ const leafId = sm.getLeafId();
1262
+ if (leafId === null) {
1263
+ return { cancelled: true, detail: 'no current leaf to clone from' };
1264
+ }
1265
+ const newPath = sm.createBranchedSession(leafId);
1266
+ if (newPath === undefined) {
1267
+ return {
1268
+ cancelled: true,
1269
+ detail: 'createBranchedSession returned no path',
1270
+ };
1271
+ }
1272
+ return rt.switchSession(newPath);
1273
+ });
1274
+ break;
1275
+ }
1276
+ case 'share': {
1277
+ if (notWritable(client, 'share the currentSession()'))
1278
+ break;
1279
+ const tmpPath = join(tmpdir(), `pi-share-${Date.now()}.html`);
1280
+ void currentSession()
1281
+ .exportToHtml(tmpPath)
1282
+ .then(() => new Promise((resolve, reject) => {
1283
+ execFile('gh', ['gist', 'create', '--secret', tmpPath], { timeout: 30_000, maxBuffer: 1024 * 1024 }, (err, stdout) => {
1284
+ try {
1285
+ if (existsSync(tmpPath))
1286
+ unlinkSync(tmpPath);
1287
+ }
1288
+ catch {
1289
+ /* cleanup */
1290
+ }
1291
+ if (err) {
1292
+ reject(err);
1293
+ return;
1294
+ }
1295
+ resolve(stdout.trim());
1296
+ });
1297
+ }))
1298
+ .then((url) => {
1299
+ ackTo(client, 'share', true, url);
1300
+ })
1301
+ .catch((err) => {
1302
+ const msg = String(err?.message ?? err);
1303
+ ackTo(client, 'share', false, msg.includes('not found') ||
1304
+ msg.includes('gh: command not found') ||
1305
+ msg.includes('ENOENT')
1306
+ ? '`gh` CLI not found — install GitHub CLI and authenticate with `gh auth login`'
1307
+ : `share failed: ${msg}`);
1308
+ });
1309
+ break;
1310
+ }
1311
+ }
1312
+ };
1313
+ const handleAuthenticationFrame = (client, frame) => {
1314
+ // Open to any client, observers included: reload_auth is an idempotent
1315
+ // local re-read of shared credentials + the runtime's availability
1316
+ // snapshot. It does not steer the conversation, so the daemon's
1317
+ // canvas-wide /login fan can ride an observer connection.
1318
+ try {
1319
+ ackTo(client, 'reload_auth');
1320
+ const authGeneration = rebind.installedGeneration();
1321
+ const existingAuthReload = reloadAuthInFlight;
1322
+ let authReloadPromise;
1323
+ if (existingAuthReload !== null &&
1324
+ existingAuthReload.generation === authGeneration) {
1325
+ authReloadPromise = existingAuthReload.promise;
1326
+ }
1327
+ if (authGeneration !== undefined && authReloadPromise === undefined) {
1328
+ const authSession = currentSession();
1329
+ const authServices = currentServices();
1330
+ authReloadPromise = (async () => {
1331
+ // Credentials are read fresh by PoolCredentialStore; only the
1332
+ // ModelRuntime snapshot needs rebuilding. Authentication reloads
1333
+ // never fetch provider catalogs over the network.
1334
+ await authServices.modelRuntime.refresh({ allowNetwork: false });
1335
+ if (rebind.installedGeneration() !== authGeneration ||
1336
+ currentSession() !== authSession)
1337
+ return;
1338
+ const modelLess = authSession.model === undefined ||
1339
+ isUnknownModel(authSession.model);
1340
+ const reconcilesLogicalRoute = cfg.modelExact !== true &&
1341
+ cfg.modelProviderPinned !== true &&
1342
+ (process.env['CRTR_MODEL_INTENT'] ?? '').trim() !== '';
1343
+ if (modelLess || reconcilesLogicalRoute) {
1344
+ const resolved = await resolveLaunchModel(authServices, cfg);
1345
+ if (rebind.installedGeneration() !== authGeneration ||
1346
+ currentSession() !== authSession)
1347
+ return;
1348
+ if (resolved !== undefined) {
1349
+ const currentSpec = formatModelSpec(authSession.model, authSession.thinkingLevel);
1350
+ const resolvedSpec = formatModelSpec(resolved.model, resolved.thinkingLevel);
1351
+ if (resolvedSpec !== null && resolvedSpec !== currentSpec) {
1352
+ await authSession.setModel(resolved.model);
1353
+ if (rebind.installedGeneration() !== authGeneration ||
1354
+ currentSession() !== authSession)
1355
+ return;
1356
+ authSession.setThinkingLevel((resolved.thinkingLevel ?? 'off'));
1357
+ }
1358
+ }
1359
+ }
1360
+ if (rebind.installedGeneration() !== authGeneration ||
1361
+ currentSession() !== authSession)
1362
+ return;
1363
+ // Persist an algorithmic route choice without clearing the logical
1364
+ // intent that makes future reconciliation possible.
1365
+ await broadcastModelChanged();
1366
+ drivePendingKickoff();
1367
+ })().catch((err) => {
1368
+ emitEvent({
1369
+ level: 'warn',
1370
+ event: 'broker.auth_reload.reconcile_failed',
1371
+ error: err,
1372
+ });
1373
+ });
1374
+ const work = {
1375
+ generation: authGeneration,
1376
+ promise: authReloadPromise,
1377
+ };
1378
+ reloadAuthInFlight = work;
1379
+ void authReloadPromise.finally(() => {
1380
+ if (reloadAuthInFlight === work)
1381
+ reloadAuthInFlight = null;
1382
+ });
1383
+ }
1384
+ const authFault = readFault(nodeId);
1385
+ const authFaultGeneration = rebind.installedGeneration();
1386
+ const authFaultSession = currentSession();
1387
+ if (authFault?.link === 'pi→provider' &&
1388
+ authFault.kind === 'auth' &&
1389
+ !authFaultSession.isStreaming &&
1390
+ !authFaultRedriveInFlight) {
1391
+ authFaultRedriveInFlight = true;
1392
+ void (async () => {
1393
+ try {
1394
+ if (authReloadPromise !== undefined)
1395
+ await authReloadPromise;
1396
+ if (rebind.installedGeneration() !== authFaultGeneration ||
1397
+ currentSession() !== authFaultSession)
1398
+ return;
1399
+ const anchorEntryId = authFault.anchorEntryId;
1400
+ if (anchorEntryId !== undefined) {
1401
+ try {
1402
+ const leafId = authFaultSession.sessionManager.getLeafId?.() ?? null;
1403
+ if (leafId !== anchorEntryId)
1404
+ await authFaultSession.navigateTree(anchorEntryId);
1405
+ if (rebind.installedGeneration() !== authFaultGeneration ||
1406
+ currentSession() !== authFaultSession)
1407
+ return;
1408
+ }
1409
+ catch (err) {
1410
+ emitEvent({
1411
+ level: 'warn',
1412
+ event: 'broker.auth_fault.rewind_skipped',
1413
+ error: err,
1414
+ });
1415
+ }
1416
+ }
1417
+ const fresh = readFault(nodeId);
1418
+ if (rebind.installedGeneration() !== authFaultGeneration ||
1419
+ currentSession() !== authFaultSession ||
1420
+ authFaultSession.isStreaming ||
1421
+ fresh?.link !== 'pi→provider' ||
1422
+ fresh.kind !== 'auth' ||
1423
+ fresh.since !== authFault.since)
1424
+ return;
1425
+ clearFault(nodeId, { link: 'pi→provider' });
1426
+ await authFaultSession.prompt(AUTH_FAULT_RECOVERY_BODY);
1427
+ }
1428
+ catch (err) {
1429
+ emitEvent({
1430
+ level: 'error',
1431
+ event: 'broker.auth_fault.redrive_failed',
1432
+ error: err,
1433
+ });
1434
+ }
1435
+ finally {
1436
+ authFaultRedriveInFlight = false;
1437
+ }
1438
+ })();
1439
+ }
1440
+ }
1441
+ catch (err) {
1442
+ engineErrorTo(client)(err);
1443
+ }
1444
+ };
1445
+ const handleByeFrame = (client) => {
1446
+ client.socket.end();
1447
+ };
1448
+ const handleShutdownFrame = () => {
1449
+ disposeAndExit('shutdown-frame');
1450
+ };
1451
+ return {
1452
+ handlers: {
1453
+ hello: handleHelloFrame,
1454
+ drive: handleDriveFrame,
1455
+ deliver: handleDeliverFrame,
1456
+ dialog: handleDialogFrame,
1457
+ engineControl: handleEngineControlFrame,
1458
+ read: handleReadFrame,
1459
+ session: handleSessionFrame,
1460
+ authentication: handleAuthenticationFrame,
1461
+ bye: handleByeFrame,
1462
+ shutdown: handleShutdownFrame,
1463
+ },
1464
+ kickoff(prompt) {
1465
+ kickoffPending = prompt;
1466
+ drivePendingKickoff();
1467
+ },
1468
+ notifyTurnAccepted,
1469
+ pendingPromptDispatches: () => pendingPromptDispatches,
1470
+ resetForSession: () => {
1471
+ reloadAuthInFlight = null;
1472
+ },
1473
+ };
1474
+ }
1475
+ /** The socket listener calls this explicit dispatcher for every decoded client frame. */
1476
+ export function handleFrame(ctx, client, frame) {
1477
+ switch (frame.type) {
1478
+ case 'hello':
1479
+ ctx.handlers.hello(client, frame);
1480
+ return;
1481
+ case 'prompt':
1482
+ case 'steer':
1483
+ case 'follow_up':
1484
+ case 'abort':
1485
+ case 'bash':
1486
+ ctx.handlers.drive(client, frame);
1487
+ return;
1488
+ case 'deliver':
1489
+ ctx.handlers.deliver(client, frame);
1490
+ return;
1491
+ case 'extension_ui_response':
1492
+ ctx.handlers.dialog(client, frame);
1493
+ return;
1494
+ case 'set_model':
1495
+ case 'deliver_custom_message':
1496
+ case 'cycle_model':
1497
+ case 'cycle_ladder':
1498
+ case 'cycle_thinking':
1499
+ case 'dequeue':
1500
+ case 'set_thinking_level':
1501
+ case 'set_auto_retry':
1502
+ case 'set_auto_compaction':
1503
+ case 'compact':
1504
+ case 'set_session_name':
1505
+ ctx.handlers.engineControl(client, frame);
1506
+ return;
1507
+ case 'get_commands':
1508
+ case 'list_models':
1509
+ case 'list_scoped_models':
1510
+ case 'list_memory_refs':
1511
+ case 'get_tree':
1512
+ case 'get_settings':
1513
+ case 'list_sessions':
1514
+ case 'get_human_fork_coordinates':
1515
+ ctx.handlers.read(client, frame);
1516
+ return;
1517
+ case 'navigate_tree':
1518
+ case 'reload':
1519
+ case 'export':
1520
+ case 'new_session':
1521
+ case 'switch_session':
1522
+ case 'fork':
1523
+ case 'clone':
1524
+ case 'share':
1525
+ ctx.handlers.session(client, frame);
1526
+ return;
1527
+ case 'reload_auth':
1528
+ ctx.handlers.authentication(client, frame);
1529
+ return;
1530
+ case 'bye':
1531
+ ctx.handlers.bye(client);
1532
+ return;
1533
+ case 'shutdown':
1534
+ ctx.handlers.shutdown();
1535
+ return;
1536
+ }
1537
+ }