mixdog 1.2.1 → 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 (233) hide show
  1. package/package.json +1 -1
  2. package/scripts/gate-local.mjs +19 -3
  3. package/scripts/model-catalog-audit.mjs +1 -15
  4. package/src/agents/reviewer/AGENT.md +3 -3
  5. package/src/agents/reviewer/agent.json +1 -1
  6. package/src/defaults/shipped-history.json +1 -0
  7. package/src/repl/slash-commands.mjs +16 -1
  8. package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +29 -4
  9. package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +10 -0
  10. package/src/runtime/agent/orchestrator/mcp/client-config.mjs +36 -7
  11. package/src/runtime/agent/orchestrator/mcp/client-handshake.mjs +49 -10
  12. package/src/runtime/agent/orchestrator/mcp/client-transport.mjs +40 -3
  13. package/src/runtime/agent/orchestrator/mcp/client.mjs +230 -96
  14. package/src/runtime/agent/orchestrator/mcp/oauth-callback.mjs +103 -0
  15. package/src/runtime/agent/orchestrator/mcp/oauth-credentials.mjs +150 -0
  16. package/src/runtime/agent/orchestrator/mcp/oauth-login.mjs +111 -0
  17. package/src/runtime/agent/orchestrator/mcp/oauth-probe.mjs +87 -0
  18. package/src/runtime/agent/orchestrator/mcp/oauth-provider.mjs +95 -0
  19. package/src/runtime/agent/orchestrator/mcp/oauth-request.mjs +122 -0
  20. package/src/runtime/agent/orchestrator/mcp/oauth-shared.mjs +9 -0
  21. package/src/runtime/agent/orchestrator/mcp/oauth.mjs +229 -0
  22. package/src/runtime/agent/orchestrator/providers/account-pool.mjs +188 -29
  23. package/src/runtime/agent/orchestrator/providers/anthropic-api-recovery.mjs +2 -1
  24. package/src/runtime/agent/orchestrator/providers/anthropic-api-transport.mjs +94 -31
  25. package/src/runtime/agent/orchestrator/providers/anthropic-error-contract.mjs +96 -0
  26. package/src/runtime/agent/orchestrator/providers/anthropic-fast-mode.mjs +39 -18
  27. package/src/runtime/agent/orchestrator/providers/anthropic-max-tokens-recovery.mjs +55 -0
  28. package/src/runtime/agent/orchestrator/providers/anthropic-max-tokens.mjs +25 -21
  29. package/src/runtime/agent/orchestrator/providers/anthropic-messages.mjs +3 -1
  30. package/src/runtime/agent/orchestrator/providers/anthropic-midstream-recovery.mjs +18 -4
  31. package/src/runtime/agent/orchestrator/providers/anthropic-model-resolve.mjs +13 -1
  32. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-client-version.mjs +1 -1
  33. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-midstream.mjs +10 -1
  34. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-recovery.mjs +23 -9
  35. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-request/initial-status.mjs +46 -20
  36. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-request.mjs +14 -9
  37. package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +73 -35
  38. package/src/runtime/agent/orchestrator/providers/anthropic-overload-fallback.mjs +69 -0
  39. package/src/runtime/agent/orchestrator/providers/anthropic-server-fallback.mjs +4 -0
  40. package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +8 -1
  41. package/src/runtime/agent/orchestrator/providers/anthropic.mjs +34 -7
  42. package/src/runtime/agent/orchestrator/providers/antigravity-oauth-catalog.mjs +30 -9
  43. package/src/runtime/agent/orchestrator/providers/antigravity-oauth.mjs +7 -0
  44. package/src/runtime/agent/orchestrator/providers/antigravity-request.mjs +30 -14
  45. package/src/runtime/agent/orchestrator/providers/antigravity-response.mjs +2 -1
  46. package/src/runtime/agent/orchestrator/providers/antigravity-retry-policy.mjs +93 -0
  47. package/src/runtime/agent/orchestrator/providers/antigravity-transport.mjs +82 -9
  48. package/src/runtime/agent/orchestrator/providers/command-code-oauth.mjs +3 -0
  49. package/src/runtime/agent/orchestrator/providers/cursor-auth.mjs +38 -5
  50. package/src/runtime/agent/orchestrator/providers/cursor-client-version.mjs +1 -1
  51. package/src/runtime/agent/orchestrator/providers/cursor-wire-guards.mjs +97 -2
  52. package/src/runtime/agent/orchestrator/providers/cursor-wire-stream-bridge.mjs +102 -23
  53. package/src/runtime/agent/orchestrator/providers/cursor-wire-transport.mjs +103 -33
  54. package/src/runtime/agent/orchestrator/providers/cursor-wire.mjs +29 -6
  55. package/src/runtime/agent/orchestrator/providers/cursor.mjs +18 -1
  56. package/src/runtime/agent/orchestrator/providers/effort-configuration.mjs +77 -17
  57. package/src/runtime/agent/orchestrator/providers/gemini-cache-policy.mjs +1 -1
  58. package/src/runtime/agent/orchestrator/providers/gemini-cache.mjs +29 -8
  59. package/src/runtime/agent/orchestrator/providers/gemini-error-contract.mjs +191 -0
  60. package/src/runtime/agent/orchestrator/providers/gemini-family.mjs +9 -0
  61. package/src/runtime/agent/orchestrator/providers/gemini-history-signatures.mjs +9 -4
  62. package/src/runtime/agent/orchestrator/providers/gemini-response.mjs +3 -1
  63. package/src/runtime/agent/orchestrator/providers/gemini-retry-policy.mjs +180 -0
  64. package/src/runtime/agent/orchestrator/providers/gemini-schema.mjs +21 -82
  65. package/src/runtime/agent/orchestrator/providers/gemini-sdk-request.mjs +4 -1
  66. package/src/runtime/agent/orchestrator/providers/gemini-stream.mjs +71 -12
  67. package/src/runtime/agent/orchestrator/providers/gemini-thinking.mjs +21 -20
  68. package/src/runtime/agent/orchestrator/providers/gemini.mjs +56 -23
  69. package/src/runtime/agent/orchestrator/providers/grok-oauth-tokens.mjs +3 -0
  70. package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +68 -19
  71. package/src/runtime/agent/orchestrator/providers/mimo-oauth.mjs +3 -0
  72. package/src/runtime/agent/orchestrator/providers/model-list-sanitize.mjs +2 -1
  73. package/src/runtime/agent/orchestrator/providers/muse-code-oauth.mjs +4 -9
  74. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-send.mjs +4 -2
  75. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream/stream-result.mjs +22 -0
  76. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream.mjs +1 -0
  77. package/src/runtime/agent/orchestrator/providers/openai-compat-error-contract.mjs +50 -0
  78. package/src/runtime/agent/orchestrator/providers/openai-compat-response-normalization.mjs +23 -0
  79. package/src/runtime/agent/orchestrator/providers/openai-compat-responses-events.mjs +16 -8
  80. package/src/runtime/agent/orchestrator/providers/openai-compat-responses-state.mjs +3 -2
  81. package/src/runtime/agent/orchestrator/providers/openai-compat-responses.mjs +94 -13
  82. package/src/runtime/agent/orchestrator/providers/openai-compat-retry-profile.mjs +193 -0
  83. package/src/runtime/agent/orchestrator/providers/openai-compat-stream-common.mjs +66 -2
  84. package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +2 -1
  85. package/src/runtime/agent/orchestrator/providers/openai-compat-wire.mjs +6 -0
  86. package/src/runtime/agent/orchestrator/providers/openai-compat-xai-send.mjs +4 -2
  87. package/src/runtime/agent/orchestrator/providers/openai-compat-xai.mjs +23 -14
  88. package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +17 -3
  89. package/src/runtime/agent/orchestrator/providers/openai-direct-request.mjs +42 -25
  90. package/src/runtime/agent/orchestrator/providers/openai-error-contract.mjs +274 -0
  91. package/src/runtime/agent/orchestrator/providers/openai-http-sse-request.mjs +72 -32
  92. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-events/terminal-frames.mjs +18 -11
  93. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-outcome.mjs +1 -2
  94. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-stamps.mjs +2 -2
  95. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-state.mjs +2 -3
  96. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state.mjs +2 -2
  97. package/src/runtime/agent/orchestrator/providers/openai-http-sse-watchdogs.mjs +1 -3
  98. package/src/runtime/agent/orchestrator/providers/openai-oauth-dispatch.mjs +34 -3
  99. package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +5 -1
  100. package/src/runtime/agent/orchestrator/providers/openai-oauth-tokens.mjs +22 -2
  101. package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +5 -0
  102. package/src/runtime/agent/orchestrator/providers/openai-responses-input.mjs +5 -9
  103. package/src/runtime/agent/orchestrator/providers/openai-retry-policy.mjs +9 -0
  104. package/src/runtime/agent/orchestrator/providers/openai-ws-handshake.mjs +16 -8
  105. package/src/runtime/agent/orchestrator/providers/openai-ws-send-attempts/attempt-context.mjs +1 -2
  106. package/src/runtime/agent/orchestrator/providers/openai-ws-send-attempts/policy.mjs +25 -14
  107. package/src/runtime/agent/orchestrator/providers/openai-ws-terminal.mjs +18 -11
  108. package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +31 -20
  109. package/src/runtime/agent/orchestrator/providers/provider-error-kind.mjs +581 -0
  110. package/src/runtime/agent/orchestrator/providers/retry-backoff.mjs +46 -7
  111. package/src/runtime/agent/orchestrator/providers/retry-classification.mjs +118 -219
  112. package/src/runtime/agent/orchestrator/providers/retry-classifier.mjs +2 -0
  113. package/src/runtime/agent/orchestrator/providers/retry-executor.mjs +148 -45
  114. package/src/runtime/agent/orchestrator/providers/retry-midstream.mjs +81 -16
  115. package/src/runtime/agent/orchestrator/runtime-core/config-helpers.mjs +3 -0
  116. package/src/runtime/agent/orchestrator/runtime-core/goal-reminder.mjs +0 -18
  117. package/src/runtime/agent/orchestrator/runtime-core/model-capabilities.mjs +57 -28
  118. package/src/runtime/agent/orchestrator/runtime-core/orchestration.mjs +19 -16
  119. package/src/runtime/agent/orchestrator/runtime-core/plugin-mcp.mjs +1 -97
  120. package/src/runtime/agent/orchestrator/session/catalog-db/schema.mjs +3 -1
  121. package/src/runtime/agent/orchestrator/session/loop/send-phase.mjs +4 -0
  122. package/src/runtime/agent/orchestrator/session/loop/step-auto-effort.mjs +102 -18
  123. package/src/runtime/agent/orchestrator/session/loop/tool-phase.mjs +3 -1
  124. package/src/runtime/agent/orchestrator/session/manager/ask-queue.mjs +30 -2
  125. package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +11 -2
  126. package/src/runtime/agent/orchestrator/session/manager/ask-turn-execute.mjs +41 -21
  127. package/src/runtime/agent/orchestrator/session/manager/pending-message-entry.mjs +5 -10
  128. package/src/runtime/agent/orchestrator/session/manager/session-close.mjs +1 -1
  129. package/src/runtime/agent/orchestrator/session/manager/turn-interruption.mjs +18 -7
  130. package/src/runtime/agent/orchestrator/session/send-with-recovery.mjs +155 -29
  131. package/src/runtime/agent/orchestrator/session/store/save-worker.mjs +6 -3
  132. package/src/runtime/agent/orchestrator/session/store/write-guards.mjs +16 -0
  133. package/src/runtime/agent/orchestrator/session/store-agent-worker-rows.mjs +22 -10
  134. package/src/runtime/agent/orchestrator/session/store-summary-catalog.mjs +2 -0
  135. package/src/runtime/agent/orchestrator/session/store-summary-fields.mjs +5 -0
  136. package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +3 -0
  137. package/src/runtime/agent/orchestrator/session/transcript-restore/restore.mjs +20 -1
  138. package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +2 -2
  139. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/deadline-plan.mjs +12 -15
  140. package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +5 -5
  141. package/src/runtime/memory/lib/pg/process.mjs +34 -24
  142. package/src/runtime/memory/lib/pg/supervisor.mjs +16 -13
  143. package/src/runtime/office/README.md +1 -1
  144. package/src/runtime/office/shared/values.mjs +0 -5
  145. package/src/runtime/shared/background-tasks.mjs +92 -14
  146. package/src/runtime/shared/err-text.mjs +25 -0
  147. package/src/runtime/shared/error-presentation.mjs +159 -19
  148. package/src/runtime/shared/llm/anthropic-betas.mjs +4 -3
  149. package/src/runtime/shared/llm/anthropic-effort.mjs +123 -134
  150. package/src/runtime/shared/llm/anthropic-thinking-contract.mjs +50 -14
  151. package/src/runtime/shared/llm/model-catalog.mjs +53 -40
  152. package/src/runtime/shared/llm/openai-daybreak.mjs +4 -16
  153. package/src/runtime/shared/llm/provider-catalog-registry.mjs +11 -0
  154. package/src/runtime/shared/llm/provider-model-identities.mjs +13 -4
  155. package/src/runtime/shared/llm/usage-rollup.mjs +0 -1
  156. package/src/runtime/shared/model-display.mjs +13 -0
  157. package/src/runtime/shared/quota-resume-store.mjs +11 -0
  158. package/src/runtime/shared/session-baseline-store.mjs +960 -0
  159. package/src/runtime/shared/statusline/statusline-format.mjs +4 -4
  160. package/src/runtime/shared/task-notification-envelope.mjs +2 -2
  161. package/src/runtime/shared/turn-changes.mjs +447 -219
  162. package/src/runtime/shared/turn-snapshot-store.mjs +182 -1
  163. package/src/runtime/shared/turn-snapshot.mjs +170 -48
  164. package/src/runtime/shared/turn-worktree-snapshot.mjs +135 -46
  165. package/src/runtime/shared/worktrees/contract.mjs +0 -24
  166. package/src/runtime/shared/worktrees/test-support.mjs +19 -4
  167. package/src/session-runtime/boot/apis.mjs +6 -0
  168. package/src/session-runtime/boot/infrastructure.mjs +6 -0
  169. package/src/session-runtime/lifecycle/session-catalog.mjs +2 -0
  170. package/src/session-runtime/lifecycle/teardown.mjs +1 -1
  171. package/src/session-runtime/mcp-glue/connect.mjs +29 -9
  172. package/src/session-runtime/mcp-glue/server-catalog.mjs +20 -3
  173. package/src/session-runtime/mcp-glue.mjs +3 -0
  174. package/src/session-runtime/mcp-oauth-login.mjs +174 -0
  175. package/src/session-runtime/model-route/model-switch-target.mjs +34 -0
  176. package/src/session-runtime/model-route/set-route.mjs +83 -15
  177. package/src/session-runtime/model-route/web-search-route.mjs +1 -1
  178. package/src/session-runtime/native-web-search/routes.mjs +22 -3
  179. package/src/session-runtime/provider-models/model-meta.mjs +12 -3
  180. package/src/session-runtime/quick-model-rows/web-search-rows.mjs +13 -8
  181. package/src/session-runtime/quick-web-search-models.mjs +13 -38
  182. package/src/session-runtime/resource-extension-sync.mjs +4 -0
  183. package/src/session-runtime/resource-mcp-api.mjs +98 -2
  184. package/src/session-runtime/runtime-review-api.mjs +13 -0
  185. package/src/session-runtime/services/agent-tool/job-task-reconcile.mjs +13 -1
  186. package/src/session-runtime/services/agent-tool/job-views/agent-list.mjs +7 -2
  187. package/src/session-runtime/services/agent-tool/send-flow.mjs +22 -4
  188. package/src/session-runtime/services/agent-tool/spawn-flow/job-start.mjs +2 -0
  189. package/src/session-runtime/services/agent-tool/spawn-flow/spawn-run.mjs +22 -5
  190. package/src/session-runtime/services/agent-tool/worker-index/row-liveness.mjs +8 -1
  191. package/src/session-runtime/services/agent-tool/worker-index/row-shape.mjs +2 -2
  192. package/src/session-runtime/services/agent-tool/worker-rows.mjs +17 -0
  193. package/src/session-runtime/settings-compaction-api.mjs +1 -0
  194. package/src/standalone/daemon-drain-policy.mjs +8 -0
  195. package/src/standalone/daemon-telemetry.mjs +55 -0
  196. package/src/standalone/daemon.mjs +3 -7
  197. package/src/standalone/session-protocol.mjs +7 -0
  198. package/src/standalone/session-service/agent-tree/agent-cancel.mjs +33 -2
  199. package/src/standalone/session-service/agent-tree/agent-turns.mjs +26 -10
  200. package/src/standalone/session-service/agent-tree.mjs +12 -0
  201. package/src/standalone/session-service/quota-resume.mjs +8 -0
  202. package/src/standalone/session-service/retention/busy.mjs +15 -1
  203. package/src/standalone/session-service-api.mjs +5 -0
  204. package/src/standalone/session-service.mjs +1 -0
  205. package/src/tui/app/app-view.jsx +1 -0
  206. package/src/tui/app/create-app-pickers.mjs +4 -0
  207. package/src/tui/app/extension-pickers/mcp-login.mjs +107 -0
  208. package/src/tui/app/extension-pickers/mcp-servers-picker.mjs +2 -1
  209. package/src/tui/app/extension-pickers.mjs +2 -1
  210. package/src/tui/app/model-options.mjs +11 -16
  211. package/src/tui/app/slash-commands.mjs +7 -2
  212. package/src/tui/app/slash-dispatch/panel-commands.mjs +16 -1
  213. package/src/tui/components/StatusLine.jsx +2 -0
  214. package/src/tui/components/status-line/local-line.mjs +5 -2
  215. package/src/tui/components/status-line/use-statusline-text.mjs +7 -1
  216. package/src/tui/dist/index.mjs +816 -43
  217. package/src/tui/session/quota-resume.mjs +46 -14
  218. package/src/tui/session/session-api/commands.mjs +33 -24
  219. package/src/tui/session/session-api/extensions.mjs +22 -0
  220. package/src/tui/session/session-api/integrations.mjs +7 -0
  221. package/src/tui/session/session-api/lifecycle.mjs +5 -1
  222. package/src/tui/session/session-api/routes.mjs +71 -7
  223. package/src/tui/session/session-api/settings.mjs +5 -14
  224. package/src/tui/session/session-flow/drain.mjs +7 -1
  225. package/src/tui/session/session-flow/failed-turn-rewind.mjs +10 -4
  226. package/src/tui/session/turn.mjs +38 -15
  227. package/src/tui/statusline-ansi-bridge.mjs +1 -1
  228. package/src/ui/statusline-agents.mjs +15 -4
  229. package/src/ui/statusline.mjs +7 -3
  230. package/src/ui/streaming-markdown-heal.mjs +34 -3
  231. package/src/vendor/statusline/src/gateway/claude-current.mjs +14 -4
  232. package/src/runtime/memory/lib/memory-session-merge.mjs +0 -32
  233. package/src/runtime/shared/llm/usage-session-history.mjs +0 -266
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mixdog",
3
- "version": "1.2.1",
3
+ "version": "1.2.2",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "Open-source coding agent that matches Codex on Terminal-Bench 2.1 with 63% fewer tokens.",
@@ -2,10 +2,17 @@
2
2
  // Local pre-push check. Mirrors the test legs of .github/workflows/release-gate.yml
