@gajae-code/coding-agent 0.11.1 → 0.11.2

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 (404) hide show
  1. package/CHANGELOG.md +54 -0
  2. package/dist/types/cli/config-cli.d.ts +11 -1
  3. package/dist/types/cli/notify-cli.d.ts +1 -0
  4. package/dist/types/cli/session-picker.d.ts +2 -1
  5. package/dist/types/cli/setup-cli.d.ts +1 -1
  6. package/dist/types/commands/mcp-serve.d.ts +33 -0
  7. package/dist/types/commands/mcp.d.ts +1 -1
  8. package/dist/types/commands/notify.d.ts +0 -2
  9. package/dist/types/commands/sdk.d.ts +1 -1
  10. package/dist/types/config/atomic-yaml-patch.d.ts +18 -0
  11. package/dist/types/config/file-lock.d.ts +14 -4
  12. package/dist/types/config/keybindings.d.ts +42 -1
  13. package/dist/types/config/model-auth.d.ts +3 -0
  14. package/dist/types/config/model-bindings-applier.d.ts +13 -0
  15. package/dist/types/config/model-discovery-manager.d.ts +49 -0
  16. package/dist/types/config/model-equivalence.d.ts +20 -0
  17. package/dist/types/config/model-registry.d.ts +16 -15
  18. package/dist/types/config/model-resolver.d.ts +28 -18
  19. package/dist/types/config/models-config-schema.d.ts +11 -0
  20. package/dist/types/config/settings-schema.d.ts +56 -1
  21. package/dist/types/config/settings.d.ts +14 -11
  22. package/dist/types/coordinator-mcp/policy.d.ts +5 -0
  23. package/dist/types/coordinator-mcp/question-gate-codec.d.ts +129 -0
  24. package/dist/types/coordinator-mcp/question-state.d.ts +356 -0
  25. package/dist/types/coordinator-mcp/server.d.ts +4 -0
  26. package/dist/types/discovery/helpers.d.ts +0 -6
  27. package/dist/types/edit/index.d.ts +3 -0
  28. package/dist/types/eval/py/env.d.ts +9 -0
  29. package/dist/types/extensibility/extensions/loader.d.ts +2 -1
  30. package/dist/types/extensibility/extensions/runner.d.ts +2 -2
  31. package/dist/types/extensibility/extensions/types.d.ts +34 -3
  32. package/dist/types/extensibility/hooks/runner.d.ts +1 -1
  33. package/dist/types/extensibility/shared-events.d.ts +2 -0
  34. package/dist/types/gjc-runtime/deep-interview-recorder.d.ts +10 -1
  35. package/dist/types/gjc-runtime/deep-interview-state.d.ts +44 -0
  36. package/dist/types/gjc-runtime/goal-mode-request.d.ts +11 -1
  37. package/dist/types/gjc-runtime/launch-worktree.d.ts +4 -2
  38. package/dist/types/gjc-runtime/linux-proc.d.ts +29 -0
  39. package/dist/types/gjc-runtime/session-resolution.d.ts +2 -2
  40. package/dist/types/gjc-runtime/state-migrations.d.ts +1 -1
  41. package/dist/types/gjc-runtime/state-writer.d.ts +2 -1
  42. package/dist/types/gjc-runtime/team-launch.d.ts +59 -0
  43. package/dist/types/gjc-runtime/team-notify.d.ts +45 -0
  44. package/dist/types/gjc-runtime/team-runtime.d.ts +29 -121
  45. package/dist/types/gjc-runtime/team-store.d.ts +160 -0
  46. package/dist/types/gjc-runtime/team-workers.d.ts +78 -0
  47. package/dist/types/gjc-runtime/tmux-owner-isolation.d.ts +8 -0
  48. package/dist/types/gjc-runtime/ultragoal-change-set.d.ts +13 -0
  49. package/dist/types/gjc-runtime/ultragoal-evidence.d.ts +38 -0
  50. package/dist/types/gjc-runtime/ultragoal-guard.d.ts +7 -0
  51. package/dist/types/gjc-runtime/ultragoal-pipeline.d.ts +23 -0
  52. package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +55 -33
  53. package/dist/types/gjc-runtime/workflow-cli-common.d.ts +8 -0
  54. package/dist/types/gjc-runtime/workflow-manifest.d.ts +3 -0
  55. package/dist/types/goals/continuation-timeout-guard.d.ts +22 -0
  56. package/dist/types/goals/runtime.d.ts +1 -0
  57. package/dist/types/goals/state.d.ts +8 -0
  58. package/dist/types/harness-control-plane/lifecycle-reconciliation.d.ts +17 -0
  59. package/dist/types/harness-control-plane/phase-rollup.d.ts +4 -7
  60. package/dist/types/harness-control-plane/receipt-ingest.d.ts +5 -0
  61. package/dist/types/harness-control-plane/receipt-spool.d.ts +4 -0
  62. package/dist/types/hindsight/state.d.ts +11 -4
  63. package/dist/types/lsp/lspmux.d.ts +5 -3
  64. package/dist/types/lsp/path-trust.d.ts +1 -0
  65. package/dist/types/main.d.ts +3 -2
  66. package/dist/types/memories/storage.d.ts +1 -0
  67. package/dist/types/modes/action-registry.d.ts +29 -0
  68. package/dist/types/modes/components/chrome.d.ts +14 -0
  69. package/dist/types/modes/components/command-palette.d.ts +27 -0
  70. package/dist/types/modes/components/custom-provider-wizard.d.ts +1 -0
  71. package/dist/types/modes/components/index.d.ts +2 -1
  72. package/dist/types/modes/components/pet-selector.d.ts +1 -1
  73. package/dist/types/modes/components/plan-preview-overlay.d.ts +43 -0
  74. package/dist/types/modes/components/queue-mode-selector.d.ts +1 -1
  75. package/dist/types/modes/components/queue-pane.d.ts +12 -0
  76. package/dist/types/modes/components/read-tool-group.d.ts +6 -0
  77. package/dist/types/modes/components/session-observer-overlay.d.ts +3 -4
  78. package/dist/types/modes/components/sessions-dashboard.d.ts +32 -0
  79. package/dist/types/modes/components/settings-selector.d.ts +1 -1
  80. package/dist/types/modes/components/show-images-selector.d.ts +1 -1
  81. package/dist/types/modes/components/status-line/types.d.ts +1 -1
  82. package/dist/types/modes/components/status-line.d.ts +1 -94
  83. package/dist/types/modes/components/tasks-pane.d.ts +15 -0
  84. package/dist/types/modes/components/theme-selector.d.ts +1 -1
  85. package/dist/types/modes/components/tool-execution.d.ts +15 -0
  86. package/dist/types/modes/components/tool-status-header.d.ts +111 -0
  87. package/dist/types/modes/components/transcript-viewer-overlay.d.ts +42 -0
  88. package/dist/types/modes/controllers/goal-mode-controller.d.ts +62 -0
  89. package/dist/types/modes/controllers/input-controller.d.ts +8 -1
  90. package/dist/types/modes/controllers/mode-gate.d.ts +8 -0
  91. package/dist/types/modes/controllers/plan-mode-controller.d.ts +65 -0
  92. package/dist/types/modes/controllers/selector-controller.d.ts +15 -3
  93. package/dist/types/modes/controllers/stt-controller.d.ts +26 -0
  94. package/dist/types/modes/interactive-mode.d.ts +19 -17
  95. package/dist/types/modes/jobs-observer.d.ts +2 -0
  96. package/dist/types/modes/shared/agent-wire/deep-interview-gate.d.ts +4 -11
  97. package/dist/types/modes/shared/agent-wire/workflow-gate-broker.d.ts +20 -2
  98. package/dist/types/modes/shared/agent-wire/workflow-gate-types.d.ts +31 -0
  99. package/dist/types/modes/tasks-aggregator.d.ts +39 -0
  100. package/dist/types/modes/transcript-item-registry.d.ts +60 -0
  101. package/dist/types/modes/types.d.ts +20 -13
  102. package/dist/types/modes/utils/ui-helpers.d.ts +2 -2
  103. package/dist/types/runtime-mcp/manager.d.ts +1 -1
  104. package/dist/types/sdk/broker/broker.d.ts +78 -12
  105. package/dist/types/sdk/broker/lifecycle-codec.d.ts +4 -0
  106. package/dist/types/sdk/broker/lifecycle-ledger.d.ts +15 -1
  107. package/dist/types/sdk/broker/lifecycle.d.ts +6 -2
  108. package/dist/types/sdk/broker/process-incarnation.d.ts +2 -0
  109. package/dist/types/sdk/broker/session-index.d.ts +0 -1
  110. package/dist/types/sdk/broker/state-version.d.ts +2 -0
  111. package/dist/types/sdk/bus/chat-effect-journal.d.ts +1 -0
  112. package/dist/types/sdk/bus/config-commands.d.ts +1 -1
  113. package/dist/types/sdk/bus/config.d.ts +11 -0
  114. package/dist/types/sdk/bus/daemon-paths.d.ts +1 -0
  115. package/dist/types/sdk/bus/html-format.d.ts +3 -2
  116. package/dist/types/sdk/bus/index.d.ts +27 -0
  117. package/dist/types/sdk/bus/lifecycle-control-runtime.d.ts +4 -0
  118. package/dist/types/sdk/bus/notification-service.d.ts +20 -0
  119. package/dist/types/sdk/bus/rate-limit-pool.d.ts +13 -2
  120. package/dist/types/sdk/bus/recent-activity.d.ts +21 -12
  121. package/dist/types/sdk/bus/rich-document.d.ts +7 -0
  122. package/dist/types/sdk/bus/rich-render.d.ts +18 -41
  123. package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +9 -4
  124. package/dist/types/sdk/bus/telegram-daemon.d.ts +46 -3
  125. package/dist/types/sdk/bus/threaded-inbound.d.ts +1 -1
  126. package/dist/types/sdk/bus/threaded-render.d.ts +13 -0
  127. package/dist/types/sdk/bus/topic-registry.d.ts +24 -2
  128. package/dist/types/sdk/host/control/operations.d.ts +1 -1
  129. package/dist/types/sdk/host/host.d.ts +2 -0
  130. package/dist/types/sdk/index.d.ts +1 -0
  131. package/dist/types/sdk/session-directory.d.ts +56 -0
  132. package/dist/types/session/agent-session.d.ts +82 -47
  133. package/dist/types/session/agent-storage.d.ts +7 -0
  134. package/dist/types/session/fallback-chain-controller.d.ts +10 -2
  135. package/dist/types/session/internal/managed-session-scope.d.ts +102 -0
  136. package/dist/types/session/internal/managed-session-storage.d.ts +38 -0
  137. package/dist/types/session/messages.d.ts +16 -0
  138. package/dist/types/session/session-manager-internal.d.ts +14 -0
  139. package/dist/types/session/session-manager.d.ts +82 -12
  140. package/dist/types/session/session-storage.d.ts +43 -3
  141. package/dist/types/session/volatile-context-pruning.d.ts +15 -0
  142. package/dist/types/skill-state/{deep-interview-mutation-guard.d.ts → workflow-mutation-guard.d.ts} +24 -8
  143. package/dist/types/skill-state/workflow-state-contract.d.ts +1 -3
  144. package/dist/types/slash-commands/builtin-registry.d.ts +4 -1
  145. package/dist/types/system-prompt.d.ts +2 -11
  146. package/dist/types/task/executor.d.ts +3 -1
  147. package/dist/types/thinking.d.ts +7 -0
  148. package/dist/types/tool-discovery/tool-index.d.ts +1 -50
  149. package/dist/types/tools/ask.d.ts +64 -2
  150. package/dist/types/tools/bash.d.ts +3 -0
  151. package/dist/types/tools/browser/dead-tab-recovery.d.ts +24 -0
  152. package/dist/types/tools/browser/tab-supervisor.d.ts +21 -0
  153. package/dist/types/tools/cron.d.ts +2 -0
  154. package/dist/types/tools/fetch.d.ts +1 -0
  155. package/dist/types/tools/index.d.ts +30 -15
  156. package/dist/types/tools/output-meta.d.ts +1 -0
  157. package/dist/types/tools/read.d.ts +3 -0
  158. package/dist/types/tools/search-tool-bm25.d.ts +3 -4
  159. package/dist/types/utils/event-bus.d.ts +1 -1
  160. package/dist/types/web/search/index.d.ts +3 -1
  161. package/package.json +21 -9
  162. package/scripts/build-sdk-package-smoke.ts +34 -1
  163. package/scripts/generate-hotkeys-docs.ts +74 -0
  164. package/scripts/generate-sdk-operation-inventory.ts +64 -20
  165. package/scripts/verify-gjc-sdk-canonicalization.ts +182 -2
  166. package/src/async/job-manager.ts +49 -6
  167. package/src/cli/config-cli.ts +75 -2
  168. package/src/cli/notify-cli.ts +92 -26
  169. package/src/cli/session-picker.ts +20 -3
  170. package/src/cli/setup-cli.ts +1 -1
  171. package/src/cli.ts +1 -1
  172. package/src/commands/config.ts +1 -1
  173. package/src/commands/harness.ts +34 -106
  174. package/src/commands/launch.ts +6 -4
  175. package/src/commands/mcp-serve.ts +85 -8
  176. package/src/commands/mcp.ts +2 -2
  177. package/src/commands/notify.ts +8 -4
  178. package/src/commands/sdk.ts +61 -40
  179. package/src/commands/ultragoal.ts +4 -1
  180. package/src/config/atomic-yaml-patch.ts +80 -13
  181. package/src/config/file-lock.ts +105 -43
  182. package/src/config/keybindings.ts +64 -20
  183. package/src/config/model-auth.ts +9 -0
  184. package/src/config/model-bindings-applier.ts +139 -0
  185. package/src/config/model-discovery-manager.ts +224 -0
  186. package/src/config/model-equivalence.ts +66 -0
  187. package/src/config/model-registry.ts +169 -302
  188. package/src/config/model-resolver.ts +361 -369
  189. package/src/config/models-config-schema.ts +5 -4
  190. package/src/config/settings-schema.ts +159 -2
  191. package/src/config/settings.ts +458 -482
  192. package/src/coordinator-mcp/policy.ts +76 -11
  193. package/src/coordinator-mcp/question-gate-codec.ts +274 -0
  194. package/src/coordinator-mcp/question-state.ts +778 -0
  195. package/src/coordinator-mcp/server.ts +1702 -238
  196. package/src/debug/index.ts +1 -1
  197. package/src/defaults/gjc/skills/deep-interview/SKILL.md +13 -1
  198. package/src/defaults/gjc/skills/team/SKILL.md +16 -0
  199. package/src/discovery/helpers.ts +1 -50
  200. package/src/edit/index.ts +64 -0
  201. package/src/eval/py/env.ts +26 -0
  202. package/src/eval/py/kernel.ts +5 -3
  203. package/src/extensibility/extensions/loader.ts +9 -1
  204. package/src/extensibility/extensions/runner.ts +12 -3
  205. package/src/extensibility/extensions/types.ts +39 -1
  206. package/src/extensibility/gjc-plugins/installer.ts +16 -16
  207. package/src/extensibility/hooks/runner.ts +2 -2
  208. package/src/extensibility/runtime-skill-discovery.ts +33 -13
  209. package/src/extensibility/shared-events.ts +2 -0
  210. package/src/gjc-runtime/deep-interview-recorder.ts +63 -3
  211. package/src/gjc-runtime/deep-interview-runtime.ts +76 -27
  212. package/src/gjc-runtime/deep-interview-state.ts +317 -5
  213. package/src/gjc-runtime/goal-mode-request.ts +55 -8
  214. package/src/gjc-runtime/launch-tmux.ts +2 -5
  215. package/src/gjc-runtime/launch-worktree.ts +5 -5
  216. package/src/gjc-runtime/linux-proc.ts +71 -0
  217. package/src/gjc-runtime/ralplan-runtime.ts +74 -90
  218. package/src/gjc-runtime/session-resolution.ts +18 -3
  219. package/src/gjc-runtime/session-state-sidecar.ts +6 -107
  220. package/src/gjc-runtime/state-migrations.ts +1 -1
  221. package/src/gjc-runtime/state-runtime.ts +320 -249
  222. package/src/gjc-runtime/state-writer.ts +36 -40
  223. package/src/gjc-runtime/team-launch.ts +319 -0
  224. package/src/gjc-runtime/team-notify.ts +326 -0
  225. package/src/gjc-runtime/team-runtime.ts +1099 -1482
  226. package/src/gjc-runtime/team-store.ts +828 -0
  227. package/src/gjc-runtime/team-workers.ts +962 -0
  228. package/src/gjc-runtime/tmux-owner-isolation-cli.ts +2 -11
  229. package/src/gjc-runtime/tmux-owner-isolation.ts +14 -9
  230. package/src/gjc-runtime/tmux-sessions.ts +4 -21
  231. package/src/gjc-runtime/ultragoal-change-set.ts +172 -0
  232. package/src/gjc-runtime/ultragoal-evidence.ts +874 -0
  233. package/src/gjc-runtime/ultragoal-guard.ts +29 -5
  234. package/src/gjc-runtime/ultragoal-pipeline.ts +306 -0
  235. package/src/gjc-runtime/ultragoal-runtime.ts +84 -1311
  236. package/src/gjc-runtime/workflow-cli-common.ts +30 -0
  237. package/src/gjc-runtime/workflow-manifest.generated.json +58 -0
  238. package/src/gjc-runtime/workflow-manifest.ts +18 -0
  239. package/src/goals/continuation-timeout-guard.ts +79 -0
  240. package/src/goals/runtime.ts +7 -5
  241. package/src/goals/state.ts +18 -0
  242. package/src/harness-control-plane/lifecycle-reconciliation.ts +64 -0
  243. package/src/harness-control-plane/phase-rollup.ts +4 -7
  244. package/src/harness-control-plane/receipt-ingest.ts +5 -0
  245. package/src/harness-control-plane/receipt-spool.ts +51 -7
  246. package/src/harness-control-plane/session-lease.ts +12 -130
  247. package/src/hindsight/backend.ts +26 -27
  248. package/src/hindsight/state.ts +101 -32
  249. package/src/hooks/skill-state.ts +17 -11
  250. package/src/internal-urls/docs-index.generated.ts +21 -18
  251. package/src/lsp/client.ts +1 -1
  252. package/src/lsp/config.ts +145 -26
  253. package/src/lsp/index.ts +1 -1
  254. package/src/lsp/lspmux.ts +39 -10
  255. package/src/lsp/path-trust.ts +81 -0
  256. package/src/main.ts +28 -8
  257. package/src/memories/index.ts +1 -0
  258. package/src/memories/storage.ts +6 -2
  259. package/src/modes/action-registry.ts +165 -0
  260. package/src/modes/components/chrome.ts +40 -0
  261. package/src/modes/components/command-palette.ts +168 -0
  262. package/src/modes/components/custom-editor.ts +1 -1
  263. package/src/modes/components/custom-provider-wizard.ts +51 -14
  264. package/src/modes/components/index.ts +2 -1
  265. package/src/modes/components/pet-selector.ts +11 -17
  266. package/src/modes/components/plan-preview-overlay.ts +295 -0
  267. package/src/modes/components/queue-mode-selector.ts +10 -27
  268. package/src/modes/components/queue-pane.ts +79 -0
  269. package/src/modes/components/read-tool-group.ts +13 -0
  270. package/src/modes/components/session-observer-overlay.ts +207 -772
  271. package/src/modes/components/sessions-dashboard.ts +181 -0
  272. package/src/modes/components/settings-selector.ts +1 -1
  273. package/src/modes/components/show-images-selector.ts +10 -24
  274. package/src/modes/components/status-line/types.ts +1 -1
  275. package/src/modes/components/status-line.ts +1 -969
  276. package/src/modes/components/tasks-pane.ts +73 -0
  277. package/src/modes/components/theme-selector.ts +11 -31
  278. package/src/modes/components/tool-execution.ts +22 -0
  279. package/src/modes/components/tool-status-header.ts +1056 -0
  280. package/src/modes/components/transcript-viewer-overlay.ts +384 -0
  281. package/src/modes/controllers/btw-controller.ts +1 -0
  282. package/src/modes/controllers/event-controller.ts +3 -2
  283. package/src/modes/controllers/extension-ui-controller.ts +24 -1
  284. package/src/modes/controllers/goal-mode-controller.ts +474 -0
  285. package/src/modes/controllers/input-controller.ts +764 -229
  286. package/src/modes/controllers/mode-gate.ts +24 -0
  287. package/src/modes/controllers/plan-mode-controller.ts +517 -0
  288. package/src/modes/controllers/selector-controller.ts +225 -10
  289. package/src/modes/controllers/stt-controller.ts +125 -0
  290. package/src/modes/interactive-mode.ts +289 -1150
  291. package/src/modes/jobs-observer.ts +3 -0
  292. package/src/modes/runtime-init.ts +8 -0
  293. package/src/modes/shared/agent-wire/deep-interview-gate.ts +31 -106
  294. package/src/modes/shared/agent-wire/workflow-gate-broker.ts +71 -16
  295. package/src/modes/shared/agent-wire/workflow-gate-types.ts +177 -0
  296. package/src/modes/tasks-aggregator.ts +199 -0
  297. package/src/modes/transcript-item-registry.ts +229 -0
  298. package/src/modes/types.ts +45 -18
  299. package/src/modes/utils/ui-helpers.ts +17 -9
  300. package/src/prompts/agent-fragments/ralplan-persistence.md +6 -0
  301. package/src/prompts/agent-fragments/restricted-bash.md +1 -0
  302. package/src/prompts/agents/architect.md +2 -8
  303. package/src/prompts/agents/critic.md +2 -8
  304. package/src/prompts/agents/executor.md +3 -0
  305. package/src/prompts/agents/planner.md +4 -12
  306. package/src/prompts/system/custom-system-prompt.md +1 -23
  307. package/src/prompts/system/plan-mode-approved.md +8 -0
  308. package/src/prompts/system/system-prompt.md +17 -96
  309. package/src/prompts/tools/bash.md +4 -4
  310. package/src/prompts/tools/browser.md +15 -52
  311. package/src/prompts/tools/find.md +2 -2
  312. package/src/prompts/tools/hashline.md +13 -55
  313. package/src/prompts/tools/read.md +3 -6
  314. package/src/prompts/tools/search-tool-bm25.md +0 -2
  315. package/src/prompts/tools/search.md +2 -4
  316. package/src/prompts/tools/skill-discovery.md +1 -2
  317. package/src/prompts/tools/task.md +1 -1
  318. package/src/runtime-mcp/manager.ts +1 -1
  319. package/src/sdk/broker/broker.ts +206 -78
  320. package/src/sdk/broker/discovery.ts +1 -1
  321. package/src/sdk/broker/ensure.ts +3 -1
  322. package/src/sdk/broker/lifecycle-codec.ts +11 -0
  323. package/src/sdk/broker/lifecycle-ledger.ts +371 -74
  324. package/src/sdk/broker/lifecycle.ts +1399 -142
  325. package/src/sdk/broker/process-incarnation.ts +5 -13
  326. package/src/sdk/broker/session-index.ts +145 -56
  327. package/src/sdk/broker/state-version.ts +21 -0
  328. package/src/sdk/broker/transport.ts +1 -1
  329. package/src/sdk/bus/chat-daemon-cli.ts +2 -0
  330. package/src/sdk/bus/chat-daemon-control.ts +8 -28
  331. package/src/sdk/bus/chat-effect-journal.ts +10 -2
  332. package/src/sdk/bus/config-commands.ts +5 -4
  333. package/src/sdk/bus/config.ts +98 -0
  334. package/src/sdk/bus/conversation-store.ts +5 -29
  335. package/src/sdk/bus/daemon-paths.ts +2 -0
  336. package/src/sdk/bus/html-format.ts +20 -3
  337. package/src/sdk/bus/index.ts +778 -93
  338. package/src/sdk/bus/lifecycle-control-runtime.ts +91 -33
  339. package/src/sdk/bus/natives-augment.d.ts +26 -1
  340. package/src/sdk/bus/notification-service.ts +168 -20
  341. package/src/sdk/bus/rate-limit-pool.ts +58 -14
  342. package/src/sdk/bus/recent-activity.ts +188 -74
  343. package/src/sdk/bus/rich-document.ts +258 -0
  344. package/src/sdk/bus/rich-render.ts +62 -70
  345. package/src/sdk/bus/telegram-daemon-cli.ts +15 -59
  346. package/src/sdk/bus/telegram-daemon-contract.ts +9 -4
  347. package/src/sdk/bus/telegram-daemon.ts +1389 -237
  348. package/src/sdk/bus/threaded-inbound.ts +8 -3
  349. package/src/sdk/bus/threaded-render.ts +57 -1
  350. package/src/sdk/bus/topic-registry.ts +120 -13
  351. package/src/sdk/host/control/dispatch.ts +14 -2
  352. package/src/sdk/host/control/operations.ts +6 -1
  353. package/src/sdk/host/host.ts +31 -1
  354. package/src/sdk/host/query/handlers.ts +20 -22
  355. package/src/sdk/host/query/revision-store.ts +80 -57
  356. package/src/sdk/index.ts +1 -0
  357. package/src/sdk/protocol/operation-inventory.generated.json +943 -1907
  358. package/src/sdk/session-directory.ts +141 -0
  359. package/src/sdk/session.ts +458 -479
  360. package/src/session/agent-session.ts +1700 -583
  361. package/src/session/agent-storage.ts +18 -0
  362. package/src/session/cache-economics.ts +25 -8
  363. package/src/session/fallback-chain-controller.ts +53 -3
  364. package/src/session/internal/managed-session-scope.ts +2226 -0
  365. package/src/session/internal/managed-session-storage.ts +453 -0
  366. package/src/session/messages.ts +91 -2
  367. package/src/session/session-manager-internal.ts +33 -0
  368. package/src/session/session-manager.ts +1112 -333
  369. package/src/session/session-storage.ts +444 -39
  370. package/src/session/volatile-context-pruning.ts +78 -0
  371. package/src/setup/credential-auto-import.ts +2 -1
  372. package/src/setup/hermes/templates/operator-instructions.v1.md +1 -1
  373. package/src/setup/provider-onboarding.ts +36 -61
  374. package/src/setup/provider-presets.json +5 -0
  375. package/src/skill-state/active-state.ts +3 -13
  376. package/src/skill-state/{deep-interview-mutation-guard.ts → workflow-mutation-guard.ts} +47 -29
  377. package/src/skill-state/workflow-state-contract.ts +6 -41
  378. package/src/slash-commands/builtin-registry.ts +46 -11
  379. package/src/system-prompt.ts +37 -44
  380. package/src/task/agents.ts +19 -1
  381. package/src/task/executor.ts +90 -6
  382. package/src/task/index.ts +36 -21
  383. package/src/thinking.ts +21 -6
  384. package/src/tool-discovery/tool-index.ts +2 -134
  385. package/src/tools/ask.ts +157 -21
  386. package/src/tools/ast-edit.ts +1 -1
  387. package/src/tools/bash.ts +38 -0
  388. package/src/tools/browser/dead-tab-recovery.ts +78 -0
  389. package/src/tools/browser/tab-supervisor.ts +282 -36
  390. package/src/tools/computer.ts +10 -3
  391. package/src/tools/cron.ts +30 -16
  392. package/src/tools/eval.ts +1 -2
  393. package/src/tools/fetch.ts +11 -4
  394. package/src/tools/index.ts +76 -24
  395. package/src/tools/output-meta.ts +13 -5
  396. package/src/tools/read.ts +37 -1
  397. package/src/tools/resource-gc.ts +2 -2
  398. package/src/tools/search-tool-bm25.ts +11 -74
  399. package/src/utils/event-bus.ts +9 -4
  400. package/src/web/search/index.ts +3 -3
  401. package/dist/types/discovery/plugin-dir-roots.d.ts +0 -15
  402. package/dist/types/runtime-mcp/discoverable-tool-metadata.d.ts +0 -7
  403. package/src/discovery/plugin-dir-roots.ts +0 -28
  404. package/src/runtime-mcp/discoverable-tool-metadata.ts +0 -25
