mixdog 1.2.0 → 1.2.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 (386) hide show
  1. package/README.md +7 -5
  2. package/package.json +1 -1
  3. package/scripts/gate-local.mjs +19 -3
  4. package/scripts/model-catalog-audit.mjs +1 -15
  5. package/src/agents/reviewer/AGENT.md +3 -3
  6. package/src/agents/reviewer/agent.json +1 -1
  7. package/src/defaults/shipped-history.json +5 -0
  8. package/src/defaults/skills/pptx/SKILL.md +6 -6
  9. package/src/defaults/skills/pptx/references/review.md +6 -4
  10. package/src/defaults/skills/pptx/scripts/source-extract.mjs +1 -1
  11. package/src/defaults/skills/setup/references/actions.md +13 -3
  12. package/src/defaults/skills/visualize/SKILL.md +12 -9
  13. package/src/repl/slash-commands.mjs +16 -1
  14. package/src/rules/shared/60-verification.md +3 -1
  15. package/src/rules/shared/70-delivery.md +4 -2
  16. package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +29 -4
  17. package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +10 -0
  18. package/src/runtime/agent/orchestrator/cache-mtime.mjs +26 -5
  19. package/src/runtime/agent/orchestrator/config.mjs +10 -4
  20. package/src/runtime/agent/orchestrator/context/collect.mjs +2 -1
  21. package/src/runtime/agent/orchestrator/context/deferred-tools.mjs +11 -5
  22. package/src/runtime/agent/orchestrator/context/skill-catalog.mjs +74 -21
  23. package/src/runtime/agent/orchestrator/mcp/client-config.mjs +122 -15
  24. package/src/runtime/agent/orchestrator/mcp/client-handshake.mjs +54 -11
  25. package/src/runtime/agent/orchestrator/mcp/client-transport.mjs +104 -9
  26. package/src/runtime/agent/orchestrator/mcp/client.mjs +358 -140
  27. package/src/runtime/agent/orchestrator/mcp/lazy-output-validator.mjs +25 -0
  28. package/src/runtime/agent/orchestrator/mcp/oauth-callback.mjs +103 -0
  29. package/src/runtime/agent/orchestrator/mcp/oauth-credentials.mjs +150 -0
  30. package/src/runtime/agent/orchestrator/mcp/oauth-login.mjs +111 -0
  31. package/src/runtime/agent/orchestrator/mcp/oauth-probe.mjs +87 -0
  32. package/src/runtime/agent/orchestrator/mcp/oauth-provider.mjs +95 -0
  33. package/src/runtime/agent/orchestrator/mcp/oauth-request.mjs +122 -0
  34. package/src/runtime/agent/orchestrator/mcp/oauth-shared.mjs +9 -0
  35. package/src/runtime/agent/orchestrator/mcp/oauth.mjs +229 -0
  36. package/src/runtime/agent/orchestrator/mcp/plugin-placeholders.mjs +54 -0
  37. package/src/runtime/agent/orchestrator/providers/account-pool.mjs +294 -44
  38. package/src/runtime/agent/orchestrator/providers/anthropic-api-recovery.mjs +2 -1
  39. package/src/runtime/agent/orchestrator/providers/anthropic-api-transport.mjs +94 -31
  40. package/src/runtime/agent/orchestrator/providers/anthropic-error-contract.mjs +96 -0
  41. package/src/runtime/agent/orchestrator/providers/anthropic-fast-mode.mjs +39 -18
  42. package/src/runtime/agent/orchestrator/providers/anthropic-max-tokens-recovery.mjs +55 -0
  43. package/src/runtime/agent/orchestrator/providers/anthropic-max-tokens.mjs +25 -21
  44. package/src/runtime/agent/orchestrator/providers/anthropic-messages.mjs +3 -1
  45. package/src/runtime/agent/orchestrator/providers/anthropic-midstream-recovery.mjs +18 -4
  46. package/src/runtime/agent/orchestrator/providers/anthropic-model-resolve.mjs +13 -1
  47. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-client-version.mjs +1 -1
  48. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-midstream.mjs +10 -1
  49. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-recovery.mjs +23 -9
  50. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-request/initial-status.mjs +46 -20
  51. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-request.mjs +14 -9
  52. package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +73 -35
  53. package/src/runtime/agent/orchestrator/providers/anthropic-overload-fallback.mjs +69 -0
  54. package/src/runtime/agent/orchestrator/providers/anthropic-server-fallback.mjs +4 -0
  55. package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +8 -1
  56. package/src/runtime/agent/orchestrator/providers/anthropic.mjs +53 -18
  57. package/src/runtime/agent/orchestrator/providers/antigravity-oauth-catalog.mjs +30 -9
  58. package/src/runtime/agent/orchestrator/providers/antigravity-oauth.mjs +7 -0
  59. package/src/runtime/agent/orchestrator/providers/antigravity-request.mjs +30 -14
  60. package/src/runtime/agent/orchestrator/providers/antigravity-response.mjs +2 -1
  61. package/src/runtime/agent/orchestrator/providers/antigravity-retry-policy.mjs +93 -0
  62. package/src/runtime/agent/orchestrator/providers/antigravity-transport.mjs +82 -9
  63. package/src/runtime/agent/orchestrator/providers/auto-effort.mjs +9 -0
  64. package/src/runtime/agent/orchestrator/providers/command-code-oauth-login.mjs +337 -0
  65. package/src/runtime/agent/orchestrator/providers/command-code-oauth-tokens.mjs +159 -0
  66. package/src/runtime/agent/orchestrator/providers/command-code-oauth.mjs +447 -0
  67. package/src/runtime/agent/orchestrator/providers/cursor-auth.mjs +38 -5
  68. package/src/runtime/agent/orchestrator/providers/cursor-client-version.mjs +1 -1
  69. package/src/runtime/agent/orchestrator/providers/cursor-wire-guards.mjs +97 -2
  70. package/src/runtime/agent/orchestrator/providers/cursor-wire-stream-bridge.mjs +102 -23
  71. package/src/runtime/agent/orchestrator/providers/cursor-wire-transport.mjs +103 -33
  72. package/src/runtime/agent/orchestrator/providers/cursor-wire.mjs +29 -6
  73. package/src/runtime/agent/orchestrator/providers/cursor.mjs +18 -1
  74. package/src/runtime/agent/orchestrator/providers/custom-provider.mjs +7 -4
  75. package/src/runtime/agent/orchestrator/providers/effort-configuration.mjs +86 -19
  76. package/src/runtime/agent/orchestrator/providers/gemini-cache-policy.mjs +1 -1
  77. package/src/runtime/agent/orchestrator/providers/gemini-cache.mjs +29 -8
  78. package/src/runtime/agent/orchestrator/providers/gemini-error-contract.mjs +191 -0
  79. package/src/runtime/agent/orchestrator/providers/gemini-family.mjs +9 -0
  80. package/src/runtime/agent/orchestrator/providers/gemini-history-signatures.mjs +9 -4
  81. package/src/runtime/agent/orchestrator/providers/gemini-response.mjs +3 -1
  82. package/src/runtime/agent/orchestrator/providers/gemini-retry-policy.mjs +180 -0
  83. package/src/runtime/agent/orchestrator/providers/gemini-schema.mjs +21 -82
  84. package/src/runtime/agent/orchestrator/providers/gemini-sdk-request.mjs +4 -1
  85. package/src/runtime/agent/orchestrator/providers/gemini-stream.mjs +71 -12
  86. package/src/runtime/agent/orchestrator/providers/gemini-thinking.mjs +21 -20
  87. package/src/runtime/agent/orchestrator/providers/gemini.mjs +97 -51
  88. package/src/runtime/agent/orchestrator/providers/grok-oauth-tokens.mjs +3 -0
  89. package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +68 -19
  90. package/src/runtime/agent/orchestrator/providers/lib/gemini-model-catalog.mjs +7 -1
  91. package/src/runtime/agent/orchestrator/providers/mimo-oauth.mjs +3 -0
  92. package/src/runtime/agent/orchestrator/providers/model-list-sanitize.mjs +2 -1
  93. package/src/runtime/agent/orchestrator/providers/model-proxy-bridge.mjs +1080 -0
  94. package/src/runtime/agent/orchestrator/providers/model-proxy-channel.d.mts +15 -0
  95. package/src/runtime/agent/orchestrator/providers/model-proxy-channel.mjs +35 -0
  96. package/src/runtime/agent/orchestrator/providers/muse-code-oauth.mjs +4 -9
  97. package/src/runtime/agent/orchestrator/providers/oauth-credential-probes.mjs +12 -0
  98. package/src/runtime/agent/orchestrator/providers/oauth-usage.mjs +6 -5
  99. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-send.mjs +5 -3
  100. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream/stream-result.mjs +22 -0
  101. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream.mjs +1 -0
  102. package/src/runtime/agent/orchestrator/providers/openai-compat-error-contract.mjs +50 -0
  103. package/src/runtime/agent/orchestrator/providers/openai-compat-models.mjs +6 -2
  104. package/src/runtime/agent/orchestrator/providers/openai-compat-options.mjs +11 -0
  105. package/src/runtime/agent/orchestrator/providers/openai-compat-response-normalization.mjs +23 -0
  106. package/src/runtime/agent/orchestrator/providers/openai-compat-responses-events.mjs +16 -8
  107. package/src/runtime/agent/orchestrator/providers/openai-compat-responses-state.mjs +3 -2
  108. package/src/runtime/agent/orchestrator/providers/openai-compat-responses.mjs +94 -13
  109. package/src/runtime/agent/orchestrator/providers/openai-compat-retry-profile.mjs +193 -0
  110. package/src/runtime/agent/orchestrator/providers/openai-compat-stream-common.mjs +66 -2
  111. package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +2 -1
  112. package/src/runtime/agent/orchestrator/providers/openai-compat-wire.mjs +6 -0
  113. package/src/runtime/agent/orchestrator/providers/openai-compat-xai-send.mjs +6 -3
  114. package/src/runtime/agent/orchestrator/providers/openai-compat-xai.mjs +23 -14
  115. package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +26 -6
  116. package/src/runtime/agent/orchestrator/providers/openai-direct-request.mjs +42 -25
  117. package/src/runtime/agent/orchestrator/providers/openai-error-contract.mjs +274 -0
  118. package/src/runtime/agent/orchestrator/providers/openai-http-sse-request.mjs +72 -32
  119. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-events/terminal-frames.mjs +18 -11
  120. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-outcome.mjs +1 -2
  121. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-stamps.mjs +2 -2
  122. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-state.mjs +2 -3
  123. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state.mjs +2 -2
  124. package/src/runtime/agent/orchestrator/providers/openai-http-sse-watchdogs.mjs +1 -3
  125. package/src/runtime/agent/orchestrator/providers/openai-oauth-dispatch.mjs +34 -3
  126. package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +5 -1
  127. package/src/runtime/agent/orchestrator/providers/openai-oauth-tokens.mjs +22 -2
  128. package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +5 -0
  129. package/src/runtime/agent/orchestrator/providers/openai-responses-input.mjs +5 -9
  130. package/src/runtime/agent/orchestrator/providers/openai-retry-policy.mjs +9 -0
  131. package/src/runtime/agent/orchestrator/providers/openai-ws-handshake.mjs +16 -8
  132. package/src/runtime/agent/orchestrator/providers/openai-ws-send-attempts/attempt-context.mjs +1 -2
  133. package/src/runtime/agent/orchestrator/providers/openai-ws-send-attempts/policy.mjs +25 -14
  134. package/src/runtime/agent/orchestrator/providers/openai-ws-terminal.mjs +18 -11
  135. package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +35 -24
  136. package/src/runtime/agent/orchestrator/providers/provider-error-kind.mjs +581 -0
  137. package/src/runtime/agent/orchestrator/providers/registry.mjs +35 -9
  138. package/src/runtime/agent/orchestrator/providers/retry-backoff.mjs +46 -7
  139. package/src/runtime/agent/orchestrator/providers/retry-classification.mjs +118 -219
  140. package/src/runtime/agent/orchestrator/providers/retry-classifier.mjs +2 -0
  141. package/src/runtime/agent/orchestrator/providers/retry-executor.mjs +148 -45
  142. package/src/runtime/agent/orchestrator/providers/retry-midstream.mjs +81 -16
  143. package/src/runtime/agent/orchestrator/runtime-core/config-helpers.mjs +3 -0
  144. package/src/runtime/agent/orchestrator/runtime-core/effort.mjs +3 -0
  145. package/src/runtime/agent/orchestrator/runtime-core/extension-scopes.mjs +15 -55
  146. package/src/runtime/agent/orchestrator/runtime-core/goal-control.mjs +15 -2
  147. package/src/runtime/agent/orchestrator/runtime-core/goal-reminder.mjs +0 -18
  148. package/src/runtime/agent/orchestrator/runtime-core/model-capabilities.mjs +57 -28
  149. package/src/runtime/agent/orchestrator/runtime-core/orchestration.mjs +19 -16
  150. package/src/runtime/agent/orchestrator/runtime-core/plugin-mcp.mjs +268 -181
  151. package/src/runtime/agent/orchestrator/runtime-core/skill-tool-loading.mjs +4 -1
  152. package/src/runtime/agent/orchestrator/runtime-core/tool-catalog-schema.mjs +21 -0
  153. package/src/runtime/agent/orchestrator/runtime-core/tool-catalog.mjs +54 -3
  154. package/src/runtime/agent/orchestrator/session/catalog-db/index.mjs +284 -0
  155. package/src/runtime/agent/orchestrator/session/catalog-db/schema.mjs +270 -0
  156. package/src/runtime/agent/orchestrator/session/catalog-db/sqlite-gate.mjs +27 -0
  157. package/src/runtime/agent/orchestrator/session/catalog-db/worker.mjs +628 -0
  158. package/src/runtime/agent/orchestrator/session/compact/messages.mjs +6 -2
  159. package/src/runtime/agent/orchestrator/session/conversation-import/claude.mjs +184 -0
  160. package/src/runtime/agent/orchestrator/session/conversation-import/codex.mjs +136 -0
  161. package/src/runtime/agent/orchestrator/session/conversation-import/convert.mjs +128 -0
  162. package/src/runtime/agent/orchestrator/session/conversation-import/imported-session.mjs +127 -0
  163. package/src/runtime/agent/orchestrator/session/conversation-import/turns.mjs +101 -0
  164. package/src/runtime/agent/orchestrator/session/explicit-skills.mjs +83 -5
  165. package/src/runtime/agent/orchestrator/session/imported-message.mjs +11 -0
  166. package/src/runtime/agent/orchestrator/session/loop/completion-guards.mjs +2 -1
  167. package/src/runtime/agent/orchestrator/session/loop/loop-state.mjs +1 -1
  168. package/src/runtime/agent/orchestrator/session/loop/no-tool-turn.mjs +3 -2
  169. package/src/runtime/agent/orchestrator/session/loop/send-phase.mjs +10 -4
  170. package/src/runtime/agent/orchestrator/session/loop/steering.mjs +4 -3
  171. package/src/runtime/agent/orchestrator/session/loop/step-auto-effort.mjs +102 -18
  172. package/src/runtime/agent/orchestrator/session/loop/tool-helpers.mjs +3 -0
  173. package/src/runtime/agent/orchestrator/session/loop/tool-phase.mjs +3 -1
  174. package/src/runtime/agent/orchestrator/session/manager/ask-queue.mjs +42 -5
  175. package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +14 -3
  176. package/src/runtime/agent/orchestrator/session/manager/ask-turn-auto-effort.mjs +20 -12
  177. package/src/runtime/agent/orchestrator/session/manager/ask-turn-execute.mjs +50 -23
  178. package/src/runtime/agent/orchestrator/session/manager/ask-turn-loop-options.mjs +4 -0
  179. package/src/runtime/agent/orchestrator/session/manager/pending-message-entry.mjs +22 -14
  180. package/src/runtime/agent/orchestrator/session/manager/session-close.mjs +1 -1
  181. package/src/runtime/agent/orchestrator/session/manager/turn-interruption.mjs +18 -7
  182. package/src/runtime/agent/orchestrator/session/manager.mjs +0 -1
  183. package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +4 -3
  184. package/src/runtime/agent/orchestrator/session/send-with-recovery.mjs +155 -29
  185. package/src/runtime/agent/orchestrator/session/store/listing.mjs +6 -2
  186. package/src/runtime/agent/orchestrator/session/store/save-worker.mjs +6 -3
  187. package/src/runtime/agent/orchestrator/session/store/summary-cache.mjs +6 -0
  188. package/src/runtime/agent/orchestrator/session/store/write-guards.mjs +16 -0
  189. package/src/runtime/agent/orchestrator/session/store-agent-worker-rows.mjs +22 -10
  190. package/src/runtime/agent/orchestrator/session/store-summary-catalog.mjs +819 -0
  191. package/src/runtime/agent/orchestrator/session/store-summary-fields.mjs +12 -0
  192. package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +22 -12
  193. package/src/runtime/agent/orchestrator/session/store-summary-reader-catalog-fixture.mjs +158 -0
  194. package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +183 -374
  195. package/src/runtime/agent/orchestrator/session/synthetic-user-envelope.mjs +3 -1
  196. package/src/runtime/agent/orchestrator/session/tool-batch/pre-dispatch.mjs +1 -1
  197. package/src/runtime/agent/orchestrator/session/transcript-restore/restore.mjs +41 -3
  198. package/src/runtime/agent/orchestrator/session/transcript-restore/user-text.mjs +38 -17
  199. package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +2 -2
  200. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/deadline-plan.mjs +12 -15
  201. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/invocation-plan.mjs +17 -1
  202. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/spawn-env.mjs +4 -0
  203. package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +5 -5
  204. package/src/runtime/agent/orchestrator/tools/builtin/lib/list-helpers.mjs +2 -2
  205. package/src/runtime/agent/orchestrator/tools/builtin/path-diagnostics.mjs +6 -6
  206. package/src/runtime/agent/orchestrator/tools/builtin/read-range-index.mjs +173 -22
  207. package/src/runtime/agent/orchestrator/tools/builtin/read-single-guards.mjs +8 -4
  208. package/src/runtime/agent/orchestrator/tools/builtin/read-single-tool.mjs +5 -4
  209. package/src/runtime/agent/orchestrator/tools/builtin/read-source-windows.mjs +15 -2
  210. package/src/runtime/agent/orchestrator/tools/builtin/search-glob-tool.mjs +13 -10
  211. package/src/runtime/agent/orchestrator/tools/builtin/search-grep-tool.mjs +1 -1
  212. package/src/runtime/agent/orchestrator/tools/builtin/search-path-diagnostics.mjs +17 -12
  213. package/src/runtime/agent/orchestrator/tools/code-graph/dispatch/root-resolution.mjs +2 -2
  214. package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +1 -1
  215. package/src/runtime/agent/orchestrator/tools/patch/apply-patch/codex-batch.mjs +4 -4
  216. package/src/runtime/agent/orchestrator/tools/patch/apply-patch/route.mjs +2 -2
  217. package/src/runtime/agent/orchestrator/tools/patch/orchestrator.mjs +1 -1
  218. package/src/runtime/agent/orchestrator/tools/patch/read-redirects.mjs +34 -30
  219. package/src/runtime/agent/orchestrator/tools/patch/sequence/units.mjs +7 -7
  220. package/src/runtime/agent/orchestrator/tools/patch/sequence.mjs +1 -1
  221. package/src/runtime/agent/orchestrator/tools/shell-snapshot.mjs +4 -0
  222. package/src/runtime/attachments/store.d.mts +3 -0
  223. package/src/runtime/channels/lib/owned-runtime/owned-start.mjs +1 -1
  224. package/src/runtime/channels/lib/webhook/session-dispatch.mjs +16 -4
  225. package/src/runtime/channels/lib/worker-main.mjs +17 -14
  226. package/src/runtime/computer-bridge/linux-readiness.mjs +0 -2
  227. package/src/runtime/effort-judge/judge-client.mjs +56 -18
  228. package/src/runtime/memory/lib/memory.mjs +1 -1
  229. package/src/runtime/memory/lib/pg/process.mjs +34 -24
  230. package/src/runtime/memory/lib/pg/supervisor.mjs +16 -13
  231. package/src/runtime/memory/lib/session-ingest.mjs +2 -2
  232. package/src/runtime/office/README.md +1 -1
  233. package/src/runtime/office/shared/values.mjs +0 -5
  234. package/src/runtime/shared/atomic-file.mjs +25 -4
  235. package/src/runtime/shared/background-tasks.mjs +92 -14
  236. package/src/runtime/shared/err-text.mjs +25 -0
  237. package/src/runtime/shared/error-presentation.mjs +159 -19
  238. package/src/runtime/shared/extension-scopes.mjs +5 -3
  239. package/src/runtime/shared/file-lock.mjs +37 -11
  240. package/src/runtime/shared/llm/anthropic-betas.mjs +4 -3
  241. package/src/runtime/shared/llm/anthropic-effort.mjs +134 -131
  242. package/src/runtime/shared/llm/anthropic-thinking-contract.mjs +50 -14
  243. package/src/runtime/shared/llm/model-catalog.mjs +53 -52
  244. package/src/runtime/shared/llm/openai-daybreak.mjs +4 -16
  245. package/src/runtime/shared/llm/provider-catalog-registry.mjs +17 -0
  246. package/src/runtime/shared/llm/provider-model-identities.mjs +13 -4
  247. package/src/runtime/shared/llm/usage-rollup.mjs +0 -1
  248. package/src/runtime/shared/model-display.mjs +13 -0
  249. package/src/runtime/shared/plugin-manifest.mjs +18 -9
  250. package/src/runtime/shared/pristine-execution-contract.json +9 -2
  251. package/src/runtime/shared/pristine-execution.mjs +28 -4
  252. package/src/runtime/shared/provider-accounts.mjs +124 -20
  253. package/src/runtime/shared/provider-api-key.mjs +12 -0
  254. package/src/runtime/shared/quota-resume-store.mjs +11 -0
  255. package/src/runtime/shared/session-baseline-store.mjs +960 -0
  256. package/src/runtime/shared/shell-display.mjs +9 -3
  257. package/src/runtime/shared/skill-mentions.d.mts +10 -0
  258. package/src/runtime/shared/skill-mentions.mjs +133 -0
  259. package/src/runtime/shared/skill-script-runtime.mjs +264 -0
  260. package/src/runtime/shared/statusline/statusline-format.mjs +4 -4
  261. package/src/runtime/shared/task-notification-envelope.mjs +2 -2
  262. package/src/runtime/shared/tool-primitives.mjs +35 -2
  263. package/src/runtime/shared/turn-changes.mjs +447 -219
  264. package/src/runtime/shared/turn-snapshot-store.mjs +182 -1
  265. package/src/runtime/shared/turn-snapshot.mjs +170 -48
  266. package/src/runtime/shared/turn-worktree-snapshot.mjs +160 -51
  267. package/src/runtime/shared/worker-requests.mjs +15 -2
  268. package/src/runtime/shared/worktrees/contract.mjs +0 -24
  269. package/src/runtime/shared/worktrees/test-support.mjs +19 -4
  270. package/src/session-runtime/automation-agents.mjs +8 -2
  271. package/src/session-runtime/boot/apis.mjs +61 -0
  272. package/src/session-runtime/boot/config.mjs +5 -2
  273. package/src/session-runtime/boot/facade.mjs +1 -0
  274. package/src/session-runtime/boot/infrastructure.mjs +35 -18
  275. package/src/session-runtime/conversation-import/discovery.mjs +698 -0
  276. package/src/session-runtime/conversation-import/service.mjs +369 -0
  277. package/src/session-runtime/cwd-plugins/cwd-apply.mjs +10 -5
  278. package/src/session-runtime/cwd-plugins/plugin-status.mjs +47 -42
  279. package/src/session-runtime/lifecycle/session-catalog.mjs +2 -0
  280. package/src/session-runtime/lifecycle/teardown.mjs +1 -1
  281. package/src/session-runtime/mcp-glue/connect.mjs +83 -16
  282. package/src/session-runtime/mcp-glue/plugin-servers.mjs +499 -0
  283. package/src/session-runtime/mcp-glue/server-catalog.mjs +135 -33
  284. package/src/session-runtime/mcp-glue.mjs +25 -2
  285. package/src/session-runtime/mcp-oauth-login.mjs +174 -0
  286. package/src/session-runtime/model-route/model-switch-target.mjs +34 -0
  287. package/src/session-runtime/model-route/set-route.mjs +83 -15
  288. package/src/session-runtime/model-route/web-search-route.mjs +1 -1
  289. package/src/session-runtime/native-web-search/routes.mjs +22 -3
  290. package/src/session-runtime/new-session-config.mjs +3 -2
  291. package/src/session-runtime/notification-bus.mjs +9 -2
  292. package/src/session-runtime/provider-auth/accounts.mjs +33 -15
  293. package/src/session-runtime/provider-models/model-meta.mjs +12 -3
  294. package/src/session-runtime/provider-models/web-search-catalog.mjs +5 -1
  295. package/src/session-runtime/provider-readiness.mjs +16 -10
  296. package/src/session-runtime/quick-model-rows/web-search-rows.mjs +13 -8
  297. package/src/session-runtime/quick-web-search-models.mjs +13 -38
  298. package/src/session-runtime/resource-extension-sync.mjs +24 -5
  299. package/src/session-runtime/resource-mcp-api.mjs +198 -78
  300. package/src/session-runtime/resource-plugins-api.mjs +29 -41
  301. package/src/session-runtime/resource-scope-decorate.mjs +6 -8
  302. package/src/session-runtime/resource-skills-api.mjs +23 -2
  303. package/src/session-runtime/runtime-review-api.mjs +13 -0
  304. package/src/session-runtime/schedule-session-run.mjs +1 -1
  305. package/src/session-runtime/services/agent-tool/job-task-reconcile.mjs +13 -1
  306. package/src/session-runtime/services/agent-tool/job-views/agent-list.mjs +7 -2
  307. package/src/session-runtime/services/agent-tool/send-flow.mjs +22 -4
  308. package/src/session-runtime/services/agent-tool/spawn-flow/job-start.mjs +2 -0
  309. package/src/session-runtime/services/agent-tool/spawn-flow/spawn-run.mjs +22 -5
  310. package/src/session-runtime/services/agent-tool/worker-index/row-liveness.mjs +8 -1
  311. package/src/session-runtime/services/agent-tool/worker-index/row-shape.mjs +2 -2
  312. package/src/session-runtime/services/agent-tool/worker-rows.mjs +17 -0
  313. package/src/session-runtime/services/channel-session-router.mjs +4 -1
  314. package/src/session-runtime/services/hook-bus/command-handler.mjs +4 -1
  315. package/src/session-runtime/services/hook-bus/handlers.mjs +7 -3
  316. package/src/session-runtime/services/provider-admin.mjs +64 -18
  317. package/src/session-runtime/services/usage-dashboard-model.mjs +3 -0
  318. package/src/session-runtime/settings-compaction-api.mjs +1 -0
  319. package/src/session-runtime/setup-tool/executor.mjs +12 -1
  320. package/src/session-runtime/setup-tool/extended-actions.mjs +1 -0
  321. package/src/session-runtime/setup-tool/settings-contract.mjs +1 -0
  322. package/src/session-runtime/setup-tool/tool-defs.mjs +7 -1
  323. package/src/session-runtime/skills-api.mjs +7 -3
  324. package/src/session-runtime/turn/turn-run/ask-callbacks.mjs +1 -0
  325. package/src/session-runtime/webhook-session-run.mjs +16 -4
  326. package/src/skill-script-entry.mjs +36 -0
  327. package/src/standalone/daemon-drain-policy.mjs +8 -0
  328. package/src/standalone/daemon-stored-session-views.mjs +11 -3
  329. package/src/standalone/daemon-telemetry.mjs +55 -0
  330. package/src/standalone/daemon.mjs +44 -11
  331. package/src/standalone/session-protocol.mjs +12 -0
  332. package/src/standalone/session-service/agent-tree/agent-cancel.mjs +33 -2
  333. package/src/standalone/session-service/agent-tree/agent-turns.mjs +26 -10
  334. package/src/standalone/session-service/agent-tree.mjs +12 -0
  335. package/src/standalone/session-service/quota-resume.mjs +8 -0
  336. package/src/standalone/session-service/retention/busy.mjs +15 -1
  337. package/src/standalone/session-service/session-calls/session-action.mjs +6 -1
  338. package/src/standalone/session-service-api.mjs +5 -0
  339. package/src/standalone/session-service.mjs +1 -0
  340. package/src/tui/App.jsx +9 -1
  341. package/src/tui/app/app-view/floating-panel.jsx +9 -1
  342. package/src/tui/app/app-view.jsx +7 -0
  343. package/src/tui/app/create-app-pickers.mjs +4 -0
  344. package/src/tui/app/extension-pickers/mcp-login.mjs +107 -0
  345. package/src/tui/app/extension-pickers/mcp-servers-picker.mjs +2 -1
  346. package/src/tui/app/extension-pickers/plugins-pickers.mjs +35 -16
  347. package/src/tui/app/extension-pickers.mjs +2 -1
  348. package/src/tui/app/model-options.mjs +13 -16
  349. package/src/tui/app/skill-mention-palette.mjs +88 -0
  350. package/src/tui/app/slash-commands.mjs +7 -2
  351. package/src/tui/app/slash-dispatch/panel-commands.mjs +16 -1
  352. package/src/tui/app/slash-palette.mjs +23 -7
  353. package/src/tui/app/transcript-row-estimate.mjs +11 -3
  354. package/src/tui/app/use-prompt-draft-flow.mjs +7 -2
  355. package/src/tui/app/use-prompt-state.mjs +5 -0
  356. package/src/tui/components/Message.jsx +22 -1
  357. package/src/tui/components/PromptInput.jsx +21 -3
  358. package/src/tui/components/SlashCommandPalette.jsx +12 -3
  359. package/src/tui/components/StatusLine.jsx +2 -0
  360. package/src/tui/components/TranscriptItem.jsx +9 -1
  361. package/src/tui/components/prompt-input/key-handler.mjs +21 -8
  362. package/src/tui/components/status-line/local-line.mjs +5 -2
  363. package/src/tui/components/status-line/use-statusline-text.mjs +7 -1
  364. package/src/tui/components/user-message-skills.mjs +38 -0
  365. package/src/tui/dist/index.mjs +1323 -214
  366. package/src/tui/session/queue-helpers.mjs +7 -2
  367. package/src/tui/session/quota-resume.mjs +46 -14
  368. package/src/tui/session/session-api/commands.mjs +33 -24
  369. package/src/tui/session/session-api/extensions.mjs +26 -2
  370. package/src/tui/session/session-api/integrations.mjs +7 -0
  371. package/src/tui/session/session-api/lifecycle.mjs +5 -1
  372. package/src/tui/session/session-api/routes.mjs +71 -7
  373. package/src/tui/session/session-api/settings.mjs +12 -14
  374. package/src/tui/session/session-flow/drain.mjs +21 -5
  375. package/src/tui/session/session-flow/failed-turn-rewind.mjs +10 -4
  376. package/src/tui/session/tui-steering-persist.mjs +25 -9
  377. package/src/tui/session/turn.mjs +55 -15
  378. package/src/tui/statusline-ansi-bridge.mjs +1 -1
  379. package/src/ui/statusline-agents.mjs +15 -4
  380. package/src/ui/statusline.mjs +7 -3
  381. package/src/ui/streaming-markdown-heal.mjs +34 -3
  382. package/src/vendor/statusline/src/gateway/claude-current.mjs +14 -4
  383. package/src/runtime/effort-judge/previous-human-request.mjs +0 -162
  384. package/src/runtime/effort-judge/verified-context-input.mjs +0 -10
  385. package/src/runtime/memory/lib/memory-session-merge.mjs +0 -32
  386. package/src/runtime/shared/llm/usage-session-history.mjs +0 -266