3
3
  // (jobs: runtime, runtime-slow, desktop, desktop-tests fast/slow). It MUST be
4
4
  // updated together with that workflow whenever those commands change.
5
+ // Up front, before any build or leg, it runs two fast source checks and stops
6
+ // if either fails: the gate's shipped-history job (build-shipped-history.mjs
7
+ // --check; needs a full clone, a shallow one keeps the committed file), and the
8
+ // released-section changelog check the desktop fast lane asserts
9
+ // (changelog-locales.test.mjs; Deploy's plan adds the Unreleased section).
5
10
  // Not covered locally: runtime-macos, computer-backend (cargo + Linux X11),
6
11
  // desktop-tests `computer` lane (windows-latest, src/main/computer/ only),
7
- // relay (apps/relay tests), graph (cargo test). The gate's path filtering is
8
- // not applied: every leg always runs. Run `npm ci` (root and apps/desktop) first.
12
+ // relay (apps/relay tests), graph (cargo test), patch (cargo test of the
13
+ // checked-out engine), mobile-scripts (Python unittest). The gate's path
14
+ // filtering is not applied: every check and leg always runs. Run `npm ci`
15
+ // (root and apps/desktop) first.
9
16
  import { spawn, spawnSync } from 'node:child_process';
10
17
  import fs from 'node:fs';