@@ -40,11 +40,18 @@ import { isValidThemeColor, type ThemeColor } from "../modes/theme/theme";
40
40
  import type { AuthStorage, OAuthCredential } from "../session/auth-storage";
41
41
  import type { ActiveSearchModelContext, WebSearchMode } from "../web/search/types";
42
42
  import { type ConfigError, ConfigFile } from "./config-file";
43
+ import { isAuthenticated, kNoAuth } from "./model-auth";
44
+ import { ModelBindingsApplier } from "./model-bindings-applier";
45
+ import { ModelDiscoveryManager, type ProviderDiscoveryState } from "./model-discovery-manager";
46
+
47
+ export type { ProviderDiscoveryState, ProviderDiscoveryStatus } from "./model-discovery-manager";
48
+
43
49
  import {
44
50
  buildCanonicalModelIndex,
45
51
  type CanonicalModelIndex,
46
52
  type CanonicalModelRecord,
47
53
  type CanonicalModelVariant,
54
+ compareEquivalentModelVariants,
48
55
  formatCanonicalVariantSelector,
49
56
  type ModelEquivalenceConfig,
50
57
  } from "./model-equivalence";
@@ -53,8 +60,8 @@ import {
53
60
  type ModelProfileDefinition,
54
61
  mergeModelProfiles,
55
62
  } from "./model-profiles";
