@north-light/crouter 0.3.170 → 0.3.172

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (670) hide show
  1. package/dist/api/client.d.ts +37 -11
  2. package/dist/api/client.js +84 -15
  3. package/dist/api/dto/human.d.ts +53 -39
  4. package/dist/api/dto/human.js +2 -3
  5. package/dist/api/dto/inbox.d.ts +0 -1
  6. package/dist/api/dto/nodes.d.ts +12 -0
  7. package/dist/api/dto/review-comments.d.ts +137 -0
  8. package/dist/api/dto/review-comments.js +5 -0
  9. package/dist/api/dto/reviews.d.ts +109 -0
  10. package/dist/api/dto/reviews.js +5 -0
  11. package/dist/api/index.d.ts +2 -0
  12. package/dist/api/index.js +2 -0
  13. package/dist/api/routes.d.ts +17 -4
  14. package/dist/api/routes.js +21 -5
  15. package/dist/build-root.d.ts +1 -16
  16. package/dist/build-root.js +1 -25
  17. package/dist/builtin-memory/00-runtime-base.md +7 -5
  18. package/dist/builtin-memory/04-base-worker.md +1 -1
  19. package/dist/builtin-memory/04-orchestration-kernel.md +7 -7
  20. package/dist/builtin-memory/05-kinds/design/00-base.md +1 -1
  21. package/dist/builtin-memory/05-kinds/design/01-orchestrator.md +1 -1
  22. package/dist/builtin-memory/05-kinds/developer/00-base.md +1 -1
  23. package/dist/builtin-memory/05-kinds/explore/00-base.md +1 -1
  24. package/dist/builtin-memory/05-kinds/plan/00-base.md +1 -1
  25. package/dist/builtin-memory/05-kinds/plan/01-orchestrator.md +1 -1
  26. package/dist/builtin-memory/05-kinds/review/00-base.md +1 -1
  27. package/dist/builtin-memory/05-kinds/review/companion/00-base.md +20 -0
  28. package/dist/builtin-memory/05-kinds/spec/00-base.md +3 -7
  29. package/dist/builtin-memory/05-kinds/spec/01-orchestrator.md +3 -3
  30. package/dist/builtin-memory/05-kinds/spec/requirements.md +3 -3
  31. package/dist/builtin-memory/design.md +2 -2
  32. package/dist/builtin-memory/insights/capture.md +50 -0
  33. package/dist/builtin-memory/insights/init.md +45 -0
  34. package/dist/builtin-memory/insights/listen.md +9 -0
  35. package/dist/builtin-memory/internal/agent-shaping.md +2 -2
  36. package/dist/builtin-memory/internal/nodes-and-canvas.md +1 -1
  37. package/dist/builtin-memory/internal/plugins.md +12 -11
  38. package/dist/builtin-memory/internal/storage-tiers.md +2 -2
  39. package/dist/builtin-memory/{planning.md → plan/roadmap.md} +3 -3
  40. package/dist/builtin-memory/plan.md +19 -0
  41. package/dist/builtin-memory/spec/guide.md +38 -0
  42. package/dist/builtin-memory/spec/requirements.md +31 -0
  43. package/dist/builtin-memory/spec/roadmap.md +35 -0
  44. package/dist/builtin-memory/spec.md +10 -86
  45. package/dist/builtin-pi-packages/pi-crtr-extensions/README.md +3 -4
  46. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/__tests__/provider-rotation.test.ts +49 -5
  47. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/provider-rotation.d.ts +56 -0
  48. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/provider-rotation.js +920 -0
  49. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/provider-rotation.ts +221 -156
  50. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/statusline.ts +4 -5
  51. package/dist/cli.js +2 -6
  52. package/dist/clients/attach/__tests__/attach-chrome-remote.test.js +7 -35
  53. package/dist/clients/attach/__tests__/attach-keybindings.test.js +32 -16
  54. package/dist/clients/attach/__tests__/context-message.test.js +142 -6
  55. package/dist/clients/attach/__tests__/crtr-output.test.js +29 -45
  56. package/dist/clients/attach/__tests__/diagram.test.js +1 -1
  57. package/dist/clients/attach/__tests__/file-review-focus.test.js +48 -0
  58. package/dist/clients/attach/__tests__/group-activity.test.js +266 -0
  59. package/dist/clients/attach/__tests__/input-controller-extension-command.test.js +32 -0
  60. package/dist/clients/attach/__tests__/session-identity-refresh.test.js +32 -0
  61. package/dist/clients/attach/__tests__/titled-editor-preview.test.js +1 -2
  62. package/dist/clients/attach/chrome/header.d.ts +10 -0
  63. package/dist/clients/attach/chrome/header.js +30 -0
  64. package/dist/clients/attach/chrome/recap.d.ts +7 -0
  65. package/dist/clients/attach/chrome/recap.js +19 -0
  66. package/dist/clients/attach/chrome/status-line.d.ts +3 -0
  67. package/dist/clients/attach/chrome/status-line.js +5 -0
  68. package/dist/clients/attach/chrome/widgets.js +11 -21
  69. package/dist/clients/attach/command.js +11 -7
  70. package/dist/clients/attach/config.d.ts +4 -0
  71. package/dist/clients/attach/config.js +3 -0
  72. package/dist/clients/attach/input/controller.d.ts +19 -0
  73. package/dist/clients/attach/input/controller.js +62 -28
  74. package/dist/clients/attach/input/titled-editor.d.ts +0 -4
  75. package/dist/clients/attach/input/titled-editor.js +2 -10
  76. package/dist/clients/attach/overlays/file-review.d.ts +9 -5
  77. package/dist/clients/attach/overlays/file-review.js +9 -70
  78. package/dist/clients/attach/overlays/help.d.ts +33 -0
  79. package/dist/clients/attach/overlays/help.js +204 -0
  80. package/dist/clients/attach/render/activity-slot.d.ts +4 -0
  81. package/dist/clients/attach/render/activity-slot.js +13 -0
  82. package/dist/clients/attach/render/chat-view.d.ts +117 -12
  83. package/dist/clients/attach/render/chat-view.js +604 -135
  84. package/dist/clients/attach/render/context-message.d.ts +2 -1
  85. package/dist/clients/attach/render/context-message.js +17 -13
  86. package/dist/clients/attach/render/crtr-output.js +1 -3
  87. package/dist/clients/attach/render/diagram.js +2 -2
  88. package/dist/clients/attach/render/edit-diff.d.ts +4 -1
  89. package/dist/clients/attach/render/edit-diff.js +7 -11
  90. package/dist/clients/attach/render/group-activity.d.ts +32 -0
  91. package/dist/clients/attach/render/group-activity.js +206 -0
  92. package/dist/clients/attach/render/group-recap.d.ts +10 -0
  93. package/dist/clients/attach/render/group-recap.js +93 -0
  94. package/dist/clients/attach/render/html-markdown.d.ts +17 -0
  95. package/dist/clients/attach/render/html-markdown.js +228 -0
  96. package/dist/clients/attach/render/measured-container.d.ts +96 -0
  97. package/dist/clients/attach/render/measured-container.js +322 -0
  98. package/dist/clients/attach/render/scroll-animation.d.ts +8 -0
  99. package/dist/clients/attach/render/scroll-animation.js +80 -0
  100. package/dist/clients/attach/render/tool-calls.js +6 -6
  101. package/dist/clients/attach/render/transcript-copy.d.ts +9 -0
  102. package/dist/clients/attach/render/transcript-copy.js +81 -0
  103. package/dist/clients/attach/render/viewport.d.ts +29 -0
  104. package/dist/clients/attach/render/viewport.js +159 -0
  105. package/dist/clients/attach/session/bindings.d.ts +10 -7
  106. package/dist/clients/attach/session/bindings.js +23 -8
  107. package/dist/clients/attach/session/connection.js +4 -5
  108. package/dist/clients/attach/session/context.d.ts +6 -4
  109. package/dist/clients/attach/session/frame.d.ts +34 -0
  110. package/dist/clients/attach/session/frame.js +176 -0
  111. package/dist/clients/attach/session/frames.d.ts +4 -1
  112. package/dist/clients/attach/session/frames.js +14 -1
  113. package/dist/clients/attach/session/identity.js +4 -3
  114. package/dist/clients/attach/session/input-wiring.d.ts +14 -4
  115. package/dist/clients/attach/session/input-wiring.js +15 -13
  116. package/dist/clients/attach/session/invariant.d.ts +9 -0
  117. package/dist/clients/attach/session/invariant.js +15 -0
  118. package/dist/clients/attach/session/keys.d.ts +10 -2
  119. package/dist/clients/attach/session/keys.js +13 -5
  120. package/dist/clients/attach/session/layout.d.ts +10 -8
  121. package/dist/clients/attach/session/layout.js +33 -34
  122. package/dist/clients/attach/session/mouse.d.ts +18 -0
  123. package/dist/clients/attach/session/mouse.js +64 -0
  124. package/dist/clients/attach/session/pane-tag.d.ts +3 -0
  125. package/dist/clients/attach/session/pane-tag.js +51 -8
  126. package/dist/clients/attach/session/reconnect.d.ts +3 -0
  127. package/dist/clients/attach/session/reconnect.js +42 -5
  128. package/dist/clients/attach/session/surface-frame.d.ts +20 -0
  129. package/dist/clients/attach/session/surface-frame.js +73 -0
  130. package/dist/clients/attach/session/surface-host.d.ts +64 -0
  131. package/dist/clients/attach/session/surface-host.js +255 -0
  132. package/dist/clients/attach/session/whip.d.ts +17 -0
  133. package/dist/clients/attach/session/whip.js +334 -0
  134. package/dist/clients/attach/slash/dispatch.d.ts +13 -1
  135. package/dist/clients/attach/slash/dispatch.js +51 -29
  136. package/dist/clients/attach/viewer.js +907 -819
  137. package/dist/clients/conversation/conversation.d.ts +65 -0
  138. package/dist/clients/conversation/conversation.js +318 -0
  139. package/dist/clients/conversation/frames.d.ts +30 -0
  140. package/dist/clients/conversation/frames.js +52 -0
  141. package/dist/clients/conversation/region.d.ts +51 -0
  142. package/dist/clients/conversation/region.js +265 -0
  143. package/dist/clients/conversation/submit.d.ts +38 -0
  144. package/dist/clients/conversation/submit.js +51 -0
  145. package/dist/clients/inbox/__tests__/inbox-controller.test.js +247 -0
  146. package/dist/clients/inbox/__tests__/mount-panel.test.js +776 -0
  147. package/dist/clients/inbox/controller.d.ts +79 -0
  148. package/dist/clients/inbox/controller.js +448 -0
  149. package/dist/clients/inbox/deck-adapter.d.ts +29 -0
  150. package/dist/clients/inbox/deck-adapter.js +62 -0
  151. package/dist/clients/inbox/layout.d.ts +8 -0
  152. package/dist/clients/inbox/layout.js +9 -0
  153. package/dist/clients/inbox/resolve.d.ts +10 -0
  154. package/dist/clients/inbox/resolve.js +34 -0
  155. package/dist/clients/inbox/review/__tests__/editor-roundtrip.test.js +29 -0
  156. package/dist/clients/inbox/review/__tests__/remap.test.js +196 -0
  157. package/dist/clients/inbox/review/__tests__/review-core.test.js +341 -0
  158. package/dist/clients/inbox/review/anchors.d.ts +41 -0
  159. package/dist/clients/inbox/review/anchors.js +89 -0
  160. package/dist/clients/inbox/review/columns.d.ts +22 -0
  161. package/dist/clients/inbox/review/columns.js +40 -0
  162. package/dist/clients/inbox/review/comments-client.d.ts +66 -0
  163. package/dist/clients/inbox/review/comments-client.js +98 -0
  164. package/dist/clients/inbox/review/composition.d.ts +60 -0
  165. package/dist/clients/inbox/review/composition.js +278 -0
  166. package/dist/clients/inbox/review/document-region.d.ts +120 -0
  167. package/dist/clients/inbox/review/document-region.js +902 -0
  168. package/dist/clients/inbox/review/frame.d.ts +45 -0
  169. package/dist/clients/inbox/review/frame.js +215 -0
  170. package/dist/clients/inbox/review/keys.d.ts +67 -0
  171. package/dist/clients/inbox/review/keys.js +130 -0
  172. package/dist/clients/inbox/review/launch.d.ts +10 -0
  173. package/dist/clients/inbox/review/launch.js +129 -0
  174. package/dist/clients/inbox/review/live-source.d.ts +24 -0
  175. package/dist/clients/inbox/review/live-source.js +160 -0
  176. package/dist/clients/inbox/review/nvim.d.ts +13 -0
  177. package/dist/clients/inbox/review/nvim.js +42 -0
  178. package/dist/clients/inbox/review/remap.d.ts +51 -0
  179. package/dist/clients/inbox/review/remap.js +157 -0
  180. package/dist/clients/inbox/review/review-client.d.ts +30 -0
  181. package/dist/clients/inbox/review/review-client.js +107 -0
  182. package/dist/clients/inbox/review/roundtrip.d.ts +5 -0
  183. package/dist/clients/inbox/review/roundtrip.js +36 -0
  184. package/dist/clients/inbox/review/state.d.ts +143 -0
  185. package/dist/clients/inbox/review/state.js +282 -0
  186. package/dist/clients/inbox/review-adapter.d.ts +29 -0
  187. package/dist/clients/inbox/review-adapter.js +92 -0
  188. package/dist/clients/inbox/review-visibility.d.ts +5 -0
  189. package/dist/clients/inbox/review-visibility.js +19 -0
  190. package/dist/clients/inbox/surface.d.ts +19 -0
  191. package/dist/clients/inbox/surface.js +128 -0
  192. package/dist/clients/inbox/tui/ansi.d.ts +66 -0
  193. package/dist/clients/inbox/tui/ansi.js +317 -0
  194. package/dist/clients/inbox/tui/clipboard.d.ts +2 -0
  195. package/dist/clients/inbox/tui/clipboard.js +26 -0
  196. package/dist/clients/inbox/tui/input.d.ts +7 -0
  197. package/dist/clients/inbox/tui/input.js +653 -0
  198. package/dist/clients/inbox/tui/keys.d.ts +28 -0
  199. package/dist/clients/inbox/tui/keys.js +111 -0
  200. package/dist/clients/inbox/tui/panel.d.ts +2 -0
  201. package/dist/clients/inbox/tui/panel.js +217 -0
  202. package/dist/clients/inbox/tui/render.d.ts +37 -0
  203. package/dist/clients/inbox/tui/render.js +476 -0
  204. package/dist/clients/inbox/tui/types.d.ts +57 -0
  205. package/dist/clients/inbox/tui/types.js +1 -0
  206. package/dist/clients/inbox/tui.d.ts +6 -0
  207. package/dist/clients/inbox/tui.js +74 -0
  208. package/dist/clients/surfaces/host.d.ts +66 -0
  209. package/dist/clients/surfaces/host.js +1 -0
  210. package/dist/clients/surfaces/region.d.ts +16 -0
  211. package/dist/clients/surfaces/region.js +1 -0
  212. package/dist/clients/web/__tests__/push-engine.test.js +14 -158
  213. package/dist/clients/web/dev-server.d.ts +3 -3
  214. package/dist/clients/web/dev-server.js +9 -23
  215. package/dist/clients/web/events.js +4 -6
  216. package/dist/clients/web/push-engine.d.ts +2 -66
  217. package/dist/clients/web/push-engine.js +30 -161
  218. package/dist/clients/web/push-registry.d.ts +0 -4
  219. package/dist/clients/web/push-registry.js +2 -7
  220. package/dist/clients/web/server.js +3 -35
  221. package/dist/clients/web/web-client/shared/protocol.d.ts +0 -108
  222. package/dist/clients/web/web-cmd.js +2 -2
  223. package/dist/commands/__tests__/human.test.js +27 -15
  224. package/dist/commands/api-client.js +3 -3
  225. package/dist/commands/canvas-browse.js +1 -1
  226. package/dist/commands/canvas-issue.js +1 -1
  227. package/dist/commands/canvas-use.js +1 -1
  228. package/dist/commands/human/doc.d.ts +2 -0
  229. package/dist/commands/human/doc.js +98 -0
  230. package/dist/commands/human/inbox.d.ts +2 -0
  231. package/dist/commands/human/inbox.js +101 -0
  232. package/dist/commands/human/prompts.d.ts +2 -3
  233. package/dist/commands/human/prompts.js +27 -180
  234. package/dist/commands/human/queue.d.ts +1 -5
  235. package/dist/commands/human/queue.js +54 -216
  236. package/dist/commands/human/review.d.ts +2 -0
  237. package/dist/commands/human/review.js +298 -0
  238. package/dist/commands/human/shared.d.ts +2 -69
  239. package/dist/commands/human/shared.js +1 -181
  240. package/dist/commands/human.js +15 -20
  241. package/dist/commands/memory/__tests__/lint.test.d.ts +1 -0
  242. package/dist/commands/memory/__tests__/lint.test.js +15 -0
  243. package/dist/commands/memory/delete.js +2 -2
  244. package/dist/commands/memory/lint.d.ts +1 -0
  245. package/dist/commands/memory/lint.js +21 -3
  246. package/dist/commands/memory/read.js +4 -4
  247. package/dist/commands/node.js +8 -7
  248. package/dist/commands/pkg/plugin-manage.js +143 -68
  249. package/dist/commands/surface-inspect.js +2 -2
  250. package/dist/commands/surface.js +6 -12
  251. package/dist/commands/sys/__tests__/setup-core.test.js +9 -5
  252. package/dist/commands/sys/__tests__/setup-front-door.test.js +7 -42
  253. package/dist/commands/sys/config.js +29 -3
  254. package/dist/commands/sys/feedback.js +1 -1
  255. package/dist/commands/sys/setup-core.d.ts +12 -11
  256. package/dist/commands/sys/setup-core.js +55 -46
  257. package/dist/commands/sys/setup-wizard.d.ts +4 -1
  258. package/dist/commands/sys/setup-wizard.js +50 -9
  259. package/dist/commands/sys/sync-deps.js +1 -1
  260. package/dist/commands/sys.js +4 -6
  261. package/dist/core/__tests__/broker-sdk-wiring.test.js +6 -1
  262. package/dist/core/__tests__/broker-snapshot-history.test.js +9 -1
  263. package/dist/core/__tests__/dead-node-policy-table.test.js +3 -4
  264. package/dist/core/__tests__/fixtures/fake-engine.d.ts +8 -5
  265. package/dist/core/__tests__/fixtures/fake-engine.js +28 -2
  266. package/dist/core/__tests__/full/broker-provider-retry.test.js +87 -2
  267. package/dist/core/__tests__/full/broker-whip-interrupt.test.d.ts +1 -0
  268. package/dist/core/__tests__/full/broker-whip-interrupt.test.js +39 -0
  269. package/dist/core/__tests__/full/detach-focus.test.js +1 -1
  270. package/dist/core/__tests__/human-deliver-e2e.test.js +64 -64
  271. package/dist/core/__tests__/human-deliver.test.js +71 -345
  272. package/dist/core/__tests__/inline-memory-refs.test.js +2 -12
  273. package/dist/core/__tests__/memory-resolver-precedence.test.js +12 -15
  274. package/dist/core/__tests__/migration.test.js +10 -0
  275. package/dist/core/__tests__/model-pin-durability.test.js +14 -0
  276. package/dist/core/__tests__/phase4-review-migration.test.d.ts +1 -0
  277. package/dist/core/__tests__/phase4-review-migration.test.js +100 -0
  278. package/dist/core/__tests__/phase4-review-store.test.d.ts +1 -0
  279. package/dist/core/__tests__/phase4-review-store.test.js +143 -0
  280. package/dist/core/__tests__/remote-canvas-target.test.js +2 -2
  281. package/dist/core/__tests__/revive.test.js +22 -53
  282. package/dist/core/__tests__/session-cycles.test.js +48 -18
  283. package/dist/core/__tests__/spawn-no-kickoff.test.js +8 -12
  284. package/dist/core/__tests__/stop-guard.test.js +16 -1
  285. package/dist/core/__tests__/tmux-surface.test.js +21 -25
  286. package/dist/core/asset-root.d.ts +7 -0
  287. package/dist/core/asset-root.js +18 -0
  288. package/dist/core/broker-client/client.d.ts +0 -7
  289. package/dist/core/broker-client/client.js +4 -10
  290. package/dist/core/broker-client/transport-relay.d.ts +3 -4
  291. package/dist/core/broker-client/transport-relay.js +3 -4
  292. package/dist/core/canvas/__tests__/attention.test.js +1 -1
  293. package/dist/core/canvas/__tests__/remote-canvas-source.test.js +1 -1
  294. package/dist/core/canvas/__tests__/remote-transport.test.d.ts +1 -0
  295. package/dist/core/{view/__tests__/transport-remote.test.js → canvas/__tests__/remote-transport.test.js} +2 -2
  296. package/dist/core/canvas/__tests__/render-remote.test.js +1 -1
  297. package/dist/core/canvas/attention.js +4 -4
  298. package/dist/core/canvas/canvas.d.ts +6 -11
  299. package/dist/core/canvas/canvas.js +11 -33
  300. package/dist/core/canvas/db.js +207 -38
  301. package/dist/core/canvas/extensions.d.ts +1 -1
  302. package/dist/core/canvas/extensions.js +8 -1
  303. package/dist/core/canvas/index.d.ts +0 -1
  304. package/dist/core/canvas/index.js +0 -1
  305. package/dist/core/canvas/nav-model.d.ts +1 -1
  306. package/dist/core/canvas/nav-model.js +1 -1
  307. package/dist/core/canvas/paths.d.ts +5 -0
  308. package/dist/core/canvas/paths.js +16 -0
  309. package/dist/core/canvas/remote-canvas-source.js +2 -2
  310. package/dist/core/canvas/remote-transport.d.ts +7 -0
  311. package/dist/core/{view/transport-remote.js → canvas/remote-transport.js} +3 -4
  312. package/dist/core/canvas/source.js +1 -1
  313. package/dist/core/canvas/status-glyph.js +4 -12
  314. package/dist/core/canvas/types.d.ts +10 -35
  315. package/dist/core/clipboard-text.d.ts +8 -0
  316. package/dist/core/clipboard-text.js +18 -0
  317. package/dist/core/command-manifests/registry.d.ts +1 -3
  318. package/dist/core/command-plugins/bundle.d.ts +29 -0
  319. package/dist/core/command-plugins/bundle.js +198 -0
  320. package/dist/core/command-plugins/discovery.d.ts +0 -8
  321. package/dist/core/command-plugins/discovery.js +2 -39
  322. package/dist/core/command-plugins/transport/http-fetch.d.ts +4 -30
  323. package/dist/core/command-plugins/transport/http-fetch.js +17 -95
  324. package/dist/core/command.d.ts +1 -13
  325. package/dist/core/command.js +1 -16
  326. package/dist/core/config.d.ts +4 -0
  327. package/dist/core/config.js +28 -3
  328. package/dist/core/document-lines.d.ts +4 -0
  329. package/dist/core/document-lines.js +6 -0
  330. package/dist/core/feed/feed.d.ts +2 -0
  331. package/dist/core/feed/feed.js +11 -7
  332. package/dist/core/human/__tests__/inbox-cancel-worker.d.ts +1 -0
  333. package/dist/core/human/__tests__/inbox-cancel-worker.js +7 -0
  334. package/dist/core/human/__tests__/inbox-claim-worker.d.ts +1 -0
  335. package/dist/core/human/__tests__/inbox-claim-worker.js +17 -0
  336. package/dist/core/human/__tests__/inbox-core.test.d.ts +1 -0
  337. package/dist/core/human/__tests__/inbox-core.test.js +144 -0
  338. package/dist/core/human/__tests__/inbox-stale-claim-worker.d.ts +1 -0
  339. package/dist/core/human/__tests__/inbox-stale-claim-worker.js +8 -0
  340. package/dist/core/human/__tests__/visible-paths.test.d.ts +1 -0
  341. package/dist/{clients/attach → core/human}/__tests__/visible-paths.test.js +9 -0
  342. package/dist/core/human/claim.d.ts +23 -0
  343. package/dist/core/human/claim.js +67 -0
  344. package/dist/core/human/convention.d.ts +30 -0
  345. package/dist/core/human/convention.js +202 -0
  346. package/dist/core/human/deck-factories.d.ts +8 -0
  347. package/dist/core/human/deck-factories.js +15 -0
  348. package/dist/core/human/deck-schema.d.ts +70 -0
  349. package/dist/core/human/deck-schema.js +92 -0
  350. package/dist/core/human/root.d.ts +7 -0
  351. package/dist/core/human/root.js +37 -0
  352. package/dist/core/human/scan.d.ts +3 -0
  353. package/dist/core/human/scan.js +76 -0
  354. package/dist/core/human/summary.d.ts +8 -0
  355. package/dist/core/human/summary.js +44 -0
  356. package/dist/core/human/tickets.d.ts +49 -0
  357. package/dist/core/human/tickets.js +190 -0
  358. package/dist/core/human/types.d.ts +136 -0
  359. package/dist/core/human/types.js +5 -0
  360. package/dist/{clients/attach → core/human}/visible-paths.d.ts +1 -1
  361. package/dist/{clients/attach → core/human}/visible-paths.js +3 -2
  362. package/dist/core/inspector/chrome.d.ts +6 -0
  363. package/dist/core/inspector/chrome.js +14 -0
  364. package/dist/core/inspector/contract.d.ts +105 -0
  365. package/dist/core/inspector/contract.js +1 -0
  366. package/dist/core/inspector/core.d.ts +4 -5
  367. package/dist/core/inspector/core.js +3 -5
  368. package/dist/core/inspector/host.d.ts +46 -0
  369. package/dist/core/{tui → inspector}/host.js +42 -208
  370. package/dist/core/inspector/text.d.ts +2 -2
  371. package/dist/core/inspector/transport.d.ts +6 -0
  372. package/dist/core/inspector/transport.js +36 -0
  373. package/dist/core/inspector/tui.d.ts +2 -2
  374. package/dist/core/inspector/tui.js +15 -15
  375. package/dist/core/keybindings/__tests__/inbox-affordance.test.js +11 -11
  376. package/dist/core/keybindings/__tests__/resolve.test.js +10 -19
  377. package/dist/core/keybindings/attach-control.d.ts +9 -3
  378. package/dist/core/keybindings/attach-control.js +3 -1
  379. package/dist/core/keybindings/catalog.d.ts +6 -5
  380. package/dist/core/keybindings/catalog.js +61 -133
  381. package/dist/core/keybindings/inbox.d.ts +5 -6
  382. package/dist/core/keybindings/inbox.js +9 -10
  383. package/dist/core/keybindings/index.d.ts +3 -3
  384. package/dist/core/keybindings/index.js +3 -3
  385. package/dist/core/keybindings/persistence.d.ts +5 -1
  386. package/dist/core/keybindings/persistence.js +24 -6
  387. package/dist/core/keybindings/resolve.d.ts +6 -0
  388. package/dist/core/keybindings/resolve.js +10 -3
  389. package/dist/core/memory/inline-ref-grammar.js +2 -3
  390. package/dist/core/memory-resolver.d.ts +27 -29
  391. package/dist/core/memory-resolver.js +45 -39
  392. package/dist/core/model-routes.d.ts +33 -0
  393. package/dist/core/model-routes.js +42 -3
  394. package/dist/core/preview-registry.js +17 -55
  395. package/dist/core/profiles/select.js +3 -3
  396. package/dist/core/{view/remote-canvas-target.d.ts → remote-canvas-target.d.ts} +2 -2
  397. package/dist/core/{view/remote-canvas-target.js → remote-canvas-target.js} +4 -4
  398. package/dist/core/review/__tests__/capture-origin.test.d.ts +1 -0
  399. package/dist/core/review/__tests__/capture-origin.test.js +139 -0
  400. package/dist/core/review/__tests__/comments.test.d.ts +1 -0
  401. package/dist/core/review/__tests__/comments.test.js +98 -0
  402. package/dist/core/review/__tests__/project.test.d.ts +1 -0
  403. package/dist/core/review/__tests__/project.test.js +71 -0
  404. package/dist/core/review/__tests__/remap.test.d.ts +1 -0
  405. package/dist/core/review/__tests__/remap.test.js +159 -0
  406. package/dist/core/review/__tests__/stage-identity.test.d.ts +1 -0
  407. package/dist/core/review/__tests__/stage-identity.test.js +140 -0
  408. package/dist/core/review/birth.d.ts +7 -0
  409. package/dist/core/review/birth.js +23 -0
  410. package/dist/core/review/comments.d.ts +69 -0
  411. package/dist/core/review/comments.js +295 -0
  412. package/dist/core/review/companion.d.ts +35 -0
  413. package/dist/core/review/companion.js +205 -0
  414. package/dist/core/review/document.d.ts +27 -0
  415. package/dist/core/review/document.js +138 -0
  416. package/dist/core/review/project.d.ts +13 -0
  417. package/dist/core/review/project.js +58 -0
  418. package/dist/core/review/realize.d.ts +19 -0
  419. package/dist/core/review/realize.js +217 -0
  420. package/dist/core/review/remap.d.ts +24 -0
  421. package/dist/core/review/remap.js +156 -0
  422. package/dist/core/review/signal.d.ts +5 -0
  423. package/dist/core/review/signal.js +31 -0
  424. package/dist/core/review/stage.d.ts +26 -0
  425. package/dist/core/review/stage.js +184 -0
  426. package/dist/core/review/store.d.ts +46 -0
  427. package/dist/core/review/store.js +163 -0
  428. package/dist/core/review/ticket-filter.d.ts +17 -0
  429. package/dist/core/review/ticket-filter.js +32 -0
  430. package/dist/core/review/types.d.ts +130 -0
  431. package/dist/core/review/types.js +23 -0
  432. package/dist/core/runtime/__tests__/session-visibility.test.d.ts +1 -0
  433. package/dist/core/runtime/__tests__/session-visibility.test.js +119 -0
  434. package/dist/core/runtime/bearings.d.ts +5 -17
  435. package/dist/core/runtime/bearings.js +88 -71
  436. package/dist/core/runtime/broker-protocol.d.ts +31 -8
  437. package/dist/core/runtime/broker.d.ts +15 -1
  438. package/dist/core/runtime/broker.js +573 -141
  439. package/dist/core/runtime/canvas-extensions.d.ts +4 -5
  440. package/dist/core/runtime/canvas-extensions.js +5 -5
  441. package/dist/core/runtime/front-door.js +4 -4
  442. package/dist/core/runtime/headless-pi.d.ts +24 -0
  443. package/dist/core/runtime/headless-pi.js +152 -0
  444. package/dist/core/runtime/kickoff.d.ts +1 -5
  445. package/dist/core/runtime/kickoff.js +1 -5
  446. package/dist/core/runtime/model-registry.d.ts +3 -0
  447. package/dist/core/runtime/model-registry.js +13 -0
  448. package/dist/core/runtime/naming-persist.d.ts +4 -0
  449. package/dist/core/runtime/naming-persist.js +27 -1
  450. package/dist/core/runtime/naming.d.ts +17 -12
  451. package/dist/core/runtime/naming.js +91 -140
  452. package/dist/core/runtime/node-read.js +19 -10
  453. package/dist/core/runtime/nodes.d.ts +9 -14
  454. package/dist/core/runtime/nodes.js +3 -53
  455. package/dist/core/runtime/package-health.d.ts +6 -21
  456. package/dist/core/runtime/package-health.js +12 -123
  457. package/dist/core/runtime/placement-tmux.d.ts +2 -1
  458. package/dist/core/runtime/placement-tmux.js +6 -4
  459. package/dist/core/runtime/placement.d.ts +1 -0
  460. package/dist/core/runtime/placement.js +4 -8
  461. package/dist/core/runtime/promote.js +3 -0
  462. package/dist/core/runtime/recap.d.ts +2 -7
  463. package/dist/core/runtime/recap.js +35 -69
  464. package/dist/core/runtime/revive.js +34 -77
  465. package/dist/core/runtime/session-cycles.d.ts +4 -0
  466. package/dist/core/runtime/session-cycles.js +14 -1
  467. package/dist/core/runtime/session-visibility.d.ts +37 -0
  468. package/dist/core/runtime/session-visibility.js +83 -0
  469. package/dist/core/runtime/spawn.js +10 -22
  470. package/dist/core/runtime/stop-guard.d.ts +1 -1
  471. package/dist/core/runtime/stop-guard.js +18 -13
  472. package/dist/core/runtime/surface-bg.js +1 -1
  473. package/dist/core/runtime/tmux-chrome.d.ts +1 -1
  474. package/dist/core/runtime/tmux-chrome.js +1 -1
  475. package/dist/core/runtime/tmux.d.ts +35 -7
  476. package/dist/core/runtime/tmux.js +439 -62
  477. package/dist/core/runtime/tool-group-summary.d.ts +19 -0
  478. package/dist/core/runtime/tool-group-summary.js +88 -0
  479. package/dist/core/scope.d.ts +2 -7
  480. package/dist/core/scope.js +9 -35
  481. package/dist/core/substrate/schema.d.ts +2 -2
  482. package/dist/core/termrender/code-doc.d.ts +12 -0
  483. package/dist/core/termrender/code-doc.js +92 -0
  484. package/dist/core/termrender/display.d.ts +12 -0
  485. package/dist/core/termrender/display.js +19 -0
  486. package/dist/core/termrender/termrender.d.ts +73 -0
  487. package/dist/core/termrender/termrender.js +795 -0
  488. package/dist/core/termrender/version.d.ts +1 -0
  489. package/dist/core/termrender/version.js +1 -0
  490. package/dist/core/tui/terminal.js +1 -2
  491. package/dist/core/user-settings.d.ts +71 -3
  492. package/dist/core/user-settings.js +83 -1
  493. package/dist/daemon/api/__tests__/inbox.test.js +44 -133
  494. package/dist/daemon/api/handlers/human.d.ts +0 -10
  495. package/dist/daemon/api/handlers/human.js +104 -702
  496. package/dist/daemon/api/handlers/inbox.js +113 -142
  497. package/dist/daemon/api/handlers/review-comments.d.ts +2 -0
  498. package/dist/daemon/api/handlers/review-comments.js +500 -0
  499. package/dist/daemon/api/handlers/reviews.d.ts +2 -0
  500. package/dist/daemon/api/handlers/reviews.js +287 -0
  501. package/dist/daemon/api/map.d.ts +22 -7
  502. package/dist/daemon/api/map.js +159 -11
  503. package/dist/daemon/api/server.js +4 -0
  504. package/dist/daemon/cron-run.js +6 -9
  505. package/dist/daemon/crtrd.js +17 -33
  506. package/dist/daemon/fleet.d.ts +1 -8
  507. package/dist/daemon/fleet.js +4 -30
  508. package/dist/daemon/human/finish.d.ts +14 -0
  509. package/dist/daemon/human/finish.js +115 -0
  510. package/dist/daemon/human/sweep.d.ts +6 -0
  511. package/dist/daemon/human/sweep.js +49 -0
  512. package/dist/daemon/messaging/node-message.d.ts +18 -0
  513. package/dist/daemon/messaging/node-message.js +70 -0
  514. package/dist/daemon/review/comment-notify.d.ts +26 -0
  515. package/dist/daemon/review/comment-notify.js +107 -0
  516. package/dist/daemon/review/deliver.d.ts +7 -0
  517. package/dist/daemon/review/deliver.js +56 -0
  518. package/dist/daemon/review/finish.d.ts +22 -0
  519. package/dist/daemon/review/finish.js +254 -0
  520. package/dist/daemon/review/sweep.d.ts +11 -0
  521. package/dist/daemon/review/sweep.js +120 -0
  522. package/dist/index.d.ts +2 -0
  523. package/dist/index.js +2 -2
  524. package/dist/pi-extensions/__tests__/canvas-context-intro.test.js +42 -64
  525. package/dist/pi-extensions/__tests__/canvas-recap.test.d.ts +1 -0
  526. package/dist/pi-extensions/__tests__/canvas-recap.test.js +159 -0
  527. package/dist/pi-extensions/__tests__/canvas-stophook-agentend.test.js +16 -38
  528. package/dist/pi-extensions/__tests__/canvas-structured-output.test.d.ts +1 -0
  529. package/dist/pi-extensions/__tests__/canvas-structured-output.test.js +63 -0
  530. package/dist/pi-extensions/canvas-context-intro.d.ts +8 -11
  531. package/dist/pi-extensions/canvas-context-intro.js +50 -104
  532. package/dist/pi-extensions/canvas-recap.d.ts +15 -2
  533. package/dist/pi-extensions/canvas-recap.js +192 -69
  534. package/dist/pi-extensions/canvas-review-boundary.d.ts +59 -0
  535. package/dist/pi-extensions/canvas-review-boundary.js +81 -0
  536. package/dist/pi-extensions/canvas-stophook.d.ts +3 -3
  537. package/dist/pi-extensions/canvas-stophook.js +78 -128
  538. package/dist/pi-extensions/canvas-structured-output.js +52 -8
  539. package/dist/shared/generated-context.d.ts +29 -4
  540. package/dist/shared/generated-context.js +122 -7
  541. package/dist/shared/tool-groups.d.ts +23 -0
  542. package/dist/shared/tool-groups.js +60 -0
  543. package/dist/shared/working-activity.d.ts +8 -0
  544. package/dist/shared/working-activity.js +26 -0
  545. package/dist/types.d.ts +36 -2
  546. package/dist/types.js +51 -0
  547. package/dist/web-client/assets/index-CCPaLeW7.css +2 -0
  548. package/dist/web-client/assets/index-kyOWsnZK.js +86 -0
  549. package/dist/web-client/index.html +2 -2
  550. package/dist/web-client/sw.js +1 -1
  551. package/docs/compat/hearth-crtr-v4.md +2 -2
  552. package/docs/compat/hearth-crtr-v5.md +4 -2
  553. package/docs/public-api.md +3 -3
  554. package/package.json +21 -14
  555. package/runtime.lock.json +4661 -1453
  556. package/scripts/postinstall.mjs +15 -2
  557. package/dist/builtin-pi-packages/pi-mode-switch/README.md +0 -36
  558. package/dist/builtin-pi-packages/pi-mode-switch/bin/mode +0 -36
  559. package/dist/builtin-pi-packages/pi-mode-switch/extensions/index.ts +0 -449
  560. package/dist/builtin-pi-packages/pi-mode-switch/package.json +0 -14
  561. package/dist/builtin-pi-packages/pi-mode-switch/tsconfig.json +0 -9
  562. package/dist/builtin-views/_lib/states.mjs +0 -161
  563. package/dist/builtin-views/canvas/core.mjs +0 -657
  564. package/dist/builtin-views/canvas/text.mjs +0 -58
  565. package/dist/builtin-views/canvas/tui.mjs +0 -168
  566. package/dist/builtin-views/canvas/web.jsx +0 -121
  567. package/dist/builtin-views/chat/core.mjs +0 -684
  568. package/dist/builtin-views/chat/text.mjs +0 -101
  569. package/dist/builtin-views/chat/tui.mjs +0 -360
  570. package/dist/builtin-views/chat/web.jsx +0 -362
  571. package/dist/builtin-views/git-pr/core.mjs +0 -673
  572. package/dist/builtin-views/git-pr/text.mjs +0 -84
  573. package/dist/builtin-views/git-pr/tui.mjs +0 -301
  574. package/dist/builtin-views/git-pr/web.jsx +0 -216
  575. package/dist/builtin-views/inbox/_lib/render.mjs +0 -175
  576. package/dist/builtin-views/inbox/core.mjs +0 -1273
  577. package/dist/builtin-views/inbox/text.mjs +0 -73
  578. package/dist/builtin-views/inbox/tui.mjs +0 -314
  579. package/dist/builtin-views/inbox/web.jsx +0 -188
  580. package/dist/builtin-views/linkedin/core.mjs +0 -906
  581. package/dist/builtin-views/linkedin/text.mjs +0 -69
  582. package/dist/builtin-views/linkedin/tui.mjs +0 -413
  583. package/dist/builtin-views/linkedin/web.jsx +0 -206
  584. package/dist/builtin-views/prompt-review/core.mjs +0 -735
  585. package/dist/builtin-views/prompt-review/text.mjs +0 -15
  586. package/dist/builtin-views/prompt-review/tui.mjs +0 -196
  587. package/dist/builtin-views/prompt-review/web.jsx +0 -484
  588. package/dist/builtin-views/workspace-sidebar/__tests__/core.test.js +0 -50
  589. package/dist/builtin-views/workspace-sidebar/__tests__/core.test.ts +0 -53
  590. package/dist/builtin-views/workspace-sidebar/core.mjs +0 -670
  591. package/dist/builtin-views/workspace-sidebar/text.mjs +0 -53
  592. package/dist/builtin-views/workspace-sidebar/tui.mjs +0 -141
  593. package/dist/builtin-views/workspace-sidebar/web.jsx +0 -109
  594. package/dist/clients/attach/session/mode.d.ts +0 -25
  595. package/dist/clients/attach/session/mode.js +0 -97
  596. package/dist/commands/sys/promptstudio.d.ts +0 -2
  597. package/dist/commands/sys/promptstudio.js +0 -65
  598. package/dist/commands/view-cycle.d.ts +0 -2
  599. package/dist/commands/view-cycle.js +0 -130
  600. package/dist/commands/view-list.d.ts +0 -2
  601. package/dist/commands/view-list.js +0 -66
  602. package/dist/commands/view-new.d.ts +0 -2
  603. package/dist/commands/view-new.js +0 -74
  604. package/dist/commands/view-pick.d.ts +0 -6
  605. package/dist/commands/view-pick.js +0 -129
  606. package/dist/commands/view-run.d.ts +0 -2
  607. package/dist/commands/view-run.js +0 -214
  608. package/dist/commands/view.d.ts +0 -2
  609. package/dist/commands/view.js +0 -31
  610. package/dist/commands/workspace.d.ts +0 -2
  611. package/dist/commands/workspace.js +0 -165
  612. package/dist/core/__tests__/chat-view-reconnect.test.js +0 -105
  613. package/dist/core/__tests__/full/consult-startup-failure.test.js +0 -124
  614. package/dist/core/canvas/__tests__/human-work-outbox.test.js +0 -123
  615. package/dist/core/canvas/human-work-outbox.d.ts +0 -73
  616. package/dist/core/canvas/human-work-outbox.js +0 -261
  617. package/dist/core/command-plugins/store.d.ts +0 -16
  618. package/dist/core/command-plugins/store.js +0 -64
  619. package/dist/core/keybindings/__tests__/bespoke-consumers.test.js +0 -40
  620. package/dist/core/runtime/fault-recovery-nudge.d.ts +0 -3
  621. package/dist/core/runtime/fault-recovery-nudge.js +0 -3
  622. package/dist/core/tui/__tests__/host-keybindings.test.js +0 -113
  623. package/dist/core/tui/host.d.ts +0 -66
  624. package/dist/core/view/__tests__/transport-cache.test.js +0 -62
  625. package/dist/core/view/bridge.d.ts +0 -10
  626. package/dist/core/view/bridge.js +0 -31
  627. package/dist/core/view/chrome.d.ts +0 -9
  628. package/dist/core/view/chrome.js +0 -22
  629. package/dist/core/view/contract.d.ts +0 -205
  630. package/dist/core/view/contract.js +0 -23
  631. package/dist/core/view/loader.d.ts +0 -31
  632. package/dist/core/view/loader.js +0 -188
  633. package/dist/core/view/stream-local.d.ts +0 -3
  634. package/dist/core/view/stream-local.js +0 -231
  635. package/dist/core/view/transport-cache.d.ts +0 -8
  636. package/dist/core/view/transport-cache.js +0 -38
  637. package/dist/core/view/transport-local.d.ts +0 -7
  638. package/dist/core/view/transport-local.js +0 -83
  639. package/dist/core/view/transport-remote.d.ts +0 -2
  640. package/dist/core/view/transport.d.ts +0 -10
  641. package/dist/core/view/transport.js +0 -15
  642. package/dist/pi-extensions/naming-tool.d.ts +0 -24
  643. package/dist/pi-extensions/naming-tool.js +0 -67
  644. package/dist/prompts/view.d.ts +0 -7
  645. package/dist/prompts/view.js +0 -172
  646. package/dist/web/ViewChrome.d.ts +0 -7
  647. package/dist/web/ViewChrome.js +0 -28
  648. package/dist/web/ViewPane.d.ts +0 -39
  649. package/dist/web/ViewPane.js +0 -48
  650. package/dist/web/index.d.ts +0 -8
  651. package/dist/web/index.js +0 -22
  652. package/dist/web/runtime.d.ts +0 -39
  653. package/dist/web/runtime.js +0 -232
  654. package/dist/web/states.d.ts +0 -24
  655. package/dist/web/states.js +0 -24
  656. package/dist/web/transport-http.d.ts +0 -5
  657. package/dist/web/transport-http.js +0 -34
  658. package/dist/web/transport-stream.d.ts +0 -3
  659. package/dist/web/transport-stream.js +0 -204
  660. package/dist/web-client/assets/index-U_NZ66VE.js +0 -79
  661. package/dist/web-client/assets/index-aaeu4adv.css +0 -2
  662. /package/dist/{builtin-views/workspace-sidebar/__tests__/core.test.d.ts → clients/attach/__tests__/file-review-focus.test.d.ts} +0 -0
  663. /package/dist/clients/attach/__tests__/{visible-paths.test.d.ts → group-activity.test.d.ts} +0 -0
  664. /package/dist/{core/__tests__/chat-view-reconnect.test.d.ts → clients/attach/__tests__/input-controller-extension-command.test.d.ts} +0 -0
  665. /package/dist/{core/__tests__/full/consult-startup-failure.test.d.ts → clients/attach/__tests__/session-identity-refresh.test.d.ts} +0 -0
  666. /package/dist/{core/canvas/__tests__/human-work-outbox.test.d.ts → clients/inbox/__tests__/inbox-controller.test.d.ts} +0 -0
  667. /package/dist/{core/keybindings/__tests__/bespoke-consumers.test.d.ts → clients/inbox/__tests__/mount-panel.test.d.ts} +0 -0
  668. /package/dist/{core/tui/__tests__/host-keybindings.test.d.ts → clients/inbox/review/__tests__/editor-roundtrip.test.d.ts} +0 -0
  669. /package/dist/{core/view/__tests__/transport-cache.test.d.ts → clients/inbox/review/__tests__/remap.test.d.ts} +0 -0
  670. /package/dist/{core/view/__tests__/transport-remote.test.d.ts → clients/inbox/review/__tests__/review-core.test.d.ts} +0 -0