@@ -85,6 +85,12 @@ const PROVIDER_CATALOGS = Object.freeze({
85
85
  // Effort tiers live in the wire id; a row without levels has no effort
86
86
  // surface (resolveAntigravityWireModel drops the effort).
87
87
  'antigravity-oauth': { relay: true, rosterFiles: ['antigravity-oauth-models.json'], rosterLadderFinal: true },
88
+ // api.commandcode.ai/provider/v1 relays other vendors' models.
89
+ 'command-code-oauth': {
90
+ relay: true,
91
+ rosterFiles: ['command-code-oauth-models.json'],
92
+ rosterLimitsAuthoritative: true,
93
+ },
88
94
  'cursor-oauth': { relay: true },
89
95
  'cursor-api': { relay: true },
90
96
  'opencode-go': { relay: true },
@@ -126,6 +132,17 @@ export function providerCatalogEntry(provider) {
126
132
  };
127
133
  }
128
134
 
135
+ /** models.dev ids of the registered non-relay providers: vendors that serve their own models. */
136
+ export function firstPartyModelsDevIds() {
137
+ return [
138
+ ...new Set(
139
+ Object.values(PROVIDER_CATALOGS)
140
+ .filter((entry) => entry.relay !== true && entry.modelsDev)
141
+ .map((entry) => entry.modelsDev)
142
+ ),
143
+ ].sort();
144
+ }
145
+
129
146
  export function modelsDevProviderId(provider) {
130
147
  return providerCatalogEntry(provider)?.modelsDev || null;
131
148
  }
@@ -1,6 +1,7 @@
1
1
  // Model identities are shared by transport and pricing. Keep this module
2
2
  // independent of credentials, tracing and provider initialization.
3
- function fallbackModel(id, display, family, contextWindow, wire) {
3
+ // `levels` names the thinkingLevel values a single-wire model takes.
4
+ function fallbackModel(id, display, family, contextWindow, wire, levels = []) {
4
5
  const tiered = wire && typeof wire === 'object';
5
6
  return {
6
7
  id,
@@ -12,7 +13,7 @@ function fallbackModel(id, display, family, contextWindow, wire) {
12
13
  supportsVision: true,
13
14
  supportsReasoning: true,
14
15
  supportsFunctionCalling: true,
15
- reasoningLevels: tiered ? Object.keys(wire) : [],
16
+ reasoningLevels: tiered ? Object.keys(wire) : [...levels],
16
17
  wire,
17
18
  };
18
19
  }
@@ -27,8 +28,16 @@ export const ANTIGRAVITY_MODELS = Object.freeze([
27
28
  low: 'gemini-3.1-pro-low',
28
29
  high: 'gemini-3.1-pro-high',
29
30
  }),
30
- fallbackModel('claude-opus-4-6-thinking', 'Claude Opus 4.6 (Thinking)', null, 250000, 'claude-opus-4-6-thinking'),
31
- fallbackModel('claude-sonnet-4-6', 'Claude Sonnet 4.6 (Thinking)', null, 250000, 'claude-sonnet-4-6'),
31
+ fallbackModel('claude-opus-4-6-thinking', 'Claude Opus 4.6 (Thinking)', null, 250000, 'claude-opus-4-6-thinking', [
32
+ 'low',
33
+ 'medium',
34
+ 'high',
35
+ ]),
36
+ fallbackModel('claude-sonnet-4-6', 'Claude Sonnet 4.6 (Thinking)', null, 250000, 'claude-sonnet-4-6', [
37
+ 'low',
38
+ 'medium',
39
+ 'high',
40
+ ]),
32
41
  fallbackModel('gpt-oss-120b-medium', 'GPT-OSS 120B (Medium)', 'gpt-oss', 131072, 'gpt-oss-120b-medium'),
33
42
  ]);
34
43
 
@@ -59,7 +59,6 @@ const MAX_SESSION_TOKENS_PER_DAY = 400;
59
59
  const DAY_MS = 24 * 60 * 60 * 1000;
60
60
  const UNSAFE_KEYS = new Set(['__proto__', 'constructor', 'prototype']);
61
61
 
62
- // Shared with usage-session-history.mjs, which folds the same bucket shape.
63
62
  export function num(value) {
64
63
  const n = Number(value);
65
64
  return Number.isFinite(n) && n > 0 ? n : 0;
@@ -3,6 +3,19 @@
3
3
  * No I/O, catalog, or gateway imports — safe for static TUI bundling.
4
4
  */
5
5
 
6
+ const titleCaseOption = (value) =>
7
+ String(value || '')
8
+ .split(/([\s_-]+)/)
9
+ .map((part) => (/^[\s_-]+$/.test(part) ? part : `${part.charAt(0).toUpperCase()}${part.slice(1).toLowerCase()}`))
10
+ .join('');
11
+
12
+ export const effortDisplayLabel = (value) => {
13
+ const text = String(value || '').trim();
14
+ if (!text) return '';
15
+ if (text.toLowerCase() === 'xhigh') return 'XHigh';
16
+ return titleCaseOption(text);
17
+ };
18
+
6
19
  export function titleModelPart(part) {
7
20
  const text = String(part || '').trim();
8
21
  if (!text) return '';
@@ -7,10 +7,19 @@
7
7
  import { isAbsolute, join, relative, resolve } from 'node:path';
8
8
  import { readJsonSafe } from './json-file.mjs';
9
9
 
10
- /** `.codex-plugin/plugin.json` wins over a root-level `plugin.json`; a plugin
11
- * without either is an empty manifest, never an error. */
10
+ /** Manifest files in precedence order: `.codex-plugin/plugin.json`, a
11
+ * root-level `plugin.json`, then Claude Code's `.claude-plugin/plugin.json`. */
12
+ export const PLUGIN_MANIFEST_FILES = ['.codex-plugin/plugin.json', 'plugin.json', '.claude-plugin/plugin.json'];
13
+
14
+ /** First readable manifest in `PLUGIN_MANIFEST_FILES` order; a plugin without
15
+ * any is an empty manifest, never an error. A Claude Code manifest's `skills`
16
+ * is a plugin-relative path (e.g. `./.claude/skills/`). */
12
17
  export function pluginManifest(root) {
13
- return readJsonSafe(join(root, '.codex-plugin', 'plugin.json')) || readJsonSafe(join(root, 'plugin.json')) || {};
18
+ for (const rel of PLUGIN_MANIFEST_FILES) {
19
+ const manifest = readJsonSafe(join(root, ...rel.split('/')));
20
+ if (manifest) return manifest;
21
+ }
22
+ return {};
14
23
  }
15
24
 
16
25
  /** Resolve a manifest-relative path, refusing absolute paths and anything
@@ -21,22 +30,22 @@ export function resolveContainedPluginPath(root, rel) {
21
30
  const base = resolve(root);
22
31
  const abs = resolve(base, trimmed);
23
32
  const relToBase = relative(base, abs);
24
- if (relToBase.startsWith('..') || isAbsolute(relToBase)) return null;
33
+ if (relToBase.split(/[\\/]/)[0] === '..' || isAbsolute(relToBase)) return null;
25
34
  return abs;
26
35
  }
27
36
 
28
- /** Skill roots a plugin contributes: the conventional `<root>/skills/` plus an
29
- * optional manifest `skills` path. Deduplicated, absolute, root-contained. */
37
+ /** Skill roots a plugin contributes: the conventional `<root>/skills/` plus the
38
+ * manifest `skills` field, a path or a list of paths. Deduplicated, absolute,
39
+ * root-contained. */
30
40
  export function pluginSkillsRoots(root) {
31
41
  const manifest = pluginManifest(root);
32
42
  const roots = new Set();
33
43
  const add = (rel) => {
44
+ if (typeof rel !== 'string' || !rel.trim()) return;
34
45
  const abs = resolveContainedPluginPath(root, rel);
35
46
  if (abs) roots.add(abs);
36
47
  };
37
48
  add('./skills/');
38
- if (typeof manifest.skills === 'string' && manifest.skills.trim()) {
39
- add(manifest.skills.trim());
40
- }
49
+ for (const rel of Array.isArray(manifest.skills) ? manifest.skills : [manifest.skills]) add(rel);
41
50
  return [...roots];
42
51
  }
@@ -66,11 +66,18 @@
66
66
  },
67
67
  "muse-code-oauth": {
68
68
  "credentialFile": "muse-code-oauth.json",
69
- "credentialPathEnv": "MUSE_CODE_OAUTH_CREDENTIALS_PATH"
69
+ "credentialPathEnv": "MUSE_CODE_OAUTH_CREDENTIALS_PATH",
70
+ "modelCatalogFile": "muse-code-oauth-models.json"
71
+ },
72
+ "command-code-oauth": {
73
+ "credentialFile": "command-code-oauth.json",
74
+ "credentialPathEnv": "COMMAND_CODE_OAUTH_CREDENTIALS_PATH",
75
+ "modelCatalogFile": "command-code-oauth-models.json"
70
76
  },
71
77
  "mimo-oauth": {
72
78
  "credentialFile": "mimo-oauth.json",
73
- "credentialPathEnv": "MIMO_OAUTH_CREDENTIALS_PATH"
79
+ "credentialPathEnv": "MIMO_OAUTH_CREDENTIALS_PATH",
80
+ "modelCatalogFile": "mimo-oauth-models.json"
74
81
  },
75
82
  "cursor-oauth": {
76
83
  "credentialFile": "cursor-oauth.json",
@@ -8,6 +8,12 @@ import { ACCOUNT_PROVIDERS, providerAccountPath, readProviderAccountPool } from
8
8
  import { AGENT_PROVIDER_ENV_ALIASES, getAgentApiKey } from './provider-api-key.mjs';
9
9
  import { platformEntryKey } from './native-asset.mjs';
10
10
  import { clean } from './clean.mjs';
11
+ import { MODEL_PROXY_ENV, modelProxyClientMode } from './model-proxy-mode.mjs';
12
+ import { installModelProxyChannel } from '../agent/orchestrator/providers/model-proxy-channel.mjs';
13
+ import {
14
+ ModelProxyBridgeChannel,
15
+ readModelProxyBridgeDiscovery,
16
+ } from '../agent/orchestrator/providers/model-proxy-bridge.mjs';
11
17
 
12
18
  const contractPath = fileURLToPath(new URL('./pristine-execution-contract.json', import.meta.url));
13
19
 
@@ -205,6 +211,12 @@ export function createPristineExecutionBoundary({
205
211
 
206
212
  const contract = PRISTINE_EXECUTION_CONTRACT;
207
213
  const hostEnv = { ...env };
214
+ // Remote (SSH) mode: this server holds no model credentials, so the child
215
+ // runtime's model calls go through the daemon's model proxy bridge. Its
216
+ // endpoint is read from the HOST runtime root, before it is retargeted
217
+ // below; a missing or stale one fails closed (never a local credential).
218
+ const proxyMode = modelProxyClientMode(hostEnv);
219
+ const bridgeEndpoint = proxyMode ? readModelProxyBridgeDiscovery(hostEnv.MIXDOG_RUNTIME_ROOT) : null;
208
220
  const originalEnv = new Map();
209
221
  const touchedKeys = new Set();
210
222
  const rememberEnv = (name) => {
@@ -227,6 +239,8 @@ export function createPristineExecutionBoundary({
227
239
  mkdirSync(runtimeRoot, { recursive: true, mode: 0o700 });
228
240
  let cleaned = false;
229
241
  let restoreAuthBindings = () => {};
242
+ let bridgeChannel = null;
243
+ let restoreModelProxyChannel = () => {};
230
244
  // rmSync backs off linearly (retryDelay × attempt), so 50 retries at 100ms
231
245
  // is a ≈128s budget per attempt. Callers whose result is already complete
232
246
  // (headless exec) pass a smaller budget and leave stragglers to the sweep.
@@ -238,6 +252,8 @@ export function createPristineExecutionBoundary({
238
252
  else env[name] = value;
239
253
  }
240
254
  restoreAuthBindings();
255
+ restoreModelProxyChannel();
256
+ bridgeChannel?.dispose('the headless run finished');
241
257
  const removal = { recursive: true, force: true, maxRetries: rootRemovalRetries, retryDelay: 100 };
242
258
  // Windows releases file handles (pg.log, sockets) a beat after the owning
243
259
  // process exits; rmSync retries absorb that EBUSY/EPERM window.
@@ -280,7 +296,7 @@ export function createPristineExecutionBoundary({
280
296
  const oauth = contract.oauthProviders[selectedProvider];
281
297
  const apiKeyEnv = contract.apiKeyProviders[selectedProvider];
282
298
  let selectedApiKey = null;
283
- if (apiKeyEnv) {
299
+ if (apiKeyEnv && !proxyMode) {
284
300
  // Resolve before scrubbing so the selected provider may use its standard,
285
301
  // MIXDOG_AGENT_*, alias, or one provider-scoped keychain account. No
286
302
  // general config loader or all-provider credential scan is involved.
@@ -311,7 +327,15 @@ export function createPristineExecutionBoundary({
311
327
 
312
328
  let authMode = 'provider-managed';
313
329
  let catalogCount = 0;
314
- if (oauth) {
330
+ if (proxyMode) {
331
+ // No API key, OAuth account or credential binding: the PC behind the
332
+ // daemon runs every call. The runtime root stays the pristine one, for
333
+ // the services this run owns; only model calls reach the daemon.
334
+ setEnv(MODEL_PROXY_ENV, 'client');
335
+ bridgeChannel = new ModelProxyBridgeChannel(bridgeEndpoint);
336
+ restoreModelProxyChannel = installModelProxyChannel(bridgeChannel);
337
+ authMode = 'model-proxy-bridge';
338
+ } else if (oauth) {
315
339
  // Sign-in writes the selected account of the host's provider-accounts
316
340
  // pool; the single legacy file remains for hosts that predate the pool.
317
341
  // An explicit credential path still overrides both. Read the pool from
@@ -340,8 +364,8 @@ export function createPristineExecutionBoundary({
340
364
  [selectedProvider]: sourceCredential,
341
365
  });
342
366
  authMode = 'in-process-host-oauth-binding';
343
- const sourceCatalog = join(sourceDataDir, oauth.modelCatalogFile);
344
- if (oauth.modelCatalogFile && existsSync(sourceCatalog)) {
367
+ const sourceCatalog = oauth.modelCatalogFile && join(sourceDataDir, oauth.modelCatalogFile);
368
+ if (sourceCatalog && existsSync(sourceCatalog)) {
345
369
  copyFileSync(sourceCatalog, join(dataDir, oauth.modelCatalogFile));
346
370
  catalogCount = 1;
347
371
  }
@@ -1,7 +1,7 @@
1
1
  import { createHash, randomUUID } from 'node:crypto';
2
2
  import { readFileSync } from 'node:fs';
3
3
  import { join } from 'node:path';
4
- import { parseJsonText, updateJsonAtomicSync } from './atomic-file.mjs';
4
+ import { parseJsonText, updateJsonAtomic } from './atomic-file.mjs';
5
5
  import { resolvePluginData } from './plugin-paths.mjs';
6
6
 
7
7
  export const ACCOUNT_PROVIDERS = Object.freeze([
@@ -9,6 +9,7 @@ export const ACCOUNT_PROVIDERS = Object.freeze([
9
9
  'anthropic-oauth',
10
10
  'grok-oauth',
11
11
  'muse-code-oauth',
12
+ 'command-code-oauth',
12
13
  'mimo-oauth',
13
14
  'cursor-oauth',
14
15
  'antigravity-oauth',
@@ -74,16 +75,39 @@ function normalizeLabels(pool) {
74
75
  return { ...pool, accounts };
75
76
  }
76
77
 
78
+ // How the roster picks an account. `manual` serves from the selected account
79
+ // only; `limit` keeps the selected account until it reaches a limit and then
80
+ // moves to the account with the most headroom; `headroom` starts every new
81
+ // session on the account with the most headroom and keeps it for that session.
82
+ export const ACCOUNT_POLICIES = Object.freeze(['manual', 'limit', 'headroom']);
83
+
84
+ // `auto` predates `policy` and is still written beside it, so a build that
85
+ // knows only `auto` reads the same switching on/off. Whenever the two
86
+ // disagree (an older build toggled `auto`), `auto` decides whether switching
87
+ // is on at all.
88
+ export function providerAccountPolicy(pool) {
89
+ if (pool?.auto === false) return 'manual';
90
+ return pool?.policy === 'headroom' ? 'headroom' : 'limit';
91
+ }
92
+
93
+ function normalizePool(pool) {
94
+ if (!pool) return pool;
95
+ const policy = providerAccountPolicy(pool);
96
+ return { ...normalizeLabels(pool), policy, auto: policy !== 'manual' };
97
+ }
98
+
77
99
  export function readProviderAccountPool(provider, dataDir = resolvePluginData()) {
78
100
  requireAccountProvider(provider);
79
- return normalizeLabels(read(dataDir).providers[provider]) || { accounts: [], selectedId: null, auto: true };
101
+ return (
102
+ normalizePool(read(dataDir).providers[provider]) || { accounts: [], selectedId: null, auto: true, policy: 'limit' }
103
+ );
80
104
  }
81
105
 
82
- function update(provider, mutate) {
106
+ async function update(provider, mutate) {
83
107
  requireAccountProvider(provider);
84
108
  // Read failures must not be interpreted as an empty account roster.
85
109
  read();
86
- const result = updateJsonAtomicSync(
110
+ const result = await updateJsonAtomic(
87
111
  file(),
88
112
  (raw) => {
89
113
  const state = validate(raw);
@@ -94,7 +118,7 @@ function update(provider, mutate) {
94
118
  },
95
119
  { mode: 0o600, secret: true, fsyncDir: true }
96
120
  );
97
- return normalizeLabels(result.providers[provider]);
121
+ return normalizePool(result.providers[provider]);
98
122
  }
99
123
 
100
124
  /**
@@ -117,7 +141,7 @@ export function newProviderAccountId() {
117
141
  return randomUUID();
118
142
  }
119
143
 
120
- export function registerProviderAccount(provider, id, { label = '', includeDefault = false } = {}) {
144
+ export async function registerProviderAccount(provider, id, { label = '', includeDefault = false } = {}) {
121
145
  requireAccountId(id);
122
146
  return update(provider, (pool) => {
123
147
  if (includeDefault && !pool.accounts.some((row) => row.id === 'default')) {
@@ -131,12 +155,12 @@ export function registerProviderAccount(provider, id, { label = '', includeDefau
131
155
  });
132
156
  }
133
157
 
134
- export function changeProviderAccounts(provider, change) {
158
+ export async function changeProviderAccounts(provider, change) {
135
159
  if (
136
160
  !change ||
137
161
  typeof change !== 'object' ||
138
162
  Array.isArray(change) ||
139
- Object.keys(change).some((key) => !['selectedId', 'order', 'auto', 'useCredits', 'rename'].includes(key))
163
+ Object.keys(change).some((key) => !['selectedId', 'order', 'auto', 'policy', 'useCredits', 'rename'].includes(key))
140
164
  ) {
141
165
  throw new TypeError('Invalid provider account change.');
142
166
  }
@@ -175,8 +199,19 @@ export function changeProviderAccounts(provider, change) {
175
199
  }
176
200
  if (change.auto !== undefined) {
177
201
  if (typeof change.auto !== 'boolean') throw new TypeError('Automatic account switching must be boolean.');
202
+ // Turning switching on keeps a non-manual policy; on from manual is the
203
+ // classic switch-at-limit behaviour.
204
+ const current = providerAccountPolicy(pool);
205
+ if (!change.auto) pool.policy = 'manual';
206
+ else if (current === 'manual') pool.policy = 'limit';
207
+ else pool.policy = current;
178
208
  pool.auto = change.auto;
179
209
  }
210
+ if (change.policy !== undefined) {
211
+ if (!ACCOUNT_POLICIES.includes(change.policy)) throw new TypeError('Unknown account switching policy.');
212
+ pool.policy = change.policy;
213
+ pool.auto = change.policy !== 'manual';
214
+ }
180
215
  if (change.useCredits !== undefined) {
181
216
  if (typeof change.useCredits !== 'boolean') throw new TypeError('Credit use must be boolean.');
182
217
  pool.useCredits = change.useCredits;
@@ -184,7 +219,24 @@ export function changeProviderAccounts(provider, change) {
184
219
  });
185
220
  }
186
221
 
187
- export function removeProviderAccount(provider, id) {
222
+ // Selects `id` only while the selection is still `expectedSelectedId`, checked
223
+ // under the write's own lock, so a newer selection (a manual switch) wins. A
224
+ // given `expectedPolicy` must also still hold: a policy change while the
225
+ // request ran (e.g. to Manual) cancels the commit.
226
+ // Returns the pool when the selection moved, otherwise null.
227
+ export async function selectProviderAccountIf(provider, expectedSelectedId, id, expectedPolicy) {
228
+ requireAccountId(id);
229
+ let moved = false;
230
+ const pool = await update(provider, (pool) => {
231
+ if (pool.selectedId !== expectedSelectedId || !pool.accounts.some((row) => row.id === id)) return;
232
+ if (expectedPolicy && providerAccountPolicy(pool) !== expectedPolicy) return;
233
+ pool.selectedId = id;
234
+ moved = true;
235
+ });
236
+ return moved ? pool : null;
237
+ }
238
+
239
+ export async function removeProviderAccount(provider, id) {
188
240
  requireAccountId(id);
189
241
  return update(provider, (pool) => {
190
242
  pool.accounts = pool.accounts.filter((row) => row.id !== id);
@@ -192,7 +244,7 @@ export function removeProviderAccount(provider, id) {
192
244
  });
193
245
  }
194
246
 
195
- export function recordProviderAccountUsage(provider, id, snapshot) {
247
+ export async function recordProviderAccountUsage(provider, id, snapshot) {
196
248
  if (!snapshot || !Array.isArray(snapshot.quotaWindows)) return;
197
249
  const windows = snapshot.quotaWindows.slice(0, 12).map((row) => ({
198
250
  label: String(row.label || '').slice(0, 40),
@@ -200,7 +252,7 @@ export function recordProviderAccountUsage(provider, id, snapshot) {
200
252
  resetAt: typeof row.resetAt === 'number' && Number.isFinite(row.resetAt) ? row.resetAt : null,
201
253
  }));
202
254
  if (!readProviderAccountPool(provider).accounts.some((row) => row.id === id)) return;
203
- update(provider, (pool) => {
255
+ await update(provider, (pool) => {
204
256
  const row = pool.accounts.find((entry) => entry.id === id);
205
257
  if (!row) return;
206
258
  row.usage = { windows, checkedAt: Date.now() };
@@ -225,7 +277,7 @@ const MAX_BLOCK_MS = 6 * 60 * 60_000;
225
277
  // early can tell us anything.
226
278
  // `options.quota` records that the block came from a typed quota refusal or an
227
279
  // exhausted usage window, so later sends still count it as quota evidence.
228
- export function blockProviderAccount(provider, id, until, model = '', source = 'inferred', options = {}) {
280
+ export async function blockProviderAccount(provider, id, until, model = '', source = 'inferred', options = {}) {
229
281
  return update(provider, (pool) => {
230
282
  const row = pool.accounts.find((entry) => entry.id === id);
231
283
  if (!row) return;
@@ -245,7 +297,7 @@ export function blockProviderAccount(provider, id, until, model = '', source = '
245
297
  // the refusal window and the meter recorded against the OLD subscription would
246
298
  // otherwise outlive it and refuse a request the provider would have served.
247
299
  // Both are dropped; the next usage reading refills them.
248
- export function clearProviderAccountQuotaState(provider, id) {
300
+ export async function clearProviderAccountQuotaState(provider, id) {
249
301
  return update(provider, (pool) => {
250
302
  const row = pool.accounts.find((entry) => entry.id === id);
251
303
  if (!row) return;
@@ -318,14 +370,53 @@ export function providerAccountsResetAt(rows, now = Date.now(), model = '', opti
318
370
  return known.length ? Math.min(...known) : null;
319
371
  }
320
372
 
373
+ function windowFull(row, window, now) {
374
+ return window.usedPct >= 100 && (window.resetAt ? window.resetAt > now : now - row.usage.checkedAt < 60_000);
375
+ }
376
+
321
377
  export function providerAccountExhausted(row, now = Date.now(), model = '', options = {}) {
322
378
  if (row?.blockedUntil > now && blockAppliesToModel(row, model)) return true;
323
379
  if (options.useCredits === true) return false;
324
380
  return (row?.usage?.windows || []).some(
325
- (window) =>
326
- window.usedPct >= 100 &&
327
- quotaWindowAppliesToModel(window, model) &&
328
- (window.resetAt ? window.resetAt > now : now - row.usage.checkedAt < 60_000)
381
+ (window) => quotaWindowAppliesToModel(window, model) && windowFull(row, window, now)
382
+ );
383
+ }
384
+
385
+ function quotaWindowFamily(window) {
386
+ return FAMILY_QUOTA_WINDOW.exec(String(window?.label || '').trim())?.[1] || '';
387
+ }
388
+
389
+ // Which limit currently holds the account, independent of any one model:
390
+ // `{ scope: 'account' }` when every request is refused (an unscoped window is
391
+ // full, or a block with no narrower meter to attribute it to), `{ scope:
392
+ // 'model', families }` when only model-family meters ("7D Fable") or a block
393
+ // attributed to one are spent, so other models still run — else null.
394
+ export function providerAccountLimit(row, now = Date.now(), options = {}) {
395
+ if (!row) return null;
396
+ const windows = row.usage?.windows || [];
397
+ const blocked = row.blockedUntil > now;
398
+ const scope = String(row.blockedModel || '').trim();
399
+ const scopedWindows = windows.filter((window) => quotaWindowFamily(window));
400
+ // A block counts as family-scoped only when a family meter covers the model
401
+ // that earned it; any other refusal is treated as the account's.
402
+ const covering = scope ? scopedWindows.filter((window) => quotaWindowAppliesToModel(window, scope)) : [];
403
+ if (blocked && !covering.length) return { scope: 'account' };
404
+ const credits = options.useCredits === true;
405
+ if (!credits && windows.some((window) => !quotaWindowFamily(window) && windowFull(row, window, now))) {
406
+ return { scope: 'account' };
407
+ }
408
+ const families = new Set(blocked ? covering.map(quotaWindowFamily) : []);
409
+ if (!credits) {
410
+ for (const window of scopedWindows) if (windowFull(row, window, now)) families.add(quotaWindowFamily(window));
411
+ }
412
+ return families.size ? { scope: 'model', families: [...families] } : null;
413
+ }
414
+
415
+ // The account refuses `model` only through a model-scoped limit: it still
416
+ // serves other models, so it remains the provider's account of record.
417
+ export function providerAccountExhaustedForModelOnly(row, now = Date.now(), model = '', options = {}) {
418
+ return (
419
+ providerAccountExhausted(row, now, model, options) && providerAccountLimit(row, now, options)?.scope === 'model'
329
420
  );
330
421
  }
331
422
 
@@ -347,15 +438,28 @@ function accountHeadroom(row, now, model) {
347
438
  return used.length ? 100 - Math.max(...used) : null;
348
439
  }
349
440
 
350
- export function chooseProviderAccount(pool, excluded = new Set(), now = Date.now(), model = '') {
441
+ // `sessionAccountId` is the account the requesting session already runs on
442
+ // (headroom policy only): kept while usable, so a session switches only on a
443
+ // limit. Without one, the session's first request takes the account with the
444
+ // most headroom.
445
+ export function chooseProviderAccount(
446
+ pool,
447
+ excluded = new Set(),
448
+ now = Date.now(),
449
+ model = '',
450
+ { sessionAccountId } = {}
451
+ ) {
351
452
  const options = { useCredits: pool.useCredits === true };
352
453
  const usable = (row) => row && !excluded.has(row.id) && !providerAccountExhausted(row, now, model, options);
353
454
  const selected = pool.accounts.find((row) => row.id === pool.selectedId);
354
- if (pool.auto === false) {
455
+ const policy = providerAccountPolicy(pool);
456
+ if (policy === 'manual') {
355
457
  if (excluded.has(selected?.id)) return null;
356
458
  return options.useCredits || usable(selected) ? selected || null : null;
357
459
  }
358
- if (usable(selected)) return selected;
460
+ const preferred =
461
+ policy === 'headroom' ? pool.accounts.find((row) => sessionAccountId && row.id === sessionAccountId) : selected;
462
+ if (usable(preferred)) return preferred;
359
463
  // Falling back in roster order is blind: the account after the one that just
360
464
  // refused may itself be nearly spent, so the request pays a second switch to
361
465
  // learn what a recorded meter already knew. Ranking needs evidence for EVERY
@@ -69,6 +69,18 @@ export function getAgentApiKey(provider) {
69
69
  return null;
70
70
  }
71
71
 
72
+ /** The one API-key resolver for key-based providers: an explicit config key
73
+ * wins, otherwise the environment/keychain (getAgentApiKey). */
74
+ export function resolveProviderApiKey(provider, config) {
75
+ return config?.apiKey || getAgentApiKey(provider) || '';
76
+ }
77
+
78
+ /** Auth-error reload: the stored/environment key is re-read first so a
79
+ * rotated key beats the stale one captured in config. */
80
+ export function reloadProviderApiKey(provider, config) {
81
+ return getAgentApiKey(provider) || config?.apiKey || '';
82
+ }
83
+
72
84
  /** One provider's key read on demand, warming no other account: the
73
85
  * environment first (getAgentApiKey's precedence), then only this provider's
74
86
  * keychain entry through the async path (no whole-keyring snapshot). */
@@ -236,6 +236,17 @@ export function forgetQuotaSuspendedGoal(sessionId) {
236
236
  return true;
237
237
  }
238
238
 
239
+ /** Whether a session record belongs to a delegated agent child (declared
240
+ * agent-only visibility, or a legacy non-Lead agent-owned child with a distinct
241
+ * parent). Such a session never owns a quota resume: the Lead decides retries. */
242
+ export function isAgentOnlySession(session) {
243
+ if (!session || typeof session !== 'object') return false;
244
+ const lower = (value) => text(value).toLowerCase();
245
+ if (lower(session.visibility) === 'agent-only') return true;
246
+ const parentId = text(session.parentSessionId || session.ownerSessionId);
247
+ return lower(session.owner) === 'agent' && lower(session.agent) !== 'lead' && parentId !== '' && parentId !== text(session.id);
248
+ }
249
+
239
250
  /** Global setting, default on. */
240
251
  export function quotaAutoResumeEnabled() {
241
252
  return readSection(SETTING_SECTION)?.autoResume !== false;