11
18
  import os from 'node:os';
@@ -16,6 +23,15 @@ import { gateTestBudget, runGateLegs } from './lib/gate-scheduler.mjs';
16
23
  const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
17
24
  const started = Date.now();
18
25
 
26
+ const preChecks = ['node scripts/build-shipped-history.mjs --check', 'node scripts/check-changelog-locales.mjs'];
27
+ const failedPreChecks = preChecks.filter(
28
+ (cmd) => spawnSync(cmd, { cwd: root, stdio: 'inherit', shell: true }).status !== 0
29
+ );
30
+ if (failedPreChecks.length) {
31
+ console.error(`[gate:local] pre-check failed: ${failedPreChecks.join('; ')}`);
32
+ process.exit(1);
33
+ }
34
+
19
35
  const build = spawnSync('npm run build:spawn:test', { cwd: root, stdio: 'inherit', shell: true });
20
36
  if (build.status !== 0) {
21
37
  console.error('[gate:local] build:spawn:test failed');
@@ -87,7 +103,7 @@ for (const leg of legs) {
87
103
  }
88
104
  }
89
105
  console.log(
90
- 'Not covered locally: runtime-macos, computer-backend, desktop-tests computer lane (Windows-only), relay, graph'
106
+ 'Not covered locally: runtime-macos, computer-backend, desktop-tests computer lane (Windows-only), relay, graph, patch, mobile-scripts'
91
107
  );