@@ -59,15 +59,32 @@ const TRANSIENT_ROTATE_COOLDOWN_MS = 60 * 1000;
59
59
  // healthy model response into a fatal generation fault.
60
60
  const SUBSCRIPTION_LOCK_RETRY_BACKOFFS_MS = [25, 75, 150];
61
61
 
62
- let runtimeContext: ExtensionContext | undefined;
63
- let extensionAPI: ExtensionAPI | undefined;
64
-
65
62
  // A declared non-managed provider is re-registered with the route driver at
66
63
  // session start, after its owning extension has registered its real transport.
67
64
  // Keep that composed transport here: calling the registry after our wrapper is
68
65
  // installed would recurse back into the route driver.
69
66
  type ProviderStream = (model: Model<any>, context: Context, options?: SimpleStreamOptions) => AssistantMessageEventStream;
70
- const delegatedProviderStreams = new Map<string, ProviderStream>();
67
+
68
+ export interface ProviderRotationOptions {
69
+ modelIntent?: string;
70
+ nodeCwd?: string;
71
+ profileId?: string;
72
+ }
73
+
74
+ type ProviderRotationState = {
75
+ runtimeContext?: ExtensionContext;
76
+ extensionAPI?: ExtensionAPI;
77
+ delegatedProviderStreams: Map<string, ProviderStream>;
78
+ options?: Readonly<ProviderRotationOptions>;
79
+ };
80
+
81
+ function createProviderRotationState(options?: ProviderRotationOptions): ProviderRotationState {
82
+ return { delegatedProviderStreams: new Map(), ...(options === undefined ? {} : { options: { ...options } }) };
83
+ }
84
+
85
+ function configuredValue(state: ProviderRotationState, key: keyof ProviderRotationOptions, envName: string): string | undefined {
86
+ return state.options === undefined ? process.env[envName] : state.options[key];
87
+ }
71
88
 