56
- import { type ModelSelectorValue, normalizeModelSelectorValue } from "./model-selector-value";
57
63
  import {
64
+ GJC_MODEL_ASSIGNMENT_TARGET_IDS,
58
65
  type ModelOverride,
59
66
  type ModelProfileConfig,
60
67
  type ModelsConfig,
@@ -67,10 +74,20 @@ import { type Settings, settings } from "./settings";
67
74
 
68
75
  export type { CanonicalModelIndex, CanonicalModelRecord, CanonicalModelVariant, ModelEquivalenceConfig };
69
76
 
70
- export const kNoAuth = "N/A";
77
+ export { isAuthenticated, kNoAuth };
71
78
 
72
- export function isAuthenticated(apiKey: string | undefined | null): apiKey is string {
73
- return Boolean(apiKey) && apiKey !== kNoAuth;
79
+ const MAX_SESSION_CANONICAL_VARIANTS = 64;
80
+
81
+ function envAvailabilityFingerprint(): string {
82
+ return Object.entries(process.env)
83
+ .filter(
84
+ ([name]) =>
85
+ /(?:_API_KEY|_OAUTH_TOKEN|_ACCESS_TOKEN)$/.test(name) ||
86
+ /^(?:GH_TOKEN|GITHUB_TOKEN|HF_TOKEN|COPILOT_GITHUB_TOKEN)$/.test(name),
87
+ )
88
+ .sort(([left], [right]) => left.localeCompare(right))
89
+ .map(([name, value]) => `${name}=${value ?? ""}`)
90
+ .join("\u0000");
74
91
  }
75
92
 
76
93
  export type ModelRole = "default";
@@ -88,20 +105,14 @@ export const MODEL_ROLES: Record<ModelRole, ModelRoleInfo> = {
88
105
  export const MODEL_ROLE_IDS: ModelRole[] = ["default"];
89
106
  export const MODEL_PROFILE_NAME_PATTERN = /^[a-z0-9][a-z0-9._-]*$/;
90
107
  export const MODEL_PROFILE_NAME_PATTERN_DESCRIPTION = "lowercase letters, numbers, dots, underscores, or hyphens";
91
- export type GjcModelAssignmentTargetId = "default" | "executor" | "architect" | "planner" | "critic";
108
+ export type GjcModelAssignmentTargetId = (typeof GJC_MODEL_ASSIGNMENT_TARGET_IDS)[number];
92
109
 
93
110
  export interface GjcModelAssignmentTargetInfo extends ModelRoleInfo {
94
111
  id: GjcModelAssignmentTargetId;
95
112
  settingsPath: "modelRoles" | "task.agentModelOverrides";
96
113
  }
97
114
 
98
- export const GJC_MODEL_ASSIGNMENT_TARGET_IDS: GjcModelAssignmentTargetId[] = [
99
- "default",
100
- "executor",
101
- "architect",
102
- "planner",
103
- "critic",
104
- ];
115
+ export { GJC_MODEL_ASSIGNMENT_TARGET_IDS };
105
116
 
106
117
  export const GJC_MODEL_ASSIGNMENT_TARGETS: Record<GjcModelAssignmentTargetId, GjcModelAssignmentTargetInfo> = {
107
118
  default: { id: "default", tag: "DEFAULT", name: "Default", color: "success", settingsPath: "modelRoles" },
@@ -515,21 +526,11 @@ interface DiscoveryProviderConfig {
515
526
  optional?: boolean;
516
527
  }
517
528
 
518
- export type ProviderDiscoveryStatus = "idle" | "ok" | "empty" | "cached" | "unavailable" | "unauthenticated";
519
-
520
- export interface ProviderDiscoveryState {
521
- provider: string;
522
- status: ProviderDiscoveryStatus;
523
- optional: boolean;
524
- stale: boolean;
525
- fetchedAt?: number;
526
- models: string[];
527
- error?: string;
528
- }
529
-
530
529
  export interface CanonicalModelQueryOptions {
531
530
  availableOnly?: boolean;
532
531
  candidates?: readonly Model<Api>[];
532
+ /** Stable session identity used to keep a canonical variant sticky within a session. */
533
+ sessionId?: string;
533
534
  }
534
535
 
535
536
  /** Result of loading custom models from models.json */
@@ -1008,32 +1009,27 @@ function getConfiguredProviderOrderFromSettings(): string[] {
1008
1009
  export class ModelRegistry {
1009
1010
  #models: Model<Api>[] = [];
1010
1011
  #canonicalIndex: CanonicalModelIndex = { records: [], byId: new Map(), bySelector: new Map() };
1012
+ #availableModelsCache: Model<Api>[] | undefined;
1013
+ #availableModelsDisabledProviders: string | undefined;
1014
+ #availableModelsEnvFingerprint: string | undefined;
1015
+ #sessionCanonicalVariants = new Map<string, string>();
1011
1016
  #customProviderApiKeys: Map<string, string> = new Map();
1012
1017
  #providerWebSearchModes: Map<string, WebSearchMode> = new Map();
1013
1018
  #keylessProviders: Set<string> = new Set();
1014
- #discoverableProviders: DiscoveryProviderConfig[] = [];
1019
+ #discoveryManager = new ModelDiscoveryManager<DiscoveryProviderConfig>();
1015
1020
  #customModelOverlays: CustomModelOverlay[] = [];
1016
1021
  #providerOverrides: Map<string, ProviderOverride> = new Map();
1017
1022
  #modelOverrides: Map<string, Map<string, ModelOverride>> = new Map();
1018
1023
  #equivalenceConfig: ModelEquivalenceConfig | undefined;
1019
- #configuredModelBindings: NonNullable<ModelsConfig["modelBindings"]> | undefined;
1024
+ #modelBindingsApplier = new ModelBindingsApplier();
1020
1025
  #modelProfiles: Map<string, ModelProfileDefinition> = mergeModelProfiles();
1021
- #modelBindingsTargetSettings: Settings | undefined;
1022
- #appliedModelBindingRoles = new Set<string>();
1023
- #appliedAgentModelBindingOverrides = new Set<string>();
1024
- #modelBindingRoleBaselines = new Map<string, ModelSelectorValue | undefined>();
1025
- #agentModelBindingBaselines = new Map<string, ModelSelectorValue | undefined>();
1026
- #lastAppliedModelBindingRoles = new Map<string, ModelSelectorValue>();
1027
- #lastAppliedAgentModelBindingOverrides = new Map<string, ModelSelectorValue>();
1028
1026
  #configError: ConfigError | undefined = undefined;
1029
1027
  #modelsConfigFile: ConfigFile<ModelsConfig>;
1030
1028
  #lastStaticLoadMtime: number | null = null;
1031
1029
  #registeredProviderSources: Set<string> = new Set();
1032
- #providerDiscoveryStates: Map<string, ProviderDiscoveryState> = new Map();
1033
1030
  #cacheDbPath?: string;
1034
1031
  #suppressedSelectors: Map<string, number> = new Map();
1035
1032
  #backgroundRefresh?: Promise<void>;
1036
- #lastDiscoveryWarnings: Map<string, string> = new Map();
1037
1033
  // Runtime extension model overlays — persist across refresh() cycles so that
1038
1034
  // models registered by extensions survive the model selector's offline reload.
1039
1035
  #runtimeModelOverlays: CustomModelOverlay[] = [];
@@ -1058,6 +1054,7 @@ export class ModelRegistry {
1058
1054
  const keyConfig = this.#customProviderApiKeys.get(provider);
1059
1055
  return keyConfig;
1060
1056
  });
1057
+ this.authStorage.onGenerationChanged(() => this.#invalidateAvailableModels());
1061
1058
  // Load models synchronously in constructor
1062
1059
  this.#loadModels();
1063
1060
  }
@@ -1071,7 +1068,7 @@ export class ModelRegistry {
1071
1068
  this.#reloadStaticModels();
1072
1069
  this.#suppressedSelectors.clear();
1073
1070
  await this.#refreshRuntimeDiscoveries(strategy);
1074
- this.#applyConfiguredModelBindingsToTarget();
1071
+ this.#modelBindingsApplier.apply();
1075
1072
  } finally {
1076
1073
  this.#resumeRebuild();
1077
1074
  }
@@ -1105,7 +1102,7 @@ export class ModelRegistry {
1105
1102
  }
1106
1103
  }
1107
1104
  await this.#refreshRuntimeDiscoveries(strategy, new Set([providerId]));
1108
- this.#applyConfiguredModelBindingsToTarget();
1105
+ this.#modelBindingsApplier.apply();
1109
1106
  } finally {
1110
1107
  this.#resumeRebuild();
1111
1108
  }
@@ -1121,7 +1118,7 @@ export class ModelRegistry {
1121
1118
  this.#customProviderApiKeys.clear();
1122
1119
  this.#providerWebSearchModes.clear();
1123
1120
  this.#keylessProviders.clear();
1124
- this.#discoverableProviders = [];
1121
+ this.#discoveryManager.reset();
1125
1122
  // Drop config-sourced apiKeys from AuthStorage before reload; entries
1126
1123
  // removed from models.yml must actually disappear from the resolver, not
1127
1124
  // linger from the previous parse. The post-load setters below repopulate.
@@ -1134,9 +1131,8 @@ export class ModelRegistry {
1134
1131
  this.#providerOverrides.clear();
1135
1132
  this.#modelOverrides.clear();
1136
1133
  this.#equivalenceConfig = undefined;
1137
- this.#configuredModelBindings = undefined;
1134
+ this.#modelBindingsApplier.setBindings(undefined);
1138
1135
  this.#configError = undefined;
1139
- this.#providerDiscoveryStates.clear();
1140
1136
  this.#loadModels();
1141
1137
  }
1142
1138
 
@@ -1163,12 +1159,12 @@ export class ModelRegistry {
1163
1159
  } = this.#loadCustomModels();
1164
1160
  this.#configError = configError;
1165
1161
  this.#keylessProviders = keylessProviders;
1166
- this.#discoverableProviders = discoverableProviders;
1162
+ this.#discoveryManager.setProviders(discoverableProviders);
1167
1163
  this.#customModelOverlays = customModels;
1168
1164
  this.#providerOverrides = overrides;
1169
1165
  this.#modelOverrides = modelOverrides;
1170
1166
  this.#equivalenceConfig = equivalence;
1171
- this.#configuredModelBindings = modelBindings;
1167
+ this.#modelBindingsApplier.setBindings(modelBindings);
1172
1168
  this.#modelProfiles = mergeModelProfiles(profiles);
1173
1169
 
1174
1170
  this.#addImplicitDiscoverableProviders(configuredProviders);
@@ -1283,7 +1279,7 @@ export class ModelRegistry {
1283
1279
  }
1284
1280
 
1285
1281
  #loadCachedStandardProviderModels(): Model<Api>[] {
1286
- const configuredDiscoveryProviders = new Set(this.#discoverableProviders.map(provider => provider.provider));
1282
+ const configuredDiscoveryProviders = new Set(this.#discoveryManager.providers.map(provider => provider.provider));
1287
1283
  const cachedModels: Model<Api>[] = [];
1288
1284
  for (const descriptor of PROVIDER_DESCRIPTORS) {
1289
1285
  if (configuredDiscoveryProviders.has(descriptor.providerId)) {
@@ -1310,34 +1306,16 @@ export class ModelRegistry {
1310
1306
 
1311
1307
  #loadCachedDiscoverableModels(): Model<Api>[] {
1312
1308
  const cachedModels: Model<Api>[] = [];
1313
- for (const providerConfig of this.#discoverableProviders) {
1314
- const cache = readModelCache<Api>(providerConfig.provider, 24 * 60 * 60 * 1000, Date.now, this.#cacheDbPath);
1315
- if (!cache) {
1316
- this.#providerDiscoveryStates.set(providerConfig.provider, {
1317
- provider: providerConfig.provider,
1318
- status: "idle",
1319
- optional: providerConfig.optional ?? false,
1320
- stale: false,
1321
- models: [],
1322
- });
1323
- continue;
1324
- }
1325
- const models = this.#applyProviderModelOverrides(
1309
+ for (const providerConfig of this.#discoveryManager.providers) {
1310
+ const models = this.#discoveryManager.loadCached(providerConfig, this.#cacheDbPath);
1311
+ const normalized = this.#applyProviderModelOverrides(
1326
1312
  providerConfig.provider,
1327
1313
  this.#normalizeDiscoverableModels(
1328
1314
  providerConfig,
1329
- this.#applyProviderCompat(providerConfig.compat, cache.models),
1315
+ this.#applyProviderCompat(providerConfig.compat, [...models]),
1330
1316
  ),
1331
1317
  );
1332
- cachedModels.push(...models);
1333
- this.#providerDiscoveryStates.set(providerConfig.provider, {
1334
- provider: providerConfig.provider,
1335
- status: "cached",
1336
- optional: providerConfig.optional ?? false,
1337
- stale: !cache.fresh || !cache.authoritative,
1338
- fetchedAt: cache.updatedAt,
1339
- models: models.map(model => model.id),
1340
- });
1318
+ cachedModels.push(...normalized);
1341
1319
  }
1342
1320
  return cachedModels;
1343
1321
  }
@@ -1368,7 +1346,7 @@ export class ModelRegistry {
1368
1346
  #addImplicitDiscoverableProviders(configuredProviders: Set<string>): void {
1369
1347
  const disabledProviders = getDisabledProviderIdsFromSettings();
1370
1348
  if (!configuredProviders.has("ollama") && !disabledProviders.has("ollama")) {
1371
- this.#discoverableProviders.push({
1349
+ this.#discoveryManager.addProvider({
1372
1350
  provider: "ollama",
1373
1351
  api: "openai-responses",
1374
1352
  baseUrl: Bun.env.OLLAMA_BASE_URL || "http://127.0.0.1:11434",
@@ -1378,7 +1356,7 @@ export class ModelRegistry {
1378
1356
  this.#keylessProviders.add("ollama");
1379
1357
  }
1380
1358
  if (!configuredProviders.has("llama.cpp") && !disabledProviders.has("llama.cpp")) {
1381
- this.#discoverableProviders.push({
1359
+ this.#discoveryManager.addProvider({
1382
1360
  provider: "llama.cpp",
1383
1361
  api: "openai-responses",
1384
1362
  baseUrl: Bun.env.LLAMA_CPP_BASE_URL || "http://127.0.0.1:8080",
@@ -1391,7 +1369,7 @@ export class ModelRegistry {
1391
1369
  }
1392
1370
  }
1393
1371
  if (!configuredProviders.has("lm-studio") && !disabledProviders.has("lm-studio")) {
1394
- this.#discoverableProviders.push({
1372
+ this.#discoveryManager.addProvider({
1395
1373
  provider: "lm-studio",
1396
1374
  api: "openai-completions",
1397
1375
  baseUrl: Bun.env.LM_STUDIO_BASE_URL || "http://127.0.0.1:1234/v1",
@@ -1696,95 +1674,7 @@ export class ModelRegistry {
1696
1674
  this.#reloadStaticModels();
1697
1675
  }
1698
1676
  applyConfiguredModelBindings(targetSettings: Settings): void {
1699
- this.#modelBindingsTargetSettings = targetSettings;
1700
- this.#applyConfiguredModelBindingsToTarget();
1701
- }
1702
-
1703
- #cloneModelSelectorValue(value: ModelSelectorValue | undefined): ModelSelectorValue | undefined {
1704
- return Array.isArray(value) ? [...value] : value;
1705
- }
1706
-
1707
- #modelSelectorValuesEqual(left: ModelSelectorValue | undefined, right: ModelSelectorValue | undefined): boolean {
1708
- const leftSelectors = normalizeModelSelectorValue(left);
1709
- const rightSelectors = normalizeModelSelectorValue(right);
1710
- return (
1711
- leftSelectors.length === rightSelectors.length &&
1712
- leftSelectors.every((selector, index) => selector === rightSelectors[index])
1713
- );
1714
- }
1715
-
1716
- #applyConfiguredModelBindingsToTarget(): void {
1717
- const targetSettings = this.#modelBindingsTargetSettings;
1718
- if (!targetSettings) return;
1719
- const bindings = this.#configuredModelBindings;
1720
- const nextModelRoles = { ...targetSettings.get("modelRoles") };
1721
- const configuredModelRoles = bindings?.modelRoles ?? {};
1722
- const configuredModelRoleKeys = new Set(Object.keys(configuredModelRoles));
1723
- for (const role of this.#appliedModelBindingRoles) {
1724
- if (configuredModelRoleKeys.has(role)) continue;
1725
- const lastApplied = this.#lastAppliedModelBindingRoles.get(role);
1726
- if (lastApplied !== undefined && this.#modelSelectorValuesEqual(nextModelRoles[role], lastApplied)) {
1727
- const baseline = this.#modelBindingRoleBaselines.get(role);
1728
- if (baseline === undefined) {
1729
- delete nextModelRoles[role];
1730
- } else {
1731
- nextModelRoles[role] = this.#cloneModelSelectorValue(baseline)!;
1732
- }
1733
- }
1734
- this.#modelBindingRoleBaselines.delete(role);
1735
- this.#lastAppliedModelBindingRoles.delete(role);
1736
- }
1737
- for (const [role, selector] of Object.entries(configuredModelRoles)) {
1738
- const previousApplied = this.#lastAppliedModelBindingRoles.get(role);
1739
- if (!this.#modelBindingRoleBaselines.has(role)) {
1740
- this.#modelBindingRoleBaselines.set(role, this.#cloneModelSelectorValue(nextModelRoles[role]));
1741
- }
1742
- if (previousApplied === undefined || this.#modelSelectorValuesEqual(nextModelRoles[role], previousApplied)) {
1743
- nextModelRoles[role] = this.#cloneModelSelectorValue(selector)!;
1744
- this.#lastAppliedModelBindingRoles.set(role, this.#cloneModelSelectorValue(selector)!);
1745
- }
1746
- }
1747
- targetSettings.override("modelRoles", nextModelRoles);
1748
- this.#appliedModelBindingRoles = new Set(Object.keys(configuredModelRoles));
1749
-
1750
- const nextAgentModelOverrides = { ...targetSettings.get("task.agentModelOverrides") };
1751
- const configuredAgentModelOverrides = bindings?.agentModelOverrides ?? {};
1752
- const configuredAgentModelOverrideKeys = new Set(Object.keys(configuredAgentModelOverrides));
1753
- for (const agentName of this.#appliedAgentModelBindingOverrides) {
1754
- if (configuredAgentModelOverrideKeys.has(agentName)) continue;
1755
- const lastApplied = this.#lastAppliedAgentModelBindingOverrides.get(agentName);
1756
- if (
1757
- lastApplied !== undefined &&
1758
- this.#modelSelectorValuesEqual(nextAgentModelOverrides[agentName], lastApplied)
1759
- ) {
1760
- const baseline = this.#agentModelBindingBaselines.get(agentName);
1761
- if (baseline === undefined) {
1762
- delete nextAgentModelOverrides[agentName];
1763
- } else {
1764
- nextAgentModelOverrides[agentName] = this.#cloneModelSelectorValue(baseline)!;
1765
- }
1766
- }
1767
- this.#agentModelBindingBaselines.delete(agentName);
1768
- this.#lastAppliedAgentModelBindingOverrides.delete(agentName);
1769
- }
1770
- for (const [agentName, selector] of Object.entries(configuredAgentModelOverrides)) {
1771
- const previousApplied = this.#lastAppliedAgentModelBindingOverrides.get(agentName);
1772
- if (!this.#agentModelBindingBaselines.has(agentName)) {
1773
- this.#agentModelBindingBaselines.set(
1774
- agentName,
1775
- this.#cloneModelSelectorValue(nextAgentModelOverrides[agentName]),
1776
- );
1777
- }
1778
- if (
1779
- previousApplied === undefined ||
1780
- this.#modelSelectorValuesEqual(nextAgentModelOverrides[agentName], previousApplied)
1781
- ) {
1782
- nextAgentModelOverrides[agentName] = this.#cloneModelSelectorValue(selector)!;
1783
- this.#lastAppliedAgentModelBindingOverrides.set(agentName, this.#cloneModelSelectorValue(selector)!);
1784
- }
1785
- }
1786
- targetSettings.override("task.agentModelOverrides", nextAgentModelOverrides);
1787
- this.#appliedAgentModelBindingOverrides = new Set(Object.keys(configuredAgentModelOverrides));
1677
+ this.#modelBindingsApplier.applyTo(targetSettings);
1788
1678
  }
1789
1679
 
1790
1680
  async #refreshRuntimeDiscoveries(
@@ -1794,8 +1684,8 @@ export class ModelRegistry {
1794
1684
  const disabledProviders = getDisabledProviderIdsFromSettings();
1795
1685
  const selectedDiscoverableProviders = (
1796
1686
  providerFilter
1797
- ? this.#discoverableProviders.filter(provider => providerFilter.has(provider.provider))
1798
- : this.#discoverableProviders
1687
+ ? this.#discoveryManager.providers.filter(provider => providerFilter.has(provider.provider))
1688
+ : this.#discoveryManager.providers
1799
1689
  ).filter(provider => !disabledProviders.has(provider.provider));
1800
1690
  const configuredDiscoveriesPromise =
1801
1691
  selectedDiscoverableProviders.length === 0
@@ -1833,74 +1723,26 @@ export class ModelRegistry {
1833
1723
  providerConfig: DiscoveryProviderConfig,
1834
1724
  strategy: ModelRefreshStrategy,
1835
1725
  ): Promise<Model<Api>[]> {
1836
- const cached = readModelCache<Api>(providerConfig.provider, 24 * 60 * 60 * 1000, Date.now, this.#cacheDbPath);
1837
- const cachedModels = applyFinalCodexGpt56ContextCap(cached?.models ?? []);
1838
- const requiresAuth = !this.#keylessProviders.has(providerConfig.provider);
1839
- if (requiresAuth) {
1840
- const apiKey = await this.#peekApiKeyForProvider(providerConfig.provider);
1841
- if (!isAuthenticated(apiKey)) {
1842
- this.#providerDiscoveryStates.set(providerConfig.provider, {
1843
- provider: providerConfig.provider,
1844
- status: "unauthenticated",
1845
- optional: providerConfig.optional ?? false,
1846
- stale: cached !== null,
1847
- fetchedAt: cached?.updatedAt,
1848
- models: cachedModels.map(model => model.id),
1849
- });
1850
- this.#lastDiscoveryWarnings.delete(providerConfig.provider);
1851
- return cachedModels;
1852
- }
1853
- }
1854
-
1855
- const providerId = providerConfig.provider;
1856
- let discoveryError: string | undefined;
1857
- const fetchDynamicModels = async (): Promise<readonly Model<Api>[] | null> => {
1858
- try {
1859
- const models = await this.#discoverModelsByProviderType(providerConfig);
1860
- this.#lastDiscoveryWarnings.delete(providerId);
1861
- return models;
1862
- } catch (error) {
1863
- discoveryError = error instanceof Error ? error.message : String(error);
1864
- return null;
1865
- }
1866
- };
1867
-
1868
- const manager = createModelManager<Api>({
1869
- providerId,
1870
- staticModels: [],
1726
+ const mergeInput = await this.#discoveryManager.discover(providerConfig, strategy, {
1871
1727
  cacheDbPath: this.#cacheDbPath,
1872
- cacheTtlMs: 24 * 60 * 60 * 1000,
1873
- fetchDynamicModels,
1728
+ requiresAuth: provider => !this.#keylessProviders.has(provider.provider),
1729
+ peekApiKey: provider => this.#peekApiKeyForProvider(provider.provider),
1730
+ isAuthenticated,
1731
+ fetchModels: provider => this.#discoverModelsByProviderType(provider),
1874
1732
  });
1875
- const result = await manager.refresh(strategy);
1876
- const status = discoveryError
1877
- ? result.models.length > 0
1878
- ? "cached"
1879
- : "unavailable"
1880
- : strategy === "offline"
1881
- ? cached
1882
- ? "cached"
1883
- : "idle"
1884
- : result.models.length > 0
1885
- ? "ok"
1886
- : "empty";
1887
- this.#providerDiscoveryStates.set(providerId, {
1888
- provider: providerId,
1889
- status,
1890
- optional: providerConfig.optional ?? false,
1891
- stale: result.stale || status === "cached",
1892
- fetchedAt: discoveryError ? cached?.updatedAt : Date.now(),
1893
- models: result.models.map(model => model.id),
1894
- error: discoveryError,
1895
- });
1896
- if (discoveryError) {
1897
- this.#warnProviderDiscoveryFailure(providerConfig, discoveryError);
1733
+ if (!mergeInput.current) return [];
1734
+ if (mergeInput.warning) {
1735
+ logger.warn("model discovery failed for provider", {
1736
+ provider: providerConfig.provider,
1737
+ url: providerConfig.baseUrl,
1738
+ error: mergeInput.warning,
1739
+ });
1898
1740
  }
1899
1741
  return this.#applyProviderModelOverrides(
1900
- providerId,
1742
+ providerConfig.provider,
1901
1743
  this.#normalizeDiscoverableModels(
1902
1744
  providerConfig,
1903
- this.#applyProviderCompat(providerConfig.compat, result.models),
1745
+ this.#applyProviderCompat(providerConfig.compat, [...mergeInput.models]),
1904
1746
  ),
1905
1747
  );
1906
1748
  }