92
108
  console.log(`Total: ${((Date.now() - started) / 1000).toFixed(1)}s`);
93
109
  process.exit(legs.every((l) => l.ok) ? 0 : 1);
@@ -6,7 +6,6 @@ import {
6
6
  warmModelMetadataCatalogs,
7
7
  auditModelPricing,
8
8
  } from '../src/runtime/agent/orchestrator/providers/model-catalog.mjs';
9
- import { QUICK_WEB_SEARCH_MODELS } from '../src/session-runtime/quick-web-search-models.mjs';
10
9
 
11
10
  const args = new Set(process.argv.slice(2));
12
11
  const json = args.has('--json');
@@ -154,21 +153,8 @@ for (const [name, provider] of getAllProviders()) {
154
153
  });
155
154
  }
156
155
 
156
+ // Quick web-search rows carry no static windows any more (they come from the catalogs).
157
157
  const quickRows = [];
158
- for (const [provider, models] of Object.entries(QUICK_WEB_SEARCH_MODELS)) {
159
- for (const model of models) {
160
- if (model?.contextWindow == null) continue;
161
- const external = externalMeta(modelsDev, litellm, provider, model.id);
162
- const externalContext = external.modelsDevContext ?? external.litellmContext;
163
- quickRows.push({
164
- provider,
165
- id: model.id,
166
- staticContext: n(model.contextWindow),
167
- externalContext,
168
- mismatch: n(model.contextWindow) != null && externalContext != null && n(model.contextWindow) !== externalContext,
169
- });
170
- }
171
- }
172
158
 