72
89
  type AttemptResult = {
73
90
  result: "success" | "rate_limited" | "transient_exhausted" | "fatal";
@@ -75,6 +92,7 @@ type AttemptResult = {
75
92
  attemptAt: number;
76
93
  attempts?: number;
77
94
  classification?: ProviderErrorClassification;
95
+ failureMessage?: string;
78
96
  };
79
97
 
80
98
  // A genuine rate limit (HTTP 429, or 503 with a real Retry-After header) must
@@ -126,6 +144,18 @@ function classifyProviderError(error: unknown, observed: { status?: number; retr
126
144
  return { kind: "fatal", status, reason: "unclassified", hadRetryAfterHeader };
127
145
  }
128
146
 
147
+ function providerErrorMessage(error: unknown): string | undefined {
148
+ const value = error as { errorMessage?: unknown; message?: unknown } | undefined;
149
+ const message = typeof value?.errorMessage === "string" && value.errorMessage.trim()
150
+ ? value.errorMessage.trim()
151
+ : typeof value?.message === "string" && value.message.trim()
152
+ ? value.message.trim()
153
+ : error instanceof Error && error.message.trim()
154
+ ? error.message.trim()
155
+ : undefined;
156
+ return message;
157
+ }
158
+
129
159
  async function defaultSleep(ms: number): Promise<void> {
130
160
  await new Promise<void>((resolve) => setTimeout(resolve, ms));
131
161
  }
@@ -186,8 +216,8 @@ function isUserAbortError(error: unknown, signal: AbortSignal | undefined): bool
186
216
  return (error instanceof Error && /abort/i.test(error.name)) || /request was aborted|operation was aborted|the user aborted/i.test(message);
187
217
  }
188
218
 
189
- function setStatus(text: string): void {
190
- runtimeContext?.ui.setStatus("provider-rotation", text);
219
+ function setStatus(state: ProviderRotationState, text: string): void {
220
+ state.runtimeContext?.ui.setStatus("provider-rotation", text);
191
221
  }
192
222
 
193
223
  function logRotation(message: string): void {
@@ -251,24 +281,26 @@ type ResolvedCredential = SubscriptionCredential & { access: string };
251
281
 
252
282
  // The registered provider exposes its OAuth flow through `provider.auth.oauth`. Composed managed
253
283
  // providers retain that flow, so the session registry supplies the Anthropic and Codex flows.
254
- function oauthFlow(providerId: ManagedProviderId, ctx?: ExtensionContext): OAuthAuth {
255
- const oauth = (ctx ?? runtimeContext)?.modelRegistry.getProvider(providerId)?.auth.oauth;
284
+ function oauthFlow(providerId: ManagedProviderId, ctx: ExtensionContext | undefined): OAuthAuth {
285
+ const oauth = ctx?.modelRegistry.getProvider(providerId)?.auth.oauth;
256
286
  if (!oauth) throw new Error(`${getProviderLabel(providerId)} has no OAuth flow registered`);
257
287
  return oauth;
258
288
  }
259
289
 
260
290
  // `refresh` is a pure token exchange that reads only `credential.refresh` and persists
261
291
  // nothing; crouter records the rotated token in the selected pool row.
262
- async function defaultRefreshForProvider(providerId: ManagedProviderId, refreshToken: string): Promise<RefreshedCredential> {
263
- return oauthFlow(providerId).refresh({ type: "oauth", refresh: refreshToken, access: "", expires: 0 });
292
+ async function defaultRefreshForProvider(state: ProviderRotationState, providerId: ManagedProviderId, refreshToken: string): Promise<RefreshedCredential> {
293
+ return oauthFlow(providerId, state.runtimeContext).refresh({ type: "oauth", refresh: refreshToken, access: "", expires: 0 });
264
294
  }
265
295
 
296
+ type RefreshForProvider = (providerId: ManagedProviderId, refreshToken: string) => Promise<RefreshedCredential>;
297
+
266
298
  // Indirection so tests can inject refresh failures without real network calls.
267
- let activeRefreshForProvider: typeof defaultRefreshForProvider = defaultRefreshForProvider;
299
+ let activeRefreshForProvider: RefreshForProvider | undefined;
268
300
 
269
301
  /** Test-only seam: override the per-provider refresh function. Pass undefined to restore the default. */
270
- export function __setRefreshForProviderForTest(fn: typeof defaultRefreshForProvider | undefined): void {
271
- activeRefreshForProvider = fn ?? defaultRefreshForProvider;
302
+ export function __setRefreshForProviderForTest(fn: RefreshForProvider | undefined): void {
303
+ activeRefreshForProvider = fn;
272
304
  }
273
305
 
274
306
  function openBrowser(url: string): void {
@@ -295,19 +327,21 @@ function openBrowser(url: string): void {
295
327
  // `getApiKey`, which wraps pi-ai's `getOAuthApiKey` and collapses a refresh failure into a
296
328
  // generic message; calling `activeRefreshForProvider` directly keeps the real error intact so
297
329
  // `isInvalidRefreshTokenError` can classify it (cooldown -> reauth -> cross-provider-fallback).
298
- async function refreshCredentialIfNeeded(providerId: ManagedProviderId, credential: SubscriptionCredential): Promise<ResolvedCredential> {
330
+ async function refreshCredentialIfNeeded(state: ProviderRotationState, providerId: ManagedProviderId, credential: SubscriptionCredential): Promise<ResolvedCredential> {
299
331
  if (!credential.refresh || !credential.access || credential.expires === undefined) {
300
332
  throw new Error(`${getProviderLabel(providerId)} subscription "${credential.label}" is missing its credential value`);
301
333
  }
302
334
  if (credential.expires > Date.now()) return credential as ResolvedCredential;
303
- const refreshed = await refreshSubscriptionCredential(providerId, credential.label, (token) => activeRefreshForProvider(providerId, token));
335
+ const refreshed = await refreshSubscriptionCredential(providerId, credential.label, (token) =>
336
+ activeRefreshForProvider?.(providerId, token) ?? defaultRefreshForProvider(state, providerId, token),
337
+ );
304
338
  return { ...credential, ...refreshed } as ResolvedCredential;
305
339
  }
306
340
 
307
341
  /** Test-only seam: exercises the real `refreshCredentialIfNeeded` (including its locked refresh
308
342
  * transaction) without duplicating its logic. */
309
343
  export async function __refreshCredentialIfNeededForTest(providerId: ManagedProviderId, credential: SubscriptionCredential): Promise<ResolvedCredential> {
310
- return refreshCredentialIfNeeded(providerId, credential);
344
+ return refreshCredentialIfNeeded(createProviderRotationState(), providerId, credential);
311
345
  }
312
346
 
313
347
  async function defaultLoginForProvider(providerId: ManagedProviderId, ctx: ExtensionContext): Promise<LoginCredential> {
@@ -321,8 +355,8 @@ export function __setLoginForProviderForTest(fn: typeof defaultLoginForProvider
321
355
  activeLoginForProvider = fn ?? defaultLoginForProvider;
322
356
  }
323
357
 
324
- async function reauthenticateCredential(providerId: ManagedProviderId, credential: SubscriptionCredential): Promise<ResolvedCredential | undefined> {
325
- const ctx = runtimeContext;
358
+ async function reauthenticateCredential(state: ProviderRotationState, providerId: ManagedProviderId, credential: SubscriptionCredential): Promise<ResolvedCredential | undefined> {
359
+ const ctx = state.runtimeContext;
326
360
  if (!ctx?.hasUI) return undefined;
327
361
  ctx.ui.notify(`${getProviderLabel(providerId)} subscription "${credential.label}" needs re-authentication. Opening OAuth login...`, "warning");
328
362
  const authenticated = await activeLoginForProvider(providerId, ctx);
@@ -375,6 +409,7 @@ export function __setStreamForProviderForTest(fn: typeof defaultStreamForProvide
375
409
  // A genuine rate limit ends the attempt immediately (the caller cools the credential down
376
410
  // and rotates); a transient blip gets a short in-place retry budget before giving up.
377
411
  async function streamProviderAttempt(
412
+ state: ProviderRotationState,
378
413
  providerId: string,
379
414
  model: Model<any>,
380
415
  context: Context,
@@ -397,10 +432,10 @@ async function streamProviderAttempt(
397
432
  await options?.onResponse?.(response as any, responseModel);
398
433
  },
399
434
  };
400
- const provider = runtimeContext?.modelRegistry.getProvider?.(model.provider);
435
+ const provider = state.runtimeContext?.modelRegistry.getProvider?.(model.provider);
401
436
  const inner = credential
402
437
  ? streamForProvider(model, context, streamOptions)
403
- : delegatedProviderStreams.get(providerId)?.(model, context, streamOptions)
438
+ : state.delegatedProviderStreams.get(providerId)?.(model, context, streamOptions)
404
439
  ?? provider?.streamSimple(model, context, streamOptions)
405
440
  ?? streamForProvider(model, context, streamOptions);
406
441
 
@@ -408,6 +443,7 @@ async function streamProviderAttempt(
408
443
  let sawContent = false;
409
444
  let committed = false;
410
445
  let classification: ProviderErrorClassification | undefined;
446
+ let failureMessage: string | undefined;
411
447
 
412
448
  // Events are buffered ONLY until this attempt commits to the provider -- i.e. until the
413
449
  // first real content event (thinking/text/toolcall) arrives, after which rotating away is
@@ -433,6 +469,7 @@ async function streamProviderAttempt(
433
469
  flush();
434
470
  return { result: "fatal", attemptAt };
435
471
  }
472
+ failureMessage = providerErrorMessage(event.error);
436
473
  classification = classifyProviderError(event.error, { status: observedStatus, retryAfterMs: requestedRetryAfterMs });
437
474
  if (classification.kind === "fatal" || sawContent) {
438
475
  emit(event);
@@ -457,6 +494,7 @@ async function streamProviderAttempt(
457
494
  flush();
458
495
  return { result: "fatal", attemptAt };
459
496
  }
497
+ failureMessage = providerErrorMessage(error);
460
498
  classification = classifyProviderError(error, { status: observedStatus, retryAfterMs: requestedRetryAfterMs });
461
499
  if (classification.kind === "fatal" || sawContent) {
462
500
  emit({
@@ -493,7 +531,7 @@ async function streamProviderAttempt(
493
531
  // to another credential / fallback provider. Nothing is emitted into the stream here:
494
532
  // the buffered (pre-content) events are discarded exactly like the rate-limit path, so
495
533
  // the rotation target streams a clean turn.
496
- return { result: "transient_exhausted", attemptAt, classification, attempts: attempt };
534
+ return { result: "transient_exhausted", attemptAt, classification, attempts: attempt, failureMessage };
497
535
  }
498
536
  logRotation(
499
537
  `${credential ? `${getProviderLabel(providerId as ManagedProviderId)} subscription "${credential.label}"` : providerId} hit a transient error (${classification.reason}, status=${classification.status ?? "n/a"}); retrying same route in ${backoffMs}ms (attempt ${attempt + 1})`,
@@ -560,20 +598,29 @@ function logRouteQueueAdvance(from: RouteCandidate, to: RouteCandidate | undefin
560
598
  // Drive the deterministic route queue from the live rank. Managed routes walk
561
599
  // their pool rows; every other declared route is one configured credential.
562
600
  async function runManagedProvider(
601
+ state: ProviderRotationState,
563
602
  model: Model<any>,
564
603
  context: Context,
565
604
  options: SimpleStreamOptions | undefined,
566
605
  stream: AssistantMessageEventStream,
567
606
  ): Promise<void> {
568
- const liveThinking = runtimeContext?.thinkingLevel === undefined || runtimeContext?.thinkingLevel === 'off'
607
+ const liveThinking = state.runtimeContext?.thinkingLevel === undefined || state.runtimeContext?.thinkingLevel === 'off'
569
608
  ? undefined
570
- : runtimeContext.thinkingLevel;
571
- const request = modelRequestFromConfig(`${model.provider}/${model.id}${liveThinking ? `:${liveThinking}` : ''}`, process.env['CRTR_MODEL_INTENT'], false);
572
- const expanded = request ? expandModelCandidates(request, process.env['CRTR_NODE_CWD'], process.env['CRTR_PROFILE_ID']) : [];
609
+ : state.runtimeContext.thinkingLevel;
610
+ const request = modelRequestFromConfig(
611
+ `${model.provider}/${model.id}${liveThinking ? `:${liveThinking}` : ''}`,
612
+ configuredValue(state, 'modelIntent', 'CRTR_MODEL_INTENT'),
613
+ false,
614
+ );
615
+ const expanded = request ? expandModelCandidates(
616
+ request,
617
+ configuredValue(state, 'nodeCwd', 'CRTR_NODE_CWD'),
618
+ configuredValue(state, 'profileId', 'CRTR_PROFILE_ID'),
619
+ ) : [];
573
620
  const liveRank = expanded.find((candidate) => candidate.providerId === model.provider && candidate.modelId === model.id && candidate.thinkingLevel === liveThinking)?.rank ?? 0;
574
621
  const routeCandidates: RouteCandidate[] = expanded.slice(liveRank)
575
622
  .map((route) => {
576
- const registry = runtimeContext?.modelRegistry;
623
+ const registry = state.runtimeContext?.modelRegistry;
577
624
  const routeModel = registry?.find(route.providerId, route.modelId);
578
625
  if (!registry || !routeModel) return undefined;
579
626
  const availability = probeRouteAvailability(route, registry);
@@ -598,6 +645,7 @@ async function runManagedProvider(
598
645
  const seenReadyLabels = new Map<string, Set<string>>();
599
646
  const cooldownAttempts = new Map<string, number>();
600
647
  let terminalModel = model;
648
+ let lastTransientFailure: AttemptResult | undefined;
601
649
 
602
650
  for (let candidate = candidates.shift(); candidate !== undefined; candidate = candidates.shift()) {
603
651
  const { routeId, providerId, model: candidateModel } = candidate;
@@ -606,16 +654,17 @@ async function runManagedProvider(
606
654
  attemptedRoutes.add(routeId);
607
655
  terminalModel = candidateModel;
608
656
 
609
- if (!await switchToRoute(extensionAPI!, runtimeContext!, candidate)) {
657
+ if (!await switchToRoute(state.extensionAPI!, state.runtimeContext!, candidate)) {
610
658
  emitTerminalError(stream, candidateModel, `Provider route ${routeId} is unavailable: failed to select ${providerId}/${candidateModel.id}`);
611
659
  return;
612
660
  }
613
661
 
614
662
  if (!isManagedProvider(providerId)) {
615
- const result = await streamProviderAttempt(providerId, candidateModel, context, options, undefined, stream);
663
+ const result = await streamProviderAttempt(state, providerId, candidateModel, context, options, undefined, stream);
616
664
  if (result.result === 'success') { stream.end(); return; }
617
665
  if (result.result === 'fatal') { stream.end(); return; }
618
666
  const cooldownMs = result.result === 'rate_limited' ? result.retryAfterMs ?? DEFAULT_RATE_LIMIT_BACKOFF_MS : TRANSIENT_ROTATE_COOLDOWN_MS;
667
+ if (result.result === 'transient_exhausted') lastTransientFailure = result;
619
668
  localCoolingDeadlines.set(routeId, Math.max(localCoolingDeadlines.get(routeId) ?? 0, Date.now() + cooldownMs));
620
669
  logRouteQueueAdvance(candidate, candidates[0], result);
621
670
  continue;
@@ -632,12 +681,12 @@ async function runManagedProvider(
632
681
  for (const credential of available) {
633
682
  let freshCredential: ResolvedCredential;
634
683
  try {
635
- freshCredential = await refreshCredentialIfNeeded(providerId, credential);
684
+ freshCredential = await refreshCredentialIfNeeded(state, providerId, credential);
636
685
  } catch (error) {
637
686
  if (!isInvalidRefreshTokenError(error)) throw error;
638
687
  let reauthenticated: ResolvedCredential | undefined;
639
- try { reauthenticated = await reauthenticateCredential(providerId, credential); } catch (loginError) {
640
- runtimeContext?.ui.notify(`${getProviderLabel(providerId)} re-authentication failed: ${loginError instanceof Error ? loginError.message : String(loginError)}`, 'error');
688
+ try { reauthenticated = await reauthenticateCredential(state, providerId, credential); } catch (loginError) {
689
+ state.runtimeContext?.ui.notify(`${getProviderLabel(providerId)} re-authentication failed: ${loginError instanceof Error ? loginError.message : String(loginError)}`, 'error');
641
690
  }
642
691
  if (!reauthenticated) {
643
692
  const attemptAt = Date.now();
@@ -647,7 +696,7 @@ async function runManagedProvider(
647
696
  }
648
697
  freshCredential = reauthenticated;
649
698
  }
650
- const result = await streamProviderAttempt(providerId, candidateModel, context, options, freshCredential, stream);
699
+ const result = await streamProviderAttempt(state, providerId, candidateModel, context, options, freshCredential, stream);
651
700
  if (result.result === 'success') {
652
701
  if (freshCredential.rateLimitedUntil || freshCredential.authFailure) await recordSubscriptionMutation(`recording ${getProviderLabel(providerId)} success`, () => markSubscriptionSuccess(providerId, freshCredential.label, result.attemptAt));
653
702
  stream.end();
@@ -655,9 +704,9 @@ async function runManagedProvider(
655
704
  }
656
705
  if (result.result === 'fatal') { stream.end(); return; }
657
706
  const cooldownMs = result.result === 'rate_limited' ? result.retryAfterMs ?? DEFAULT_RATE_LIMIT_BACKOFF_MS : TRANSIENT_ROTATE_COOLDOWN_MS;
658
- if ((result.result === 'rate_limited' && result.retryAfterMs !== undefined) || result.result === 'transient_exhausted') {
659
- const source = result.result === 'transient_exhausted' ? 'transient cooldown' : 'server cooldown';
660
- const persisted = await recordSubscriptionMutation(`recording ${getProviderLabel(providerId)} ${source}`, () => markSubscriptionRateLimited(providerId, freshCredential.label, cooldownMs, result.attemptAt));
707
+ if (result.result === 'transient_exhausted') lastTransientFailure = result;
708
+ if (result.result === 'rate_limited' && result.retryAfterMs !== undefined) {
709
+ const persisted = await recordSubscriptionMutation(`recording ${getProviderLabel(providerId)} server cooldown`, () => markSubscriptionRateLimited(providerId, freshCredential.label, cooldownMs, result.attemptAt));
661
710
  if (persisted) cooldownAttempts.set(`${routeId}/${freshCredential.label}`, result.attemptAt);
662
711
  else localCoolingDeadlines.set(routeId, Math.max(localCoolingDeadlines.get(routeId) ?? 0, Date.now() + cooldownMs));
663
712
  } else {
@@ -672,6 +721,11 @@ async function runManagedProvider(
672
721
 
673
722
  const coolingDeadline = earliestCoolingDeadline([...knownRoutes.values()], localCoolingDeadlines);
674
723
  const tried = [...attemptedRoutes].join(', ');
724
+ if (lastTransientFailure !== undefined) {
725
+ const detail = lastTransientFailure.failureMessage ?? lastTransientFailure.classification?.reason ?? 'unknown connection failure';
726
+ emitTerminalError(stream, terminalModel, `Provider routes failed after transient retries: ${detail} (tried ${tried})`);
727
+ return;
728
+ }
675
729
  if (coolingDeadline !== undefined) {
676
730
  emitCoolingError(stream, terminalModel, new ManagedProviderCoolingError(`All provider routes are rate limited (cooling down ~${Math.ceil((coolingDeadline - Date.now()) / 1000)}s; tried ${tried})`, coolingDeadline));
677
731
  return;
@@ -720,10 +774,10 @@ function emitCoolingError(stream: AssistantMessageEventStream, model: Model<any>
720
774
  stream.end();
721
775
  }
722
776
 
723
- function streamManagedProvider(model: Model<any>, context: Context, options?: SimpleStreamOptions): AssistantMessageEventStream {
777
+ function streamManagedProvider(state: ProviderRotationState, model: Model<any>, context: Context, options?: SimpleStreamOptions): AssistantMessageEventStream {
724
778
  const stream = createAssistantMessageEventStream();
725
779
 
726
- runManagedProvider(model, context, options, stream).catch((error) => {
780
+ runManagedProvider(state, model, context, options, stream).catch((error) => {
727
781
  stream.push({
728
782
  type: "error",
729
783
  reason: "error",
@@ -747,39 +801,43 @@ function parseProvider(value: string | undefined): ManagedProviderId | undefined
747
801
  return isManagedProvider(value) ? value : undefined;
748
802
  }
749
803
 
750
- function installDeclaredRouteProviders(pi: ExtensionAPI, ctx: ExtensionContext): void {
751
- const config = readMergedLaunchConfig(ctx.cwd, process.env['CRTR_PROFILE_ID'] || null);
804
+ function installDeclaredRouteProviders(state: ProviderRotationState, pi: ExtensionAPI, ctx: ExtensionContext): void {
805
+ const config = readMergedLaunchConfig(ctx.cwd, configuredValue(state, 'profileId', 'CRTR_PROFILE_ID') || null);
752
806
  const providerIds = new Set(Object.values(config.modelRoutes ?? {}).map((route) => route.providerId));
753
807
  for (const providerId of providerIds) {
754
- if (isManagedProvider(providerId) || delegatedProviderStreams.has(providerId)) continue;
808
+ if (isManagedProvider(providerId) || state.delegatedProviderStreams.has(providerId)) continue;
755
809
  const provider = ctx.modelRegistry.getProvider?.(providerId);
756
810
  const model = ctx.modelRegistry.getAll?.().find((entry) => entry.provider === providerId);
757
811
  if (!provider || !model) continue;
758
- delegatedProviderStreams.set(providerId, provider.streamSimple.bind(provider));
812
+ state.delegatedProviderStreams.set(providerId, provider.streamSimple.bind(provider));
759
813
  // `api` is required by pi whenever a registration supplies streamSimple.
760
814
  // Re-registration merges this wrapper over the existing declaration, so the
761
815
  // provider's models, auth, and captured composed transport remain intact.
762
- pi.registerProvider(providerId, { api: model.api, streamSimple: streamManagedProvider });
816
+ pi.registerProvider(providerId, { api: model.api, streamSimple: (model, context, options) => streamManagedProvider(state, model, context, options) });
763
817
  }
764
818
  }
765
819
 
766
- function updateRotationStatus(ctx: ExtensionContext): void {
820
+ function updateRotationStatus(state: ProviderRotationState, ctx: ExtensionContext): void {
767
821
  const current = getCurrentModelRef(ctx.model);
768
- if (current && isManagedProvider(current.providerId)) setStatus(formatStatusLine(current.providerId));
822
+ if (current && isManagedProvider(current.providerId)) setStatus(state, formatStatusLine(current.providerId));
769
823
  }
770
824
 
771
825
  /** Restore only when the deterministic route rank improves; no shared-home
772
826
  * preferred-model state is needed or consulted. */
773
- async function restoreHigherRankIfAvailable(pi: ExtensionAPI, ctx: ExtensionContext): Promise<boolean> {
827
+ async function restoreHigherRankIfAvailable(state: ProviderRotationState, pi: ExtensionAPI, ctx: ExtensionContext): Promise<boolean> {
774
828
  if (!readRotationConfig().revertWhenAvailable) return false;
775
829
  const liveThinking = ctx.thinkingLevel === undefined || ctx.thinkingLevel === 'off' ? undefined : ctx.thinkingLevel;
776
830
  const request = modelRequestFromConfig(
777
831
  `${ctx.model?.provider ?? ''}/${ctx.model?.id ?? ''}${liveThinking ? `:${liveThinking}` : ''}`,
778
- process.env['CRTR_MODEL_INTENT'],
832
+ configuredValue(state, 'modelIntent', 'CRTR_MODEL_INTENT'),
779
833
  false,
780
834
  );
781
835
  if (!request || !ctx.model) return false;
782
- const candidates = expandModelCandidates(request, process.env['CRTR_NODE_CWD'], process.env['CRTR_PROFILE_ID']);
836
+ const candidates = expandModelCandidates(
837
+ request,
838
+ configuredValue(state, 'nodeCwd', 'CRTR_NODE_CWD'),
839
+ configuredValue(state, 'profileId', 'CRTR_PROFILE_ID'),
840
+ );
783
841
  const liveRank = candidates.find((candidate) => candidate.providerId === ctx.model?.provider && candidate.modelId === ctx.model?.id && candidate.thinkingLevel === liveThinking)?.rank;
784
842
  if (liveRank === undefined) return false;
785
843
  const preferred = candidates.find((candidate) => candidate.rank < liveRank && resolveProviderCandidates([{
@@ -895,134 +953,141 @@ async function loginProvider(providerId: ManagedProviderId, ctx: ExtensionContex
895
953
  );
896
954
  }
897
955
 
898
- export default async function (pi: ExtensionAPI): Promise<void> {
899
- extensionAPI = pi;
956
+ export function createProviderRotationExtension(options?: ProviderRotationOptions): (pi: ExtensionAPI) => Promise<void> {
957
+ const state = createProviderRotationState(options);
900
958
 
901
- pi.registerProvider(ANTHROPIC_PROVIDER_ID, {
902
- api: "anthropic-messages",
903
- streamSimple: streamManagedProvider,
904
- });
905
-
906
- pi.registerProvider(OPENAI_CODEX_PROVIDER_ID, {
907
- api: "openai-codex-responses",
908
- streamSimple: streamManagedProvider,
909
- });
959
+ return async (pi: ExtensionAPI): Promise<void> => {
960
+ state.extensionAPI = pi;
910
961
 
911
- pi.on("session_start", async (_event, ctx) => {
912
- runtimeContext = ctx;
913
- installDeclaredRouteProviders(pi, ctx);
914
- updateRotationStatus(ctx);
915
- });
962
+ pi.registerProvider(ANTHROPIC_PROVIDER_ID, {
963
+ api: "anthropic-messages",
964
+ streamSimple: (model, context, streamOptions) => streamManagedProvider(state, model, context, streamOptions),
965
+ });
916
966
 
917
- pi.on("before_agent_start", async (_event, ctx) => {
918
- runtimeContext = ctx;
919
- const restored = await restoreHigherRankIfAvailable(pi, ctx);
920
- if (restored && ctx.model && isManagedProvider(ctx.model.provider)) {
921
- logRotation(`restored higher-ranked route ${ctx.model.provider}/${ctx.model.id}`);
922
- setStatus(formatStatusLine(ctx.model.provider));
923
- }
924
- });
967
+ pi.registerProvider(OPENAI_CODEX_PROVIDER_ID, {
968
+ api: "openai-codex-responses",
969
+ streamSimple: (model, context, streamOptions) => streamManagedProvider(state, model, context, streamOptions),
970
+ });
925
971
 
926
- pi.on("model_select", async (_event, ctx) => {
927
- runtimeContext = ctx;
928
- updateRotationStatus(ctx);
929
- });
972
+ pi.on("session_start", async (_event, ctx) => {
973
+ state.runtimeContext = ctx;
974
+ installDeclaredRouteProviders(state, pi, ctx);
975
+ updateRotationStatus(state, ctx);
976
+ });
930
977
 
931
- pi.registerCommand("provider-sub", {
932
- description: "Manage subscription credential pools for provider rotation",
933
- handler: async (args, ctx) => {
934
- const [providerArg, verb, ...rest] = parseCommand(args);
935
- const providerId = parseProvider(providerArg);
936
- if (!providerId) {
937
- ctx.ui.notify("Usage: /provider-sub <anthropic|openai-codex> <list|add|select|rm>", "error");
938
- return;
978
+ pi.on("before_agent_start", async (_event, ctx) => {
979
+ state.runtimeContext = ctx;
980
+ const restored = await restoreHigherRankIfAvailable(state, pi, ctx);
981
+ if (restored && ctx.model && isManagedProvider(ctx.model.provider)) {
982
+ logRotation(`restored higher-ranked route ${ctx.model.provider}/${ctx.model.id}`);
983
+ setStatus(state, formatStatusLine(ctx.model.provider));
939
984
  }
985
+ });
940
986
 
941
- const action = verb === "rm" ? "remove" : verb || "list";
987
+ pi.on("model_select", async (_event, ctx) => {
988
+ state.runtimeContext = ctx;
989
+ updateRotationStatus(state, ctx);
990
+ });
942
991
 
943
- if (action === "status" || action === "list") {
944
- const pool = readSubscriptionPool(providerId);
945
- const lines = [formatStatusLine(providerId), ""];
946
- for (const [index, entry] of pool.entries()) {
947
- const cooldown = entry.rateLimitedUntil > Date.now() ? ` cooldown=${Math.ceil((entry.rateLimitedUntil - Date.now()) / 1000)}s` : "";
948
- const account = entry.accountId ? ` account=${shortAccountId(entry.accountId)}` : "";
949
- lines.push(`${index + 1}. ${entry.label}${account}${cooldown}`);
992
+ pi.registerCommand("provider-sub", {
993
+ description: "Manage subscription credential pools for provider rotation",
994
+ handler: async (args, ctx) => {
995
+ const [providerArg, verb, ...rest] = parseCommand(args);
996
+ const providerId = parseProvider(providerArg);
997
+ if (!providerId) {
998
+ ctx.ui.notify("Usage: /provider-sub <anthropic|openai-codex> <list|add|select|rm>", "error");
999
+ return;
950
1000
  }
951
- ctx.ui.notify(lines.join("\n"), "info");
952
- return;
953
- }
954
1001
 
955
- if (action === "add") {
956
- if (!ctx.hasUI) {
957
- ctx.ui.notify(`${getProviderLabel(providerId)} login requires an interactive UI.`, "error");
1002
+ const action = verb === "rm" ? "remove" : verb || "list";
1003
+
1004
+ if (action === "status" || action === "list") {
1005
+ const pool = readSubscriptionPool(providerId);
1006
+ const lines = [formatStatusLine(providerId), ""];
1007
+ for (const [index, entry] of pool.entries()) {
1008
+ const cooldown = entry.rateLimitedUntil > Date.now() ? ` cooldown=${Math.ceil((entry.rateLimitedUntil - Date.now()) / 1000)}s` : "";
1009
+ const account = entry.accountId ? ` account=${shortAccountId(entry.accountId)}` : "";
1010
+ lines.push(`${index + 1}. ${entry.label}${account}${cooldown}`);
1011
+ }
1012
+ ctx.ui.notify(lines.join("\n"), "info");
958
1013
  return;
959
1014
  }
960
- const pool = readSubscriptionPool(providerId);
961
- const label = (rest.join(" ") || (await ctx.ui.input(`${getProviderLabel(providerId)} subscription label`, "")) || "").trim();
962
- if (!label) return;
963
- if (pool.some((entry) => entry.label.toLowerCase() === label.toLowerCase())) {
964
- ctx.ui.notify(`${getProviderLabel(providerId)} subscription already exists: ${label}`, "error");
1015
+
1016
+ if (action === "add") {
1017
+ if (!ctx.hasUI) {
1018
+ ctx.ui.notify(`${getProviderLabel(providerId)} login requires an interactive UI.`, "error");
1019
+ return;
1020
+ }
1021
+ const pool = readSubscriptionPool(providerId);
1022
+ const label = (rest.join(" ") || (await ctx.ui.input(`${getProviderLabel(providerId)} subscription label`, "")) || "").trim();
1023
+ if (!label) return;
1024
+ if (pool.some((entry) => entry.label.toLowerCase() === label.toLowerCase())) {
1025
+ ctx.ui.notify(`${getProviderLabel(providerId)} subscription already exists: ${label}`, "error");
1026
+ return;
1027
+ }
1028
+ try {
1029
+ const cred = await activeLoginForProvider(providerId, ctx);
1030
+ const identity = await enrichManagedLoginIdentity(providerId, cred);
1031
+ // Explicit add always creates a new label, so a matching real account is rejected by
1032
+ // the locked uniqueness gate rather than creating a second quota slot.
1033
+ await addSubscription(providerId, {
1034
+ label,
1035
+ refresh: cred.refresh,
1036
+ access: cred.access,
1037
+ expires: cred.expires,
1038
+ ...(identity.accountId ? { accountId: identity.accountId } : {}),
1039
+ rateLimitedUntil: 0,
1040
+ lastAttemptAt: 0,
1041
+ lastRateLimitedAt: 0,
1042
+ });
1043
+ logRotation(`added ${getProviderLabel(providerId)} subscription "${label}"`);
1044
+ ctx.ui.notify(`Added ${getProviderLabel(providerId)} subscription: ${label}`, "info");
1045
+ updateRotationStatus(state, ctx);
1046
+ } catch (error) {
1047
+ ctx.ui.notify(error instanceof Error ? error.message : String(error), "error");
1048
+ }
965
1049
  return;
966
1050
  }
967
- try {
968
- const cred = await activeLoginForProvider(providerId, ctx);
969
- const identity = await enrichManagedLoginIdentity(providerId, cred);
970
- // Explicit add always creates a new label, so a matching real account is rejected by
971
- // the locked uniqueness gate rather than creating a second quota slot.
972
- await addSubscription(providerId, {
973
- label,
974
- refresh: cred.refresh,
975
- access: cred.access,
976
- expires: cred.expires,
977
- ...(identity.accountId ? { accountId: identity.accountId } : {}),
978
- rateLimitedUntil: 0,
979
- lastAttemptAt: 0,
980
- lastRateLimitedAt: 0,
981
- });
982
- logRotation(`added ${getProviderLabel(providerId)} subscription "${label}"`);
983
- ctx.ui.notify(`Added ${getProviderLabel(providerId)} subscription: ${label}`, "info");
984
- updateRotationStatus(ctx);
985
- } catch (error) {
986
- ctx.ui.notify(error instanceof Error ? error.message : String(error), "error");
987
- }
988
- return;
989
- }
990
1051
 
991
- if (action === "select") {
992
- const ref = rest.join(" ");
993
- if (!ref) {
994
- ctx.ui.notify(`Usage: /provider-sub ${providerId} select <label|index>`, "error");
1052
+ if (action === "select") {
1053
+ const ref = rest.join(" ");
1054
+ if (!ref) {
1055
+ ctx.ui.notify(`Usage: /provider-sub ${providerId} select <label|index>`, "error");
1056
+ return;
1057
+ }
1058
+ try {
1059
+ await promoteSubscription(providerId, ref);
1060
+ logRotation(`selected ${getProviderLabel(providerId)} subscription "${ref}"`);
1061
+ ctx.ui.notify(`Preferred ${getProviderLabel(providerId)} subscription: ${ref}`, "info");
1062
+ updateRotationStatus(state, ctx);
1063
+ } catch (error) {
1064
+ ctx.ui.notify(error instanceof Error ? error.message : String(error), "error");
1065
+ }
995
1066
  return;
996
1067
  }
997
- try {
998
- await promoteSubscription(providerId, ref);
999
- logRotation(`selected ${getProviderLabel(providerId)} subscription "${ref}"`);
1000
- ctx.ui.notify(`Preferred ${getProviderLabel(providerId)} subscription: ${ref}`, "info");
1001
- updateRotationStatus(ctx);
1002
- } catch (error) {
1003
- ctx.ui.notify(error instanceof Error ? error.message : String(error), "error");
1004
- }
1005
- return;
1006
- }
1007
1068
 
1008
- if (action === "remove") {
1009
- const ref = rest.join(" ");
1010
- if (!ref) {
1011
- ctx.ui.notify(`Usage: /provider-sub ${providerId} rm <label|index>`, "error");
1069
+ if (action === "remove") {
1070
+ const ref = rest.join(" ");
1071
+ if (!ref) {
1072
+ ctx.ui.notify(`Usage: /provider-sub ${providerId} rm <label|index>`, "error");
1073
+ return;
1074
+ }
1075
+ try {
1076
+ await removeManagedAccount(providerId, ref);
1077
+ logRotation(`removed ${getProviderLabel(providerId)} subscription "${ref}"`);
1078
+ ctx.ui.notify(`Removed ${getProviderLabel(providerId)} subscription: ${ref}`, "info");
1079
+ updateRotationStatus(state, ctx);
1080
+ } catch (error) {
1081
+ ctx.ui.notify(error instanceof Error ? error.message : String(error), "error");
1082
+ }
1012
1083
  return;
1013
1084
  }
1014
- try {
1015
- await removeManagedAccount(providerId, ref);
1016
- logRotation(`removed ${getProviderLabel(providerId)} subscription "${ref}"`);
1017
- ctx.ui.notify(`Removed ${getProviderLabel(providerId)} subscription: ${ref}`, "info");
1018
- updateRotationStatus(ctx);
1019
- } catch (error) {
1020
- ctx.ui.notify(error instanceof Error ? error.message : String(error), "error");
1021
- }
1022
- return;
1023
- }
1024
1085
 
1025
- ctx.ui.notify(`Unknown /provider-sub verb: ${verb}`, "error");
1026
- },
1027
- });
1086
+ ctx.ui.notify(`Unknown /provider-sub verb: ${verb}`, "error");
1087
+ },
1088
+ });
1089
+ };
1028
1090
  }
1091
+
1092
+ const defaultProviderRotationExtension = createProviderRotationExtension();
1093
+ export default defaultProviderRotationExtension;
@@ -218,11 +218,10 @@ export default function (pi: ExtensionAPI) {
218
218
  });
219
219
 
220
220
  // --- Input bar border: session-name badge with solid background ---
221
- // Compose with whatever editor is already installed (e.g. the mode-switch
222
- // badge on the bottom border) instead of replacing it: wrap the previous
223
- // factory in a Proxy that overrides only render (session-name badge on the
224
- // top border). Other editor decorations keep working regardless of load
225
- // order.
221
+ // Compose with whatever editor is already installed instead of replacing it:
222
+ // wrap the previous factory in a Proxy that overrides only render
223
+ // (session-name badge on the top border). Other editor decorations keep
224
+ // working regardless of load order.
226
225
  const previousEditorFactory = ctx.ui.getEditorComponent();
227
226
  ctx.ui.setEditorComponent((tui, theme, keybindings) => {
228
227
  const base = (