@@ -1917,25 +1759,12 @@ export class ModelRegistry {
1917
1759
  }
1918
1760
  }
1919
1761
 
1920
- #warnProviderDiscoveryFailure(providerConfig: DiscoveryProviderConfig, error: string): void {
1921
- const previous = this.#lastDiscoveryWarnings.get(providerConfig.provider);
1922
- if (previous === error) {
1923
- return;
1924
- }
1925
- this.#lastDiscoveryWarnings.set(providerConfig.provider, error);
1926
- logger.warn("model discovery failed for provider", {
1927
- provider: providerConfig.provider,
1928
- url: providerConfig.baseUrl,
1929
- error,
1930
- });
1931
- }
1932
-
1933
1762
  async #discoverBuiltInProviderModels(
1934
1763
  strategy: ModelRefreshStrategy,
1935
1764
  providerFilter?: ReadonlySet<string>,
1936
1765
  ): Promise<Model<Api>[]> {
1937
1766
  // Skip providers already handled by configured discovery (e.g. user-configured ollama with discovery.type)
1938
- const configuredDiscoveryProviders = new Set(this.#discoverableProviders.map(p => p.provider));
1767
+ const configuredDiscoveryProviders = new Set(this.#discoveryManager.providers.map(p => p.provider));
1939
1768
  const managerOptions = (await this.#collectBuiltInModelManagerOptions()).filter(opts => {
1940
1769
  if (configuredDiscoveryProviders.has(opts.providerId)) {
1941
1770
  return false;
@@ -2424,9 +2253,16 @@ export class ModelRegistry {
2424
2253
  return;
2425
2254
  }
2426
2255
  this.#canonicalIndex = buildCanonicalModelIndex(this.#models, this.#equivalenceConfig);
2256
+ this.#invalidateAvailableModels();
2427
2257
  this.#rebuildPending = false;
2428
2258
  }
2429
2259
 
2260
+ #invalidateAvailableModels(): void {
2261
+ this.#availableModelsCache = undefined;
2262
+ this.#availableModelsDisabledProviders = undefined;
2263
+ this.#availableModelsEnvFingerprint = undefined;
2264
+ }
2265
+
2430
2266
  #suspendRebuild(): void {
2431
2267
  this.#rebuildSuspended += 1;
2432
2268
  }
@@ -2438,6 +2274,7 @@ export class ModelRegistry {
2438
2274
  if (this.#rebuildSuspended === 0 && this.#rebuildPending) {
2439
2275
  this.#rebuildPending = false;
2440
2276
  this.#canonicalIndex = buildCanonicalModelIndex(this.#models, this.#equivalenceConfig);
2277
+ this.#invalidateAvailableModels();
2441
2278
  }
2442
2279
  }
2443
2280
 
@@ -2488,8 +2325,7 @@ export class ModelRegistry {
2488
2325
  return this.#models;
2489
2326
  }
2490
2327
 
2491
- #isModelAvailable(model: Model<Api>): boolean {
2492
- const disabledProviders = getDisabledProviderIdsFromSettings();
2328
+ #isModelAvailable(model: Model<Api>, disabledProviders = getDisabledProviderIdsFromSettings()): boolean {
2493
2329
  return (
2494
2330
  !disabledProviders.has(model.provider) &&
2495
2331
  (this.#keylessProviders.has(model.provider) || this.authStorage.hasAuth(model.provider))
@@ -2503,13 +2339,10 @@ export class ModelRegistry {
2503
2339
  const candidateKeys = options?.candidates
2504
2340
  ? new Set(options.candidates.map(candidate => formatCanonicalVariantSelector(candidate)))
2505
2341
  : undefined;
2342
+ const disabledProviders = options?.availableOnly ? getDisabledProviderIdsFromSettings() : undefined;
2506
2343
  return record.variants.filter(variant => {
2507
- if (candidateKeys && !candidateKeys.has(variant.selector)) {
2508
- return false;
2509
- }
2510
- if (options?.availableOnly && !this.#isModelAvailable(variant.model)) {
2511
- return false;
2512
- }
2344
+ if (candidateKeys && !candidateKeys.has(variant.selector)) return false;
2345
+ if (options?.availableOnly && !this.#isModelAvailable(variant.model, disabledProviders)) return false;
2513
2346
  return true;
2514
2347
  });
2515
2348
  }
@@ -2537,13 +2370,26 @@ export class ModelRegistry {
2537
2370
  return result;
2538
2371
  }
2539
2372
 
2373
+ #rememberCanonicalVariant(sessionId: string, selector: string): void {
2374
+ this.#sessionCanonicalVariants.delete(sessionId);
2375
+ this.#sessionCanonicalVariants.set(sessionId, selector);
2376
+ if (this.#sessionCanonicalVariants.size > MAX_SESSION_CANONICAL_VARIANTS) {
2377
+ this.#sessionCanonicalVariants.delete(this.#sessionCanonicalVariants.keys().next().value!);
2378
+ }
2379
+ }
2540
2380
  #resolveCanonicalVariant(
2541
2381
  variants: readonly CanonicalModelVariant[],
2542
2382
  allCandidates: readonly Model<Api>[],
2383
+ sessionId?: string,
2543
2384
  ): CanonicalModelVariant | undefined {
2544
- if (variants.length === 0) {
2545
- return undefined;
2546
- }
2385
+ if (variants.length === 0) return undefined;
2386
+ const stickySelector = sessionId ? this.#sessionCanonicalVariants.get(sessionId) : undefined;
2387
+ const stickyVariant = stickySelector ? variants.find(variant => variant.selector === stickySelector) : undefined;
2388
+ if (stickyVariant) {
2389
+ this.#rememberCanonicalVariant(sessionId!, stickyVariant.selector);
2390
+ return stickyVariant;
2391
+ }
2392
+ if (sessionId && stickySelector) this.#sessionCanonicalVariants.delete(sessionId);
2547
2393
  const providerRank = this.#providerRank(allCandidates);
2548
2394
  const modelOrder = new Map<string, number>();
2549
2395
  for (let index = 0; index < allCandidates.length; index += 1) {
@@ -2555,35 +2401,16 @@ export class ModelRegistry {
2555
2401
  heuristic: 2,
2556
2402
  fallback: 3,
2557
2403
  };
2558
- return [...variants].sort((left, right) => {
2559
- // Prefer vision-capable variants over configured provider order so an
2560
- // ambiguous canonical id never resolves to a text-only namesake when a
2561
- // vision-capable variant of the same id is available.
2562
- const leftVision = left.model.input.includes("image") ? 0 : 1;
2563
- const rightVision = right.model.input.includes("image") ? 0 : 1;
2564
- if (leftVision !== rightVision) {
2565
- return leftVision - rightVision;
2566
- }
2567
- const leftProviderRank = providerRank.get(left.model.provider.toLowerCase()) ?? Number.MAX_SAFE_INTEGER;
2568
- const rightProviderRank = providerRank.get(right.model.provider.toLowerCase()) ?? Number.MAX_SAFE_INTEGER;
2569
- if (leftProviderRank !== rightProviderRank) {
2570
- return leftProviderRank - rightProviderRank;
2571
- }
2572
- const leftExact = left.model.id === left.canonicalId ? 0 : 1;
2573
- const rightExact = right.model.id === right.canonicalId ? 0 : 1;
2574
- if (leftExact !== rightExact) {
2575
- return leftExact - rightExact;
2576
- }
2577
- if (sourceRank[left.source] !== sourceRank[right.source]) {
2578
- return sourceRank[left.source] - sourceRank[right.source];
2579
- }
2580
- if (left.model.id.length !== right.model.id.length) {
2581
- return left.model.id.length - right.model.id.length;
2582
- }
2583
- const leftOrder = modelOrder.get(left.selector) ?? Number.MAX_SAFE_INTEGER;
2584
- const rightOrder = modelOrder.get(right.selector) ?? Number.MAX_SAFE_INTEGER;
2585
- return leftOrder - rightOrder;
2586
- })[0];
2404
+ return [...variants].sort((left, right) =>
2405
+ compareEquivalentModelVariants(left.model, right.model, {
2406
+ providerRank,
2407
+ canonicalId: left.canonicalId === right.canonicalId ? left.canonicalId : undefined,
2408
+ leftSourceRank: sourceRank[left.source],
2409
+ rightSourceRank: sourceRank[right.source],
2410
+ includeCost: true,
2411
+ modelOrder,
2412
+ }),
2413
+ )[0];
2587
2414
  }
2588
2415
 
2589
2416
  getCanonicalModels(options?: CanonicalModelQueryOptions): CanonicalModelRecord[] {
@@ -2612,23 +2439,49 @@ export class ModelRegistry {
2612
2439
 
2613
2440
  resolveCanonicalModel(canonicalId: string, options?: CanonicalModelQueryOptions): Model<Api> | undefined {
2614
2441
  const variants = this.getCanonicalVariants(canonicalId, options);
2615
- if (variants.length === 0) {
2616
- return undefined;
2617
- }
2442
+ if (variants.length === 0) return undefined;
2618
2443
  const candidates = options?.candidates ?? (options?.availableOnly ? this.getAvailable() : this.getAll());
2619
- return this.#resolveCanonicalVariant(variants, candidates)?.model;
2444
+ const resolved = this.#resolveCanonicalVariant(variants, candidates, options?.sessionId);
2445
+ if (resolved && options?.sessionId) this.#rememberCanonicalVariant(options.sessionId, resolved.selector);
2446
+ return resolved?.model;
2620
2447
  }
2621
2448
 
2622
2449
  getCanonicalId(model: Model<Api>): string | undefined {
2623
2450
  return this.#canonicalIndex.bySelector.get(formatCanonicalVariantSelector(model).toLowerCase());
2624
2451
  }
2625
2452
 
2453
+ /**
2454
+ * Seed a child canonical scope from a concrete parent model without touching
2455
+ * the parent's canonical selection.
2456
+ */
2457
+ seedCanonicalVariant(sessionId: string, model: Model<Api>): boolean {
2458
+ const scope = sessionId.trim();
2459
+ if (!scope) return false;
2460
+ const selector = formatCanonicalVariantSelector(model);
2461
+ if (!this.#canonicalIndex.bySelector.has(selector.toLowerCase())) return false;
2462
+ this.#rememberCanonicalVariant(scope, selector);
2463
+ return true;
2464
+ }
2465
+
2626
2466
  /**
2627
2467
  * Get only models that have auth configured.
2628
2468
  * This is a fast check that doesn't refresh OAuth tokens.
2629
2469
  */
2630
2470
  getAvailable(): Model<Api>[] {
2631
- return this.#models.filter(model => this.#isModelAvailable(model));
2471
+ const disabledProviders = getDisabledProviderIdsFromSettings();
2472
+ const disabledProviderKey = [...disabledProviders].sort().join("\u0000");
2473
+ const envFingerprint = envAvailabilityFingerprint();
2474
+ if (
2475
+ this.#availableModelsCache &&
2476
+ this.#availableModelsDisabledProviders === disabledProviderKey &&
2477
+ this.#availableModelsEnvFingerprint === envFingerprint
2478
+ ) {
2479
+ return this.#availableModelsCache;
2480
+ }
2481
+ this.#availableModelsCache = this.#models.filter(model => this.#isModelAvailable(model, disabledProviders));
2482
+ this.#availableModelsDisabledProviders = disabledProviderKey;
2483
+ this.#availableModelsEnvFingerprint = envFingerprint;
2484
+ return this.#availableModelsCache;
2632
2485
  }
2633
2486
 
2634
2487
  /**
@@ -2646,13 +2499,13 @@ export class ModelRegistry {
2646
2499
 
2647
2500
  getDiscoverableProviders(): string[] {
2648
2501
  const disabledProviders = getDisabledProviderIdsFromSettings();
2649
- return this.#discoverableProviders
2502
+ return this.#discoveryManager.providers
2650
2503
  .filter(provider => !disabledProviders.has(provider.provider))
2651
2504
  .map(provider => provider.provider);
2652
2505
  }
2653
2506
 
2654
2507
  getProviderDiscoveryState(provider: string): ProviderDiscoveryState | undefined {
2655
- return this.#providerDiscoveryStates.get(provider);
2508
+ return this.#discoveryManager.getState(provider);
2656
2509
  }
2657
2510
 
2658
2511
  /**
@@ -2688,6 +2541,13 @@ export class ModelRegistry {
2688
2541
  };
2689
2542
  }
2690
2543
 
2544
+ async #getApiKeyOrNoAuth(provider: string, lookup: () => Promise<string | undefined>): Promise<string | undefined> {
2545
+ if (this.#keylessProviders.has(provider) && !this.authStorage.hasAuth(provider)) {
2546
+ return kNoAuth;
2547
+ }
2548
+ return lookup();
2549
+ }
2550
+
2691
2551
  /**
2692
2552
  * Get API key for a model.
2693
2553
  */
@@ -2696,14 +2556,13 @@ export class ModelRegistry {
2696
2556
  sessionId?: string,
2697
2557
  options: { credentialSelector?: AuthCredentialSelector } = {},
2698
2558
  ): Promise<string | undefined> {
2699
- if (this.#keylessProviders.has(model.provider) && !this.authStorage.hasAuth(model.provider)) {
2700
- return kNoAuth;
2701
- }
2702
- return this.authStorage.getApiKey(model.provider, sessionId, {
2703
- baseUrl: model.baseUrl,
2704
- modelId: model.id,
2705
- credentialSelector: options.credentialSelector,
2706
- });
2559
+ return this.#getApiKeyOrNoAuth(model.provider, () =>
2560
+ this.authStorage.getApiKey(model.provider, sessionId, {
2561
+ baseUrl: model.baseUrl,
2562
+ modelId: model.id,
2563
+ credentialSelector: options.credentialSelector,
2564
+ }),
2565
+ );
2707
2566
  }
2708
2567
 
2709
2568
  /**
@@ -2715,20 +2574,16 @@ export class ModelRegistry {
2715
2574
  baseUrl?: string,
2716
2575
  options: { credentialSelector?: AuthCredentialSelector } = {},
2717
2576
  ): Promise<string | undefined> {
2718
- if (this.#keylessProviders.has(provider) && !this.authStorage.hasAuth(provider)) {
2719
- return kNoAuth;
2720
- }
2721
- return this.authStorage.getApiKey(provider, sessionId, {
2722
- baseUrl,
2723
- credentialSelector: options.credentialSelector,
2724
- });
2577
+ return this.#getApiKeyOrNoAuth(provider, () =>
2578
+ this.authStorage.getApiKey(provider, sessionId, {
2579
+ baseUrl,
2580
+ credentialSelector: options.credentialSelector,
2581
+ }),
2582
+ );
2725
2583
  }
2726
2584
 
2727
2585
  async #peekApiKeyForProvider(provider: string): Promise<string | undefined> {
2728
- if (this.#keylessProviders.has(provider) && !this.authStorage.hasAuth(provider)) {
2729
- return kNoAuth;
2730
- }
2731
- return this.authStorage.peekApiKey(provider);
2586
+ return this.#getApiKeyOrNoAuth(provider, () => this.authStorage.peekApiKey(provider));
2732
2587
  }
2733
2588
 
2734
2589
  /**
@@ -2963,6 +2818,18 @@ export class ModelRegistry {
2963
2818
  }
2964
2819
  return true;
2965
2820
  }
2821
+
2822
+ /** Return whether a selector has an active, expired, or no rate-limit suppression. */
2823
+ getSelectorSuppressionStatus(selector: string): "active" | "expired" | "none" {
2824
+ const normalizedSelector = normalizeSuppressedSelector(selector);
2825
+ const suppressedUntil = this.#suppressedSelectors.get(normalizedSelector);
2826
+ if (!suppressedUntil) return "none";
2827
+ if (suppressedUntil <= Date.now()) {
2828
+ this.#suppressedSelectors.delete(normalizedSelector);
2829
+ return "expired";
2830
+ }
2831
+ return "active";
2832
+ }
2966
2833
  }
2967
2834
 
2968
2835
  /**