173
159
  const mismatches = providerResults.flatMap((p) => p.rows.filter((r) => r.contextMismatch || r.outputMismatch));
174
160
  const exactOneM = providerResults.flatMap((p) => p.rows.filter((r) => r.liveContext === 1_000_000));
@@ -1,12 +1,12 @@
1
1
  ---
2
2
  name: Reviewer
3
- description: Use once at the final check of non-trivial work: after the implementation is written and verified, before reporting completion, to independently verify correctness, regressions, risks, and acceptance criteria. Skip for low-difficulty work.
3
+ description: Use only at the end, as the final check of non-trivial work: after the implementation is written and verified, before reporting completion, to independently verify correctness, regressions, risks, and acceptance criteria. Skip for low-difficulty work.
4
4
  ---
5
5
 
6
6
  Independent final-check review agent.
7
7
 
8
- You are called once, after the deliverable is written and the Lead's own
9
- verification has run, before completion is reported.
8
+ You are called only at the end, after the deliverable is written and the
9
+ Lead's own verification has run, before completion is reported.
10
10
 
11
11
  Inspect the diff, affected boundaries, existing tests, and stated acceptance
12
12
  criteria with independent judgment. Run the necessary builds, tests, lint, or
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "id": "reviewer",
3
3
  "name": "Reviewer",
4
- "description": "Use once at the final check of non-trivial work: after the implementation is written and verified, before reporting completion, to independently verify correctness, regressions, risks, and acceptance criteria. Skip for low-difficulty work.",
4
+ "description": "Use only at the end, as the final check of non-trivial work: after the implementation is written and verified, before reporting completion, to independently verify correctness, regressions, risks, and acceptance criteria. Skip for low-difficulty work.",
5
5
  "entry": "AGENT.md"
6
6
  }
@@ -26,6 +26,7 @@
26
26
  "reviewer": [
27
27
  "07643b62440d3eba29b7360b8a33f09f",
28
28
  "25b44b46269490e26ca31c1852925171",
29
+ "a60ea68f9287d55489e92e5450432f62",
29
30
  "a61cb4557def4e7ed647d4bdacbb72e4",
30
31
  "de860d4111b61985f3b59eb1011fa421"
31
32
  ],
@@ -3,6 +3,7 @@
3
3
  * to `ctx.out`; only `/exit` and `/quit` return 'exit'.
4
4
  */
5
5
  import { dim, green, red, yellow, colorEnabled } from '../runtime/shared/statusline/ansi.mjs';
6
+ import { switchesSessionModel } from '../session-runtime/model-route/model-switch-target.mjs';
6
7
  import { createSessionStats } from '../ui/session-stats.mjs';
7
8
  import { statuslineFor } from './turn-output.mjs';
8
9
 
@@ -65,7 +66,21 @@ async function setModel(ctx, arg) {
65
66
  return;
66
67
  }
67
68
  const runtime = await ctx.ensureRuntime();
68
- await runtime.setRoute({ model: arg });
69
+ const request = { model: arg };
70
+ // The user's model choice switches this conversation in place; with
71
+ // auto-compact on model change (default on) the history is compacted on the
72
+ // current model first. A failed compaction never blocks the switch.
73
+ if (switchesSessionModel(runtime, request) && runtime.getCompactionSettings?.()?.onModelChange !== false) {
74
+ // An invalid or unavailable selector is rejected before anything compacts.
75
+ await runtime.setRoute(request, { validateOnly: true });
76
+ out.write(`${dim('compacting conversation before the model change…')}\n`);
77
+ try {
78
+ await runtime.compact();
79
+ } catch {
80
+ /* next-turn auto-compaction fits the new window */
81
+ }
82
+ }
83
+ await runtime.setRoute(request, { switchEstablished: true });
69
84
  out.write(`${green(`✓ model → ${runtime.provider}/${runtime.model}`)}\n`);
70
85
  }
71
86
 
@@ -59,6 +59,25 @@ function assistantMessageText(content) {
59
59
  .join('\n');
60
60
  }
61
61
 
62
+ // Visible assistant text inside a providerReplay envelope. Item shapes by
63
+ // provider: anthropic `{type:'text',text}`; openai/xai responses
64
+ // `{type:'message',content:[{type:'output_text',text}]}`; gemini/antigravity
65
+ // parts `{text}` (reasoning is `{thought:true,text}`). Reasoning/thinking and
66
+ // tool items are skipped.
67
+ function replayItemText(item) {
68
+ if (!item || typeof item !== 'object') return '';
69
+ if (item.type === 'message') return assistantMessageText(item.content);
70
+ if (item.type === 'text' || item.type === 'output_text' || (item.type === undefined && item.thought !== true)) {
71
+ return typeof item.text === 'string' ? item.text : '';
72
+ }
73
+ return '';
74
+ }
75
+
76
+ function providerReplayText(replay) {
77
+ const items = Array.isArray(replay?.items) ? replay.items : [];
78
+ return items.map(replayItemText).filter(Boolean).join('\n');
79
+ }
80
+
62
81
  /** Message index at askSession start — salvage only assistant rows appended this run. */
63
82
  export function resolveHandoffMessageStartIndex(session) {
64
83
  const messages = Array.isArray(session?.messages) ? session.messages : [];
@@ -72,7 +91,8 @@ function collectSessionAssistantHandoffText(session, messageStartIndex = 0) {
72
91
  for (let i = start; i < messages.length; i += 1) {
73
92
  const m = messages[i];
74
93
  if (m?.role !== 'assistant') continue;
75
- const t = assistantMessageText(m.content).trim();
94
+ const contentText = assistantMessageText(m.content).trim();
95
+ const t = contentText || providerReplayText(m.providerReplay).trim();
76
96
  if (t && t !== '.') parts.push(t);
77
97
  }
78
98
  return parts.length ? parts.join('\n\n') : '';
@@ -80,9 +100,14 @@ function collectSessionAssistantHandoffText(session, messageStartIndex = 0) {
80
100
 
81
101
  export function watchdogPartialHandoffFromError(error, session, messageStartIndex = 0) {
82
102
  if (!isAgentProgressWatchdogAbortError(error)) return null;
83
- // A canonical agent turn cannot expose its live transcript here, so it
84
- // reads the text itself and carries it on the stall error.
85
- if (typeof error.partialHandoff === 'string') return error.partialHandoff;
103
+ return turnPartialHandoff(error, session, messageStartIndex);
104
+ }
105
+
106
+ /** Assistant text a stopped turn produced before it stopped. A canonical agent
107
+ * turn cannot expose its live transcript here, so it reads the text itself
108
+ * and carries it on the turn's error. */
109
+ export function turnPartialHandoff(error, session, messageStartIndex = 0) {
110
+ if (typeof error?.partialHandoff === 'string') return error.partialHandoff;
86
111
  return partialHandoffTextFromSession(session, messageStartIndex);
87
112
  }
88
113
 
@@ -210,6 +210,16 @@ export function resolveProviderCacheKey(opts, provider) {
210
210
  );
211
211
  }
212
212
 
213
+ /** A cache identity as a prompt_cache_key: kept as is up to the 64-char wire
214
+ * limit, a bounded hash identity beyond it, '' when empty. */
215
+ export function normalizeCacheKey(value) {
216
+ const text = String(value ?? '').trim();
217
+ if (!text) return '';
218
+ const wellFormed = text.toWellFormed();
219
+ if (wellFormed.length <= 64) return wellFormed;
220
+ return `pc_${createHash('sha256').update(wellFormed).digest('hex').slice(0, 32)}`;
221
+ }
222
+
213
223
  function shortHash(value, chars = 18) {
214
224
  return createHash('sha256').update(stableHashStringify(value)).digest('hex').slice(0, chars);
215
225
  }
@@ -79,20 +79,41 @@ export function mcpUrlForLog(raw) {
79
79
 
80
80
  function replaceSecret(text, value) {
81
81
  const secret = String(value || '');
82
- return secret.length >= 4 ? text.split(secret).join('[redacted]') : text;
82
+ return secret ? text.split(secret).join('[redacted]') : text;
83
83
  }
84
84
 
85
- export function scrubMcpConnectionMessage(value, cfg = {}) {
86
- let text = String(value || '');
85
+ // Configured URL parts and header values shorter than 4 characters are left
86
+ // alone: redacting e.g. the `1` of `?v=1` would mangle every message. Known
87
+ // connection secrets (OAuth tokens) are redacted at any length.
88
+ function replaceConfiguredSecret(text, value) {
89
+ return String(value || '').length >= 4 ? replaceSecret(text, value) : text;
90
+ }
91
+
92
+ /** `value` with every one of `secrets` redacted from its strings, recursing
93
+ * into arrays and plain objects (tool results, JSON-RPC error data). */
94
+ export function scrubMcpSecrets(value, secrets) {
95
+ if (secrets.length === 0) return value;
96
+ if (typeof value === 'string') return secrets.reduce(replaceSecret, value);
97
+ if (Array.isArray(value)) return value.map((item) => scrubMcpSecrets(item, secrets));
98
+ const proto = value && typeof value === 'object' ? Object.getPrototypeOf(value) : undefined;
99
+ if (proto !== Object.prototype && proto !== null) return value;
100
+ return Object.fromEntries(
101
+ Object.entries(value).map(([key, item]) => [scrubMcpSecrets(key, secrets), scrubMcpSecrets(item, secrets)])
102
+ );
103
+ }
104
+
105
+ /** `secrets` are values known only to the live connection (its OAuth tokens). */
106
+ export function scrubMcpConnectionMessage(value, cfg = {}, secrets = []) {
107
+ let text = scrubMcpSecrets(String(value || ''), secrets);
87
108
  const rawUrl = expandEnvVars(String(cfg?.url || ''));
88
109
  if (rawUrl) {
89
110
  text = text.split(rawUrl).join(mcpUrlForLog(rawUrl));
90
111
  try {
91
112
  const parsed = new URL(rawUrl);
92
- text = replaceSecret(text, parsed.username);
93
- text = replaceSecret(text, parsed.password);
113
+ text = replaceConfiguredSecret(text, parsed.username);
114
+ text = replaceConfiguredSecret(text, parsed.password);
94
115
  for (const paramValue of parsed.searchParams.values()) {
95
- text = replaceSecret(text, paramValue);
116
+ text = replaceConfiguredSecret(text, paramValue);
96
117
  }
97
118
  } catch {
98
119
  /* invalid URL is reported without echoing it */
@@ -101,7 +122,7 @@ export function scrubMcpConnectionMessage(value, cfg = {}) {
101
122
  const headers = resolveMcpHttpHeaders(cfg);
102
123
  for (const [name, headerValue] of Object.entries(headers)) {
103
124
  if (/authorization|cookie|token|secret|api[-_]?key|signature/i.test(name)) {
104
- text = replaceSecret(text, headerValue);
125
+ text = replaceConfiguredSecret(text, headerValue);
105
126
  }
106
127
  }
107
128
  return text
@@ -286,6 +307,14 @@ export function resolveMcpTransportKind(cfg) {
286
307
  throw new Error(`Invalid config: need autoDetect, type (stdio/http/sse/ws), url (http), or command (stdio)`);
287
308
  }
288
309
 
310
+ /** The exact URL OAuth credentials of an HTTP/SSE server config are stored
311
+ * under; null for other transports. */
312
+ export function resolveMcpOAuthUrl(cfg) {
313
+ const kind = resolveMcpTransportKind(cfg);
314
+ if (kind !== 'http' && kind !== 'sse') return null;
315
+ return normalizeMcpTransportUrl(expandEnvVars(String(cfg.url ?? '')), kind);
316
+ }
317
+
289
318
  // MCP per-tool-call timeout. Default 2min: a hung/unresponsive MCP server
290
319
  // (e.g. a busy editor) must not stall a tool call indefinitely. Genuinely
291
320
  // long-running tools can raise/disable it via MIXDOG_MCP_CALL_TIMEOUT_MS or a
@@ -6,6 +6,7 @@
6
6
  import { markServiceUnreachable, isConnRefuseError } from '../../../shared/service-discovery.mjs';
7
7
  import { MCP_MAX_TIMER_MS, resolveMcpStartupTimeoutMs } from './client-config.mjs';
8
8
  import { appendMcpStderrTail } from './client-transport.mjs';
9
+ import { MCP_OAUTH_TIMEOUT_MS } from './oauth.mjs';
9
10
 
10
11
  // tools/list pagination bounds: a server that never stops handing out cursors
11
12
  // (or repeats one) cannot loop the client forever.
@@ -53,6 +54,15 @@ function startupTimeoutError(name, startupTimeoutMs) {
53
54
  return err;
54
55
  }
55
56
 
57
+ const oauthLoginError = () => new Error('MCP OAuth login failed, cancelled, or timed out');
58
+
59
+ /** Code of a connect cancelled (login cancel, disconnect, shutdown) before any
60
+ * interactive login started. */
61
+ export const MCP_CONNECT_CANCELLED = 'EMCPCONNECTCANCELLED';
62
+ function connectCancelledError(name) {
63
+ return Object.assign(new Error(`MCP server "${name}" connect cancelled`), { code: MCP_CONNECT_CANCELLED });
64
+ }
65
+
56
66
  /**
57
67
  * Bound the connect + listTools handshake so a slow/hung server can't stall
58
68
  * boot or the first turn. On expiry the pending transport/child is torn down
@@ -73,17 +83,43 @@ export async function runBoundedHandshake({
73
83
  }) {
74
84
  const startupTimeoutMs = resolveMcpStartupTimeoutMs(cfg);
75
85
  const guard = { timer: null, timedOut: false };
86
+ let rejectDeadline;
87
+ // True once an interactive login actually started (the server challenged
88
+ // and a browser sign-in was allowed).
89
+ let loginStarted = false;
90
+ const armDeadline = (ms, error = () => startupTimeoutError(name, ms)) => {
91
+ if (guard.timer) clearTimeout(guard.timer);
92
+ guard.timer = setTimeout(() => {
93
+ guard.timedOut = true;
94
+ rejectDeadline(error());
95
+ }, ms);
96
+ };
97
+ const deadline = new Promise((_, reject) => {
98
+ rejectDeadline = reject;
99
+ });
100
+ // A closed OAuth connection (login cancel, disconnect, shutdown) settles a
101
+ // handshake that would otherwise wait forever (an SSE stream closed while
102
+ // starting never settles). The SDK also closes the transport — and with it
103
+ // the OAuth connection — right before rethrowing a connect failure of its
104
+ // own (e.g. connection refused): deferring past the pending microtasks lets
105
+ // that real failure settle the race first.
106
+ const cancelOAuth = () =>
107
+ setImmediate(() => rejectDeadline(loginStarted ? oauthLoginError() : connectCancelledError(name)));
108
+ transport.oauth?.signal.addEventListener('abort', cancelOAuth, { once: true });
109
+ if (transport.oauth?.signal.aborted) cancelOAuth();
110
+ if (startupTimeoutMs > 0) armDeadline(startupTimeoutMs);
111
+ // Human authorization gets a separate, finite budget. The ordinary startup
112
+ // budget still applies until the server actually challenges authentication.
113
+ if (transport.oauth) {
114
+ transport.oauth.onStart = () => {
115
+ loginStarted = true;
116
+ armDeadline(MCP_OAUTH_TIMEOUT_MS, oauthLoginError);
117
+ };
118
+ }
76
119
  const handshake = performHandshake(client, transport, {
77
- timeout: startupTimeoutMs > 0 ? startupTimeoutMs : MCP_MAX_TIMER_MS,
120
+ timeout: !transport.oauth && startupTimeoutMs > 0 ? startupTimeoutMs : MCP_MAX_TIMER_MS,
78
121
  });
79
122
  try {
80
- if (startupTimeoutMs <= 0) return await handshake;
81
- const deadline = new Promise((_, rej) => {
82
- guard.timer = setTimeout(() => {
83
- guard.timedOut = true;
84
- rej(startupTimeoutError(name, startupTimeoutMs));
85
- }, startupTimeoutMs);
86
- });
87
123
  return await Promise.race([handshake, deadline]);
88
124
  } catch (err) {
89
125
  // A discovery-advert port that fails to connect is a corpse (recycled
@@ -92,7 +128,7 @@ export async function runBoundedHandshake({
92
128
  // ONLY — a startup/handshake timeout is a slow-but-alive server.
93
129
  if (autoDetectAdvert && isConnRefuseError(err))
94
130
  markServiceUnreachable(autoDetectAdvert.service, autoDetectAdvert.port);
95
- if (guard.timedOut) {
131
+ if (guard.timedOut || transport.oauth?.signal.aborted) {
96
132
  // Tear down the pending transport/child so a hung handshake never
97
133
  // leaks a stdio process or socket — fire-and-forget (like the
98
134
  // tool-call timeout path) so a slow tree-kill never delays this
@@ -100,7 +136,7 @@ export async function runBoundedHandshake({
100
136
  try {
101
137
  closeServer({ client, transport }).catch(() => {});
102
138
  } catch {
103
- /* ignore */
139
+ // Teardown is best effort: the handshake failure below is what gets reported.
104
140
  }
105
141
  // Never let the late handshake settle into an unhandled rejection.
106
142
  handshake.catch(() => {});
@@ -112,6 +148,9 @@ export async function runBoundedHandshake({
112
148
  }
113
149
  throw tail ? new Error(appendMcpStderrTail(String(err), tail)) : err;
114
150
  } finally {
151
+ transport.oauth?.finishConnect();
115
152
  if (guard.timer) clearTimeout(guard.timer);
153
+ transport.oauth?.signal.removeEventListener('abort', cancelOAuth);
154
+ if (transport.oauth) transport.oauth.onStart = () => {};
116
155
  }
117
156
  }
@@ -4,6 +4,7 @@
4
4
  * WebSocket, or a stdio child.
5
5
  */
6
6
  import { readServicePort } from '../../../shared/service-discovery.mjs';
7
+ import { createMcpOAuth } from './oauth.mjs';
7
8
  import {
8
9
  AUTO_DETECT_PORTS,
9
10
  expandEnvVars,
@@ -11,6 +12,7 @@ import {
11
12
  mcpUrlForLog,
12
13
  normalizeMcpTransportUrl,
13
14
  resolveMcpHttpHeaders,
15
+ resolveMcpOAuthUrl,
14
16
  resolveMcpStdioEnvironment,
15
17
  resolveMcpTransportKind,
16
18
  scrubMcpConnectionMessage,
@@ -49,6 +51,19 @@ function requestInitFor(cfg) {
49
51
  return headers && Object.keys(headers).length > 0 ? { requestInit: { headers } } : undefined;
50
52
  }
51
53
 
54
+ // Configured authentication stays authoritative even when its variables are
55
+ // unset or resolve empty: the server then fails as configured instead of
56
+ // turning into an OAuth login.
57
+ export function hasConfiguredMcpAuth(cfg) {
58
+ const configured = (value) =>
59
+ value && typeof value === 'object' && !Array.isArray(value) && Object.keys(value).length > 0;
60
+ return (
61
+ Boolean(String(cfg.bearer_token_env_var || '').trim()) ||
62
+ configured(cfg.headers) ||
63
+ configured(cfg.env_http_headers)
64
+ );
65
+ }
66
+
52
67
  // The SDK's WebSocket transport opens its socket without a way to set request
53
68
  // headers. This subclass changes only socket creation: Node's WebSocket takes
54
69
  // `{ protocols, headers }`; the guard and event wiring match the stock start().
@@ -88,7 +103,7 @@ function createHeaderWebSocketTransport(Base, messageSchema, url, headers) {
88
103
  * transports. `strictEnv` (the caller passes it for plugin-declared servers)
89
104
  * makes unset variables without a default fail the server; otherwise they
90
105
  * expand to empty and are reported in `warnings` (names only). */
91
- export function createMcpTransport({ name, cfg, sdk, log, strictEnv }) {
106
+ export function createMcpTransport({ name, cfg, sdk, log, strictEnv, oauthOptions }) {
92
107
  const { StdioClientTransport, StreamableHTTPClientTransport, SSEClientTransport, WebSocketClientTransport } = sdk;
93
108
  const kind = resolveMcpTransportKind(cfg);
94
109
  const warnings = [];
@@ -113,11 +128,33 @@ export function createMcpTransport({ name, cfg, sdk, log, strictEnv }) {
113
128
  }
114
129
  if (kind === 'http' || kind === 'sse') {
115
130
  const Transport = kind === 'http' ? StreamableHTTPClientTransport : SSEClientTransport;
116
- const url = normalizeMcpTransportUrl(expandEnvVars(String(cfg.url ?? '')), kind);
131
+ const url = resolveMcpOAuthUrl(cfg);
117
132
  const opts = requestInitFor(cfg);
133
+ // Explicit authentication remains authoritative (including custom API-key
134
+ // headers). Never forward those credentials to OAuth discovery endpoints.
135
+ let transport;
136
+ const oauth = hasConfiguredMcpAuth(cfg)
137
+ ? null
138
+ : createMcpOAuth({
139
+ url,
140
+ serverName: name,
141
+ ...oauthOptions,
142
+ // SSE advertises its POST endpoint on the authenticated event stream.
143
+ // No other path on the origin is allowed to receive this server's token.
144
+ isTransportEndpoint: (input) => kind === 'sse' && transport?._endpoint?.href === new URL(input).href,
145
+ });
146
+ transport = new Transport(new URL(url), oauth ? { fetch: oauth.fetch } : opts);
147
+ if (oauth) {
148
+ transport.oauth = oauth;
149
+ const close = transport.close.bind(transport);
150
+ transport.close = async () => {
151
+ oauth.close();
152
+ await close();
153
+ };
154
+ }
118
155
  log(`[mcp-client] Connecting "${name}" via ${kind.toUpperCase()}: ${mcpUrlForLog(url)}\n`);
119
156
  return {
120
- transport: opts ? new Transport(new URL(url), opts) : new Transport(new URL(url)),
157
+ transport,
121
158
  autoDetectAdvert: null,
122
159
  stderrTail: noStderrTail,
123
160
  warnings,