mixdog 1.2.1 → 1.2.3

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 (557) hide show
  1. package/README.md +18 -0
  2. package/package.json +2 -1
  3. package/scripts/gate-local.mjs +19 -3
  4. package/scripts/model-catalog-audit.mjs +1 -15
  5. package/scripts/provider-pingpong-live.mjs +509 -0
  6. package/src/agents/advisor/AGENT.md +1 -1
  7. package/src/agents/advisor/agent.json +1 -1
  8. package/src/agents/reviewer/AGENT.md +3 -3
  9. package/src/agents/reviewer/agent.json +1 -1
  10. package/src/defaults/shipped-history.json +6 -0
  11. package/src/defaults/skills/setup/SKILL.md +6 -1
  12. package/src/defaults/skills/setup/references/actions.md +44 -22
  13. package/src/defaults/skills/setup/references/surfaces.md +10 -8
  14. package/src/headless-exec.mjs +15 -8
  15. package/src/lib/keychain-cjs.cjs +7 -4
  16. package/src/output-styles/common.md +11 -10
  17. package/src/repl/slash-commands.mjs +16 -1
  18. package/src/rules/agent/AGENT.md +5 -5
  19. package/src/rules/lead/LEAD.md +5 -5
  20. package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +29 -4
  21. package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +10 -0
  22. package/src/runtime/agent/orchestrator/agent-trace-io.mjs +92 -61
  23. package/src/runtime/agent/orchestrator/cache-mtime.mjs +10 -1
  24. package/src/runtime/agent/orchestrator/config-providers-merge.mjs +7 -0
  25. package/src/runtime/agent/orchestrator/config.mjs +5 -0
  26. package/src/runtime/agent/orchestrator/context/collect.mjs +1 -0
  27. package/src/runtime/agent/orchestrator/context/deferred-tools.mjs +26 -7
  28. package/src/runtime/agent/orchestrator/context/skill-catalog.mjs +112 -7
  29. package/src/runtime/agent/orchestrator/mcp/client-config.mjs +36 -7
  30. package/src/runtime/agent/orchestrator/mcp/client-handshake.mjs +49 -10
  31. package/src/runtime/agent/orchestrator/mcp/client-transport.mjs +40 -3
  32. package/src/runtime/agent/orchestrator/mcp/client.mjs +230 -96
  33. package/src/runtime/agent/orchestrator/mcp/oauth-callback.mjs +103 -0
  34. package/src/runtime/agent/orchestrator/mcp/oauth-credentials.mjs +150 -0
  35. package/src/runtime/agent/orchestrator/mcp/oauth-login.mjs +111 -0
  36. package/src/runtime/agent/orchestrator/mcp/oauth-probe.mjs +87 -0
  37. package/src/runtime/agent/orchestrator/mcp/oauth-provider.mjs +95 -0
  38. package/src/runtime/agent/orchestrator/mcp/oauth-request.mjs +122 -0
  39. package/src/runtime/agent/orchestrator/mcp/oauth-shared.mjs +9 -0
  40. package/src/runtime/agent/orchestrator/mcp/oauth.mjs +229 -0
  41. package/src/runtime/agent/orchestrator/providers/account-pool.mjs +188 -29
  42. package/src/runtime/agent/orchestrator/providers/anthropic-api-recovery.mjs +2 -1
  43. package/src/runtime/agent/orchestrator/providers/anthropic-api-request.mjs +48 -20
  44. package/src/runtime/agent/orchestrator/providers/anthropic-api-transport.mjs +94 -31
  45. package/src/runtime/agent/orchestrator/providers/anthropic-error-contract.mjs +98 -0
  46. package/src/runtime/agent/orchestrator/providers/anthropic-fast-mode.mjs +39 -18
  47. package/src/runtime/agent/orchestrator/providers/anthropic-max-tokens-recovery.mjs +55 -0
  48. package/src/runtime/agent/orchestrator/providers/anthropic-max-tokens.mjs +48 -37
  49. package/src/runtime/agent/orchestrator/providers/anthropic-messages.mjs +10 -7
  50. package/src/runtime/agent/orchestrator/providers/anthropic-midstream-recovery.mjs +18 -4
  51. package/src/runtime/agent/orchestrator/providers/anthropic-model-resolve.mjs +13 -1
  52. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-client-version.mjs +1 -1
  53. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-midstream.mjs +10 -1
  54. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-recovery.mjs +23 -9
  55. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-request/initial-status.mjs +46 -20
  56. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-request.mjs +14 -9
  57. package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +102 -45
  58. package/src/runtime/agent/orchestrator/providers/anthropic-overload-fallback.mjs +69 -0
  59. package/src/runtime/agent/orchestrator/providers/anthropic-server-fallback.mjs +4 -0
  60. package/src/runtime/agent/orchestrator/providers/anthropic-sse-turn/turn-blocks.mjs +20 -1
  61. package/src/runtime/agent/orchestrator/providers/anthropic-sse-turn/turn-events/content-block-delta.mjs +15 -2
  62. package/src/runtime/agent/orchestrator/providers/anthropic-sse-turn/turn-events/content-block-start.mjs +11 -4
  63. package/src/runtime/agent/orchestrator/providers/anthropic-sse-turn/turn-events/message-events.mjs +3 -0
  64. package/src/runtime/agent/orchestrator/providers/anthropic-sse-turn/turn-events.mjs +12 -5
  65. package/src/runtime/agent/orchestrator/providers/anthropic-sse-turn/turn-outcome.mjs +1 -0
  66. package/src/runtime/agent/orchestrator/providers/anthropic-sse-turn/turn-state.mjs +14 -0
  67. package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +10 -1
  68. package/src/runtime/agent/orchestrator/providers/anthropic-turn-result.mjs +9 -0
  69. package/src/runtime/agent/orchestrator/providers/anthropic.mjs +40 -7
  70. package/src/runtime/agent/orchestrator/providers/antigravity-oauth-catalog.mjs +30 -9
  71. package/src/runtime/agent/orchestrator/providers/antigravity-oauth.mjs +7 -0
  72. package/src/runtime/agent/orchestrator/providers/antigravity-request.mjs +88 -21
  73. package/src/runtime/agent/orchestrator/providers/antigravity-response.mjs +30 -10
  74. package/src/runtime/agent/orchestrator/providers/antigravity-retry-policy.mjs +106 -0
  75. package/src/runtime/agent/orchestrator/providers/antigravity-stream.mjs +10 -61
  76. package/src/runtime/agent/orchestrator/providers/antigravity-transport.mjs +94 -15
  77. package/src/runtime/agent/orchestrator/providers/codex-client-meta.mjs +1 -1
  78. package/src/runtime/agent/orchestrator/providers/command-code-oauth.mjs +117 -72
  79. package/src/runtime/agent/orchestrator/providers/compat-request-policy.mjs +78 -2
  80. package/src/runtime/agent/orchestrator/providers/cursor-auth.mjs +75 -10
  81. package/src/runtime/agent/orchestrator/providers/cursor-client-version.mjs +1 -1
  82. package/src/runtime/agent/orchestrator/providers/cursor-wire-guards.mjs +97 -2
  83. package/src/runtime/agent/orchestrator/providers/cursor-wire-stream-bridge.mjs +103 -23
  84. package/src/runtime/agent/orchestrator/providers/cursor-wire-stream-messages.mjs +43 -2
  85. package/src/runtime/agent/orchestrator/providers/cursor-wire-transport.mjs +103 -33
  86. package/src/runtime/agent/orchestrator/providers/cursor-wire.mjs +32 -6
  87. package/src/runtime/agent/orchestrator/providers/cursor.mjs +27 -3
  88. package/src/runtime/agent/orchestrator/providers/custom-provider-config.mjs +64 -1
  89. package/src/runtime/agent/orchestrator/providers/custom-provider.mjs +70 -6
  90. package/src/runtime/agent/orchestrator/providers/custom-reasoning.mjs +34 -7
  91. package/src/runtime/agent/orchestrator/providers/custom-tool-wire.mjs +32 -6
  92. package/src/runtime/agent/orchestrator/providers/deepseek.mjs +67 -0
  93. package/src/runtime/agent/orchestrator/providers/effort-configuration.mjs +80 -18
  94. package/src/runtime/agent/orchestrator/providers/gemini-cache-policy.mjs +11 -4
  95. package/src/runtime/agent/orchestrator/providers/gemini-cache.mjs +46 -8
  96. package/src/runtime/agent/orchestrator/providers/gemini-error-contract.mjs +273 -0
  97. package/src/runtime/agent/orchestrator/providers/gemini-family.mjs +9 -0
  98. package/src/runtime/agent/orchestrator/providers/gemini-history-signatures.mjs +9 -4
  99. package/src/runtime/agent/orchestrator/providers/gemini-request-body.mjs +41 -5
  100. package/src/runtime/agent/orchestrator/providers/gemini-response.mjs +3 -1
  101. package/src/runtime/agent/orchestrator/providers/gemini-retry-policy.mjs +180 -0
  102. package/src/runtime/agent/orchestrator/providers/gemini-schema.mjs +369 -112
  103. package/src/runtime/agent/orchestrator/providers/gemini-sdk-request.mjs +3 -1
  104. package/src/runtime/agent/orchestrator/providers/gemini-stream.mjs +140 -35
  105. package/src/runtime/agent/orchestrator/providers/gemini-thinking.mjs +70 -24
  106. package/src/runtime/agent/orchestrator/providers/gemini.mjs +80 -29
  107. package/src/runtime/agent/orchestrator/providers/grok-client-version.mjs +1 -1
  108. package/src/runtime/agent/orchestrator/providers/grok-oauth-tokens.mjs +3 -0
  109. package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +69 -19
  110. package/src/runtime/agent/orchestrator/providers/lib/action-input-contract.mjs +60 -0
  111. package/src/runtime/agent/orchestrator/providers/lib/anthropic-native-blocks.mjs +1 -0
  112. package/src/runtime/agent/orchestrator/providers/lib/anthropic-request-utils.mjs +246 -42
  113. package/src/runtime/agent/orchestrator/providers/lib/anthropic-server-tool-usage.mjs +18 -0
  114. package/src/runtime/agent/orchestrator/providers/lib/anthropic-system-blocks.mjs +27 -6
  115. package/src/runtime/agent/orchestrator/providers/lib/claude-bridge-schema.mjs +388 -0
  116. package/src/runtime/agent/orchestrator/providers/lib/inclusive-usage.mjs +54 -0
  117. package/src/runtime/agent/orchestrator/providers/lib/origin-bound-fetch.mjs +16 -0
  118. package/src/runtime/agent/orchestrator/providers/lib/responses-terminal-fields.mjs +11 -0
  119. package/src/runtime/agent/orchestrator/providers/lib/usage-primitives.mjs +8 -0
  120. package/src/runtime/agent/orchestrator/providers/lib/wire-pairing.mjs +48 -22
  121. package/src/runtime/agent/orchestrator/providers/media-normalization.mjs +119 -18
  122. package/src/runtime/agent/orchestrator/providers/mimo-oauth-profile.mjs +13 -15
  123. package/src/runtime/agent/orchestrator/providers/mimo-oauth.mjs +3 -0
  124. package/src/runtime/agent/orchestrator/providers/model-list-sanitize.mjs +2 -1
  125. package/src/runtime/agent/orchestrator/providers/model-proxy-channel.d.mts +1 -1
  126. package/src/runtime/agent/orchestrator/providers/model-proxy-channel.mjs +1 -0
  127. package/src/runtime/agent/orchestrator/providers/model-proxy-provider.mjs +33 -1
  128. package/src/runtime/agent/orchestrator/providers/muse-code-oauth.mjs +4 -9
  129. package/src/runtime/agent/orchestrator/providers/oauth-credential-probes.mjs +2 -0
  130. package/src/runtime/agent/orchestrator/providers/openai-codex-metadata.mjs +2 -0
  131. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-send.mjs +123 -61
  132. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream/chunk-events.mjs +35 -14
  133. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream/stream-result.mjs +27 -2
  134. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream/stream-state.mjs +37 -7
  135. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream/tool-call-acc.mjs +2 -9
  136. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream.mjs +1 -0
  137. package/src/runtime/agent/orchestrator/providers/openai-compat-error-contract.mjs +50 -0
  138. package/src/runtime/agent/orchestrator/providers/openai-compat-models.mjs +47 -2
  139. package/src/runtime/agent/orchestrator/providers/openai-compat-options.mjs +47 -25
  140. package/src/runtime/agent/orchestrator/providers/openai-compat-presets.mjs +5 -1
  141. package/src/runtime/agent/orchestrator/providers/openai-compat-reasoning-field.mjs +151 -0
  142. package/src/runtime/agent/orchestrator/providers/openai-compat-response-normalization.mjs +64 -23
  143. package/src/runtime/agent/orchestrator/providers/openai-compat-responses-events.mjs +79 -33
  144. package/src/runtime/agent/orchestrator/providers/openai-compat-responses-head-parity.json +2901 -0
  145. package/src/runtime/agent/orchestrator/providers/openai-compat-responses-parity.fixtures.mjs +172 -0
  146. package/src/runtime/agent/orchestrator/providers/openai-compat-responses-state.mjs +3 -2
  147. package/src/runtime/agent/orchestrator/providers/openai-compat-responses.mjs +144 -15
  148. package/src/runtime/agent/orchestrator/providers/openai-compat-retry-profile.mjs +193 -0
  149. package/src/runtime/agent/orchestrator/providers/openai-compat-stream-common.mjs +66 -2
  150. package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +9 -2
  151. package/src/runtime/agent/orchestrator/providers/openai-compat-trace.mjs +2 -0
  152. package/src/runtime/agent/orchestrator/providers/openai-compat-wire.mjs +109 -26
  153. package/src/runtime/agent/orchestrator/providers/openai-compat-xai-send.mjs +27 -6
  154. package/src/runtime/agent/orchestrator/providers/openai-compat-xai.mjs +34 -20
  155. package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +23 -4
  156. package/src/runtime/agent/orchestrator/providers/openai-direct-request.mjs +37 -28
  157. package/src/runtime/agent/orchestrator/providers/openai-error-contract.mjs +274 -0
  158. package/src/runtime/agent/orchestrator/providers/openai-http-sse-request.mjs +72 -32
  159. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-events/output-items.mjs +23 -10
  160. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-events/terminal-frames.mjs +43 -15
  161. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-events.mjs +10 -0
  162. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-items.mjs +18 -7
  163. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-outcome.mjs +5 -2
  164. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-stamps.mjs +2 -2
  165. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-state.mjs +7 -3
  166. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/text-relay.mjs +9 -1
  167. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state.mjs +2 -2
  168. package/src/runtime/agent/orchestrator/providers/openai-http-sse-watchdogs.mjs +1 -3
  169. package/src/runtime/agent/orchestrator/providers/openai-model-capabilities.mjs +80 -0
  170. package/src/runtime/agent/orchestrator/providers/openai-oauth-dispatch.mjs +37 -4
  171. package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +12 -1
  172. package/src/runtime/agent/orchestrator/providers/openai-oauth-tokens.mjs +22 -2
  173. package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +12 -1
  174. package/src/runtime/agent/orchestrator/providers/openai-responses-input.mjs +76 -33
  175. package/src/runtime/agent/orchestrator/providers/openai-responses-payload-parity.fixture.json +323 -0
  176. package/src/runtime/agent/orchestrator/providers/openai-responses-payload.mjs +44 -16
  177. package/src/runtime/agent/orchestrator/providers/openai-retry-policy.mjs +9 -0
  178. package/src/runtime/agent/orchestrator/providers/openai-ws-delta.mjs +7 -16
  179. package/src/runtime/agent/orchestrator/providers/openai-ws-handshake.mjs +16 -8
  180. package/src/runtime/agent/orchestrator/providers/openai-ws-response-state/refusals.mjs +60 -0
  181. package/src/runtime/agent/orchestrator/providers/openai-ws-response-state/tool-calls.mjs +51 -35
  182. package/src/runtime/agent/orchestrator/providers/openai-ws-response-state.mjs +30 -0
  183. package/src/runtime/agent/orchestrator/providers/openai-ws-send-attempts/attempt-context.mjs +1 -2
  184. package/src/runtime/agent/orchestrator/providers/openai-ws-send-attempts/policy.mjs +25 -14
  185. package/src/runtime/agent/orchestrator/providers/openai-ws-stream/event-switch.mjs +12 -0
  186. package/src/runtime/agent/orchestrator/providers/openai-ws-stream/terminal-frames.mjs +25 -6
  187. package/src/runtime/agent/orchestrator/providers/openai-ws-terminal.mjs +18 -11
  188. package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +41 -22
  189. package/src/runtime/agent/orchestrator/providers/opencode-go-gemini.mjs +170 -0
  190. package/src/runtime/agent/orchestrator/providers/opencode-go.mjs +75 -70
  191. package/src/runtime/agent/orchestrator/providers/openrouter.mjs +75 -0
  192. package/src/runtime/agent/orchestrator/providers/provider-error-kind.mjs +720 -0
  193. package/src/runtime/agent/orchestrator/providers/registry.mjs +4 -1
  194. package/src/runtime/agent/orchestrator/providers/retry-backoff.mjs +46 -7
  195. package/src/runtime/agent/orchestrator/providers/retry-classification.mjs +123 -219
  196. package/src/runtime/agent/orchestrator/providers/retry-classifier.mjs +2 -0
  197. package/src/runtime/agent/orchestrator/providers/retry-executor.mjs +148 -45
  198. package/src/runtime/agent/orchestrator/providers/retry-midstream.mjs +81 -16
  199. package/src/runtime/agent/orchestrator/providers/transport-scope.d.mts +6 -0
  200. package/src/runtime/agent/orchestrator/providers/transport-scope.mjs +28 -0
  201. package/src/runtime/agent/orchestrator/providers/wire-gateway.mjs +157 -0
  202. package/src/runtime/agent/orchestrator/providers/wire-route.mjs +512 -0
  203. package/src/runtime/agent/orchestrator/providers/xai-tool-search-head-parity.json +1131 -0
  204. package/src/runtime/agent/orchestrator/providers/xai-tool-search.fixtures.mjs +183 -0
  205. package/src/runtime/agent/orchestrator/runtime-core/ask-user-tool-defs.mjs +100 -0
  206. package/src/runtime/agent/orchestrator/runtime-core/config-helpers.mjs +3 -0
  207. package/src/runtime/agent/orchestrator/runtime-core/deferred-tool-availability.mjs +21 -0
  208. package/src/runtime/agent/orchestrator/runtime-core/effort.mjs +15 -0
  209. package/src/runtime/agent/orchestrator/runtime-core/goal-reminder.mjs +0 -18
  210. package/src/runtime/agent/orchestrator/runtime-core/model-capabilities.mjs +57 -28
  211. package/src/runtime/agent/orchestrator/runtime-core/orchestration.mjs +19 -16
  212. package/src/runtime/agent/orchestrator/runtime-core/plugin-mcp.mjs +1 -97
  213. package/src/runtime/agent/orchestrator/runtime-core/provider-request-snapshot.mjs +7 -3
  214. package/src/runtime/agent/orchestrator/runtime-core/session-title.mjs +21 -1
  215. package/src/runtime/agent/orchestrator/runtime-core/skill-tool-loading.mjs +14 -3
  216. package/src/runtime/agent/orchestrator/runtime-core/tool-bridge.mjs +526 -0
  217. package/src/runtime/agent/orchestrator/runtime-core/tool-catalog-data.mjs +3 -0
  218. package/src/runtime/agent/orchestrator/runtime-core/tool-catalog-schema.mjs +10 -7
  219. package/src/runtime/agent/orchestrator/runtime-core/tool-catalog.mjs +176 -71
  220. package/src/runtime/agent/orchestrator/runtime-core/tool-defs.mjs +62 -10
  221. package/src/runtime/agent/orchestrator/runtime-core/tool-exposure.mjs +96 -0
  222. package/src/runtime/agent/orchestrator/session/catalog-db/schema.mjs +5 -1
  223. package/src/runtime/agent/orchestrator/session/compact/constants.mjs +3 -2
  224. package/src/runtime/agent/orchestrator/session/compact/messages.mjs +14 -93
  225. package/src/runtime/agent/orchestrator/session/compact/summary-anchor.mjs +5 -0
  226. package/src/runtime/agent/orchestrator/session/compact/user-message-kind.mjs +120 -0
  227. package/src/runtime/agent/orchestrator/session/context-tool-schema.mjs +17 -2
  228. package/src/runtime/agent/orchestrator/session/context-utils.mjs +8 -4
  229. package/src/runtime/agent/orchestrator/session/eager-dispatch/admission.mjs +2 -1
  230. package/src/runtime/agent/orchestrator/session/eager-dispatch.mjs +6 -1
  231. package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +91 -1
  232. package/src/runtime/agent/orchestrator/session/loop/completion-guards.mjs +1 -1
  233. package/src/runtime/agent/orchestrator/session/loop/context-overflow.mjs +56 -0
  234. package/src/runtime/agent/orchestrator/session/loop/deferred-call-through.mjs +18 -8
  235. package/src/runtime/agent/orchestrator/session/loop/loop-state.mjs +1 -0
  236. package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +21 -10
  237. package/src/runtime/agent/orchestrator/session/loop/provider-tool-exclusions.mjs +132 -0
  238. package/src/runtime/agent/orchestrator/session/loop/request-boundary.mjs +30 -0
  239. package/src/runtime/agent/orchestrator/session/loop/send-phase.mjs +67 -20
  240. package/src/runtime/agent/orchestrator/session/loop/step-auto-effort.mjs +104 -19
  241. package/src/runtime/agent/orchestrator/session/loop/tool-call-resolution.mjs +95 -0
  242. package/src/runtime/agent/orchestrator/session/loop/tool-exec/before-hook.mjs +30 -3
  243. package/src/runtime/agent/orchestrator/session/loop/tool-exec/dispatch.mjs +15 -4
  244. package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +15 -1
  245. package/src/runtime/agent/orchestrator/session/loop/tool-exposure-migration.mjs +150 -0
  246. package/src/runtime/agent/orchestrator/session/loop/tool-phase.mjs +7 -2
  247. package/src/runtime/agent/orchestrator/session/manager/ask-queue.mjs +30 -2
  248. package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +11 -2
  249. package/src/runtime/agent/orchestrator/session/manager/ask-turn-auto-effort.mjs +2 -1
  250. package/src/runtime/agent/orchestrator/session/manager/ask-turn-commit.mjs +34 -12
  251. package/src/runtime/agent/orchestrator/session/manager/ask-turn-execute.mjs +57 -21
  252. package/src/runtime/agent/orchestrator/session/manager/ask-turn-loop-options.mjs +57 -25
  253. package/src/runtime/agent/orchestrator/session/manager/ask-turn-prepare.mjs +6 -0
  254. package/src/runtime/agent/orchestrator/session/manager/ask-turn-settle.mjs +4 -0
  255. package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +8 -0
  256. package/src/runtime/agent/orchestrator/session/manager/pending-message-entry.mjs +5 -10
  257. package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +208 -14
  258. package/src/runtime/agent/orchestrator/session/manager/session-close.mjs +16 -4
  259. package/src/runtime/agent/orchestrator/session/manager/session-crud.mjs +25 -3
  260. package/src/runtime/agent/orchestrator/session/manager/session-fallback-title.mjs +82 -0
  261. package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +19 -2
  262. package/src/runtime/agent/orchestrator/session/manager/session-record.mjs +3 -0
  263. package/src/runtime/agent/orchestrator/session/manager/tool-resolution.mjs +4 -1
  264. package/src/runtime/agent/orchestrator/session/manager/turn-interruption.mjs +18 -7
  265. package/src/runtime/agent/orchestrator/session/manager.mjs +2 -1
  266. package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +6 -0
  267. package/src/runtime/agent/orchestrator/session/send-with-recovery.mjs +168 -29
  268. package/src/runtime/agent/orchestrator/session/side-question-store.mjs +96 -0
  269. package/src/runtime/agent/orchestrator/session/side-question.mjs +192 -0
  270. package/src/runtime/agent/orchestrator/session/store/lifecycle-barriers.mjs +17 -3
  271. package/src/runtime/agent/orchestrator/session/store/live-state.mjs +23 -0
  272. package/src/runtime/agent/orchestrator/session/store/paths-heartbeat.mjs +58 -2
  273. package/src/runtime/agent/orchestrator/session/store/save-worker.mjs +6 -3
  274. package/src/runtime/agent/orchestrator/session/store/session-delete.mjs +41 -28
  275. package/src/runtime/agent/orchestrator/session/store/session-observers.mjs +4 -2
  276. package/src/runtime/agent/orchestrator/session/store/sweep/sweep-finish.mjs +12 -1
  277. package/src/runtime/agent/orchestrator/session/store/write-admission.mjs +3 -1
  278. package/src/runtime/agent/orchestrator/session/store/write-guards.mjs +16 -0
  279. package/src/runtime/agent/orchestrator/session/store-agent-worker-rows.mjs +22 -10
  280. package/src/runtime/agent/orchestrator/session/store-summary-archived-agent.mjs +34 -15
  281. package/src/runtime/agent/orchestrator/session/store-summary-catalog.mjs +6 -0
  282. package/src/runtime/agent/orchestrator/session/store-summary-fields.mjs +13 -0
  283. package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +13 -1
  284. package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +10 -2
  285. package/src/runtime/agent/orchestrator/session/store.mjs +19 -3
  286. package/src/runtime/agent/orchestrator/session/tool-batch/execute-call.mjs +1 -0
  287. package/src/runtime/agent/orchestrator/session/tool-batch/finalize.mjs +1 -1
  288. package/src/runtime/agent/orchestrator/session/tool-batch/record-outcome.mjs +1 -1
  289. package/src/runtime/agent/orchestrator/session/tool-batch/state.mjs +7 -1
  290. package/src/runtime/agent/orchestrator/session/tool-result-offload.mjs +4 -0
  291. package/src/runtime/agent/orchestrator/session/tool-schema-rejection.mjs +77 -0
  292. package/src/runtime/agent/orchestrator/session/transcript-restore/restore.mjs +20 -1
  293. package/src/runtime/agent/orchestrator/tools/bash-policy-scan.mjs +8 -2
  294. package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +2 -2
  295. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/command-preflight.mjs +3 -3
  296. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/deadline-plan.mjs +12 -15
  297. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +4 -1
  298. package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +5 -5
  299. package/src/runtime/agent/orchestrator/tools/builtin/external-tool-adapters.mjs +14 -10
  300. package/src/runtime/agent/orchestrator/tools/builtin/lib/shell-job-records.mjs +49 -34
  301. package/src/runtime/agent/orchestrator/tools/builtin/runtime-capabilities.mjs +23 -1
  302. package/src/runtime/agent/orchestrator/tools/code-graph/dispatch/root-resolution.mjs +1 -1
  303. package/src/runtime/agent/orchestrator/tools/code-graph/search.mjs +1 -1
  304. package/src/runtime/agent/orchestrator/tools/patch/ui-diff.mjs +73 -39
  305. package/src/runtime/agent/orchestrator/tools/progress-message.mjs +18 -1
  306. package/src/runtime/agent/orchestrator/tools/shell-command.mjs +7 -4
  307. package/src/runtime/agent/orchestrator/tools/shell-exec-policy.mjs +88 -12
  308. package/src/runtime/agent/orchestrator/tools/shell-policy.mjs +2 -0
  309. package/src/runtime/computer-bridge/action-schema.mjs +44 -21
  310. package/src/runtime/github/tool.mjs +92 -2
  311. package/src/runtime/memory/index.mjs +1 -0
  312. package/src/runtime/memory/lib/http-router/ingest-routes.mjs +22 -1
  313. package/src/runtime/memory/lib/ingest/transcript-offsets.mjs +2 -0
  314. package/src/runtime/memory/lib/pg/adapter.mjs +98 -1
  315. package/src/runtime/memory/lib/pg/process.mjs +65 -50
  316. package/src/runtime/memory/lib/pg/run-child.mjs +49 -0
  317. package/src/runtime/memory/lib/pg/supervisor.mjs +124 -16
  318. package/src/runtime/memory/lib/runtime-fetcher.mjs +12 -13
  319. package/src/runtime/memory/lib/transcript-ingest.mjs +14 -0
  320. package/src/runtime/office/README.md +2 -2
  321. package/src/runtime/office/authoring/html-document-author.mjs +2 -0
  322. package/src/runtime/office/authoring/pptx-contact-sheet.mjs +2 -0
  323. package/src/runtime/office/authoring/pptx-html-measure.mjs +2 -0
  324. package/src/runtime/office/authoring/pptx-html-runner.mjs +2 -0
  325. package/src/runtime/office/authoring/xlsx-html-runner.mjs +2 -0
  326. package/src/runtime/office/core/office-actions-read.mjs +3 -0
  327. package/src/runtime/office/core/office-render-preview.mjs +2 -0
  328. package/src/runtime/office/core/office-session-dispatch.mjs +7 -0
  329. package/src/runtime/office/core/office-sessionless-actions.mjs +8 -0
  330. package/src/runtime/office/core/office-sessions.mjs +5 -0
  331. package/src/runtime/office/core/office-transactions.mjs +3 -0
  332. package/src/runtime/office/core/office-write-ledger.mjs +98 -0
  333. package/src/runtime/office/design/library/templates/mixdog-executive.mixdog-edit.pptx +0 -0
  334. package/src/runtime/office/index.mjs +30 -0
  335. package/src/runtime/office/pdf/pdf-edit-document.mjs +2 -0
  336. package/src/runtime/office/pdf/pdf-render.mjs +23 -7
  337. package/src/runtime/office/pdf/pdf-security.mjs +2 -0
  338. package/src/runtime/office/portable/portable-opc.mjs +4 -1
  339. package/src/runtime/office/quality/visual-diff.mjs +2 -0
  340. package/src/runtime/office/shared/values.mjs +0 -5
  341. package/src/runtime/shared/background-tasks.mjs +92 -14
  342. package/src/runtime/shared/bounded-file-read.mjs +13 -0
  343. package/src/runtime/shared/bounded-line-diff.mjs +182 -0
  344. package/src/runtime/shared/buffered-appender.mjs +18 -0
  345. package/src/runtime/shared/config.mjs +78 -18
  346. package/src/runtime/shared/err-text.mjs +31 -0
  347. package/src/runtime/shared/error-presentation.mjs +159 -19
  348. package/src/runtime/shared/file-permissions.d.mts +6 -1
  349. package/src/runtime/shared/file-permissions.mjs +54 -6
  350. package/src/runtime/shared/llm/anthropic-betas.mjs +4 -3
  351. package/src/runtime/shared/llm/anthropic-effort.mjs +129 -134
  352. package/src/runtime/shared/llm/anthropic-thinking-contract.mjs +50 -14
  353. package/src/runtime/shared/llm/model-catalog.mjs +53 -40
  354. package/src/runtime/shared/llm/openai-daybreak.mjs +4 -16
  355. package/src/runtime/shared/llm/provider-catalog-registry.mjs +11 -0
  356. package/src/runtime/shared/llm/provider-model-identities.mjs +13 -4
  357. package/src/runtime/shared/llm/usage-accounting.mjs +5 -2
  358. package/src/runtime/shared/llm/usage-ledger-rollup.mjs +3 -0
  359. package/src/runtime/shared/llm/usage-rollup.mjs +0 -1
  360. package/src/runtime/shared/loopback-listener.mjs +9 -0
  361. package/src/runtime/shared/model-display.mjs +13 -0
  362. package/src/runtime/shared/provider-api-key.mjs +2 -0
  363. package/src/runtime/shared/quota-resume-store.mjs +11 -0
  364. package/src/runtime/shared/review-diff.mjs +52 -0
  365. package/src/runtime/shared/session-baseline-store.mjs +960 -0
  366. package/src/runtime/shared/session-sidebar-host.d.mts +4 -0
  367. package/src/runtime/shared/session-sidebar-host.mjs +38 -0
  368. package/src/runtime/shared/slow-span.mjs +114 -0
  369. package/src/runtime/shared/statusline/statusline-format.mjs +4 -4
  370. package/src/runtime/shared/statusline/statusline-segments.mjs +28 -4
  371. package/src/runtime/shared/task-notification-envelope.mjs +2 -2
  372. package/src/runtime/shared/tool-card-model/card-header.mjs +6 -1
  373. package/src/runtime/shared/tool-card-model/generic-detail.mjs +39 -5
  374. package/src/runtime/shared/tool-card-model.mjs +5 -2
  375. package/src/runtime/shared/tool-output-limit.mjs +4 -0
  376. package/src/runtime/shared/tool-primitives.mjs +13 -0
  377. package/src/runtime/shared/tool-result-summary.mjs +8 -1
  378. package/src/runtime/shared/tool-surface.mjs +18 -3
  379. package/src/runtime/shared/tool-work-units.mjs +40 -4
  380. package/src/runtime/shared/transcript-writer/purge.mjs +74 -0
  381. package/src/runtime/shared/transcript-writer.mjs +10 -2
  382. package/src/runtime/shared/turn-changes.mjs +670 -274
  383. package/src/runtime/shared/turn-review-reply.mjs +8 -39
  384. package/src/runtime/shared/turn-snapshot-store.mjs +94 -2
  385. package/src/runtime/shared/turn-snapshot.mjs +986 -690
  386. package/src/runtime/shared/turn-worktree-snapshot.mjs +279 -262
  387. package/src/runtime/shared/user-data-guard.mjs +262 -49
  388. package/src/runtime/shared/worktrees/carry.mjs +531 -0
  389. package/src/runtime/shared/worktrees/contract.mjs +34 -24
  390. package/src/runtime/shared/worktrees/file-recovery.mjs +68 -0
  391. package/src/runtime/shared/worktrees/git-head.mjs +205 -0
  392. package/src/runtime/shared/worktrees/git.mjs +8 -3
  393. package/src/runtime/shared/worktrees/maintenance.mjs +1 -1
  394. package/src/runtime/shared/worktrees/manager.mjs +156 -8
  395. package/src/runtime/shared/worktrees/merge.mjs +31 -21
  396. package/src/runtime/shared/worktrees/paths.d.mts +6 -0
  397. package/src/runtime/shared/worktrees/test-support.mjs +19 -4
  398. package/src/runtime/terminal-bridge/client.mjs +44 -11
  399. package/src/runtime/terminal-bridge/tool-defs.mjs +13 -6
  400. package/src/runtime/tidy/apply.mjs +47 -2
  401. package/src/runtime/tidy/run-engines.mjs +11 -5
  402. package/src/session-runtime/agent-tool-routing.mjs +19 -13
  403. package/src/session-runtime/boot/apis.mjs +19 -5
  404. package/src/session-runtime/boot/facade.mjs +1 -0
  405. package/src/session-runtime/boot/infrastructure.mjs +12 -1
  406. package/src/session-runtime/boot/session.mjs +0 -1
  407. package/src/session-runtime/boot/tools.mjs +4 -1
  408. package/src/session-runtime/cwd-plugins/cwd-apply.mjs +12 -5
  409. package/src/session-runtime/internal-tool-executor/ask-user-tool.mjs +65 -0
  410. package/src/session-runtime/internal-tool-executor/bridge-tools.mjs +3 -1
  411. package/src/session-runtime/internal-tool-executor/knowledge-tools.mjs +11 -4
  412. package/src/session-runtime/internal-tool-executor/workspace-tools.mjs +115 -18
  413. package/src/session-runtime/internal-tool-executor.mjs +6 -2
  414. package/src/session-runtime/lifecycle/cold-heir.mjs +188 -0
  415. package/src/session-runtime/lifecycle/inheritance.mjs +71 -27
  416. package/src/session-runtime/lifecycle/session-catalog.mjs +14 -3
  417. package/src/session-runtime/lifecycle/session-switch/resume.mjs +6 -0
  418. package/src/session-runtime/lifecycle/session-switch/session-delete.mjs +29 -6
  419. package/src/session-runtime/lifecycle/teardown.mjs +73 -8
  420. package/src/session-runtime/lifecycle-api.mjs +1 -0
  421. package/src/session-runtime/mcp-glue/connect.mjs +46 -14
  422. package/src/session-runtime/mcp-glue/server-catalog.mjs +20 -3
  423. package/src/session-runtime/mcp-glue.mjs +3 -0
  424. package/src/session-runtime/mcp-oauth-login.mjs +174 -0
  425. package/src/session-runtime/model-route/model-switch-target.mjs +34 -0
  426. package/src/session-runtime/model-route/set-route.mjs +85 -17
  427. package/src/session-runtime/model-route/web-search-route.mjs +1 -1
  428. package/src/session-runtime/native-web-search/routes.mjs +22 -3
  429. package/src/session-runtime/native-web-search-usage.mjs +51 -0
  430. package/src/session-runtime/native-web-search.mjs +10 -1
  431. package/src/session-runtime/new-session-config.mjs +4 -2
  432. package/src/session-runtime/provider-auth/login.mjs +4 -0
  433. package/src/session-runtime/provider-models/model-meta.mjs +12 -3
  434. package/src/session-runtime/quick-model-rows/web-search-rows.mjs +13 -8
  435. package/src/session-runtime/quick-web-search-models.mjs +13 -38
  436. package/src/session-runtime/resource-extension-sync.mjs +4 -0
  437. package/src/session-runtime/resource-mcp-api.mjs +98 -2
  438. package/src/session-runtime/resource-scope-decorate.mjs +10 -3
  439. package/src/session-runtime/runtime-review-api.mjs +21 -2
  440. package/src/session-runtime/services/agent-tool/index-write-queue.mjs +81 -20
  441. package/src/session-runtime/services/agent-tool/job-task-reconcile.mjs +13 -1
  442. package/src/session-runtime/services/agent-tool/job-views/agent-list.mjs +7 -2
  443. package/src/session-runtime/services/agent-tool/lead-worker-index/forgotten-leads.mjs +29 -0
  444. package/src/session-runtime/services/agent-tool/lead-worker-index/index-file.mjs +1 -1
  445. package/src/session-runtime/services/agent-tool/lead-worker-index.mjs +73 -7
  446. package/src/session-runtime/services/agent-tool/send-flow.mjs +22 -4
  447. package/src/session-runtime/services/agent-tool/spawn-flow/job-start.mjs +2 -0
  448. package/src/session-runtime/services/agent-tool/spawn-flow/spawn-run.mjs +22 -5
  449. package/src/session-runtime/services/agent-tool/tag-registry.mjs +15 -1
  450. package/src/session-runtime/services/agent-tool/worker-index/row-liveness.mjs +8 -1
  451. package/src/session-runtime/services/agent-tool/worker-index/row-shape.mjs +2 -2
  452. package/src/session-runtime/services/agent-tool/worker-index/row-store.mjs +10 -1
  453. package/src/session-runtime/services/agent-tool/worker-index.mjs +46 -7
  454. package/src/session-runtime/services/agent-tool/worker-rows.mjs +17 -0
  455. package/src/session-runtime/services/agent-tool.mjs +3 -0
  456. package/src/session-runtime/services/channel-admin.mjs +19 -3
  457. package/src/session-runtime/services/memory-runtime-proxy/calls.mjs +4 -0
  458. package/src/session-runtime/services/memory-runtime-proxy.mjs +1 -0
  459. package/src/session-runtime/services/provider-admin.mjs +81 -15
  460. package/src/session-runtime/session-transcript-purge.mjs +32 -0
  461. package/src/session-runtime/session-turn-api.mjs +14 -5
  462. package/src/session-runtime/settings-builtin-tools-api.mjs +9 -0
  463. package/src/session-runtime/settings-compaction-api.mjs +1 -0
  464. package/src/session-runtime/setup-tool/executor.mjs +36 -6
  465. package/src/session-runtime/setup-tool/extended-actions.mjs +86 -6
  466. package/src/session-runtime/setup-tool/settings-contract.mjs +13 -3
  467. package/src/session-runtime/setup-tool/tool-defs.mjs +13 -4
  468. package/src/session-runtime/skills-api.mjs +3 -5
  469. package/src/session-runtime/tool-surface.mjs +5 -1
  470. package/src/session-runtime/turn/compaction-gate.mjs +25 -0
  471. package/src/session-runtime/turn/session-ops.mjs +7 -5
  472. package/src/session-runtime/turn/side-question-ops.mjs +22 -0
  473. package/src/session-runtime/turn/turn-run/ask-callbacks.mjs +5 -1
  474. package/src/session-runtime/turn/turn-run/turn-prepare.mjs +8 -3
  475. package/src/session-runtime/turn/turn-run/turn-settle.mjs +10 -0
  476. package/src/session-runtime/turn/turn-run/turn-timing.mjs +4 -3
  477. package/src/session-runtime/turn/turn-run.mjs +25 -9
  478. package/src/session-runtime/usage-stats-api.mjs +43 -11
  479. package/src/session-runtime/workspace/project-branches.mjs +7 -5
  480. package/src/session-runtime/workspace/transition-steps.mjs +129 -31
  481. package/src/session-runtime/workspace/transitions.mjs +190 -15
  482. package/src/standalone/daemon-agent-control.mjs +9 -1
  483. package/src/standalone/daemon-drain-policy.mjs +8 -0
  484. package/src/standalone/daemon-local-session-bridge.mjs +14 -1
  485. package/src/standalone/daemon-owner.mjs +17 -4
  486. package/src/standalone/daemon-pg-stop.mjs +51 -0
  487. package/src/standalone/daemon-stop-budget.d.mts +2 -0
  488. package/src/standalone/daemon-stop-budget.mjs +18 -0
  489. package/src/standalone/daemon-stored-session-views.mjs +2 -0
  490. package/src/standalone/daemon-telemetry.mjs +74 -0
  491. package/src/standalone/daemon.mjs +64 -8
  492. package/src/standalone/desktop-service-registry.mjs +5 -0
  493. package/src/standalone/local-session-runtime.mjs +70 -19
  494. package/src/standalone/session-client.mjs +42 -0
  495. package/src/standalone/session-protocol.mjs +14 -0
  496. package/src/standalone/session-proxy/projection.mjs +41 -4
  497. package/src/standalone/session-runtime-inline-host/boot-loaders.mjs +15 -1
  498. package/src/standalone/session-runtime-inline-host.mjs +1 -0
  499. package/src/standalone/session-service/agent-tree/agent-cancel.mjs +33 -2
  500. package/src/standalone/session-service/agent-tree/agent-turns.mjs +26 -10
  501. package/src/standalone/session-service/agent-tree.mjs +12 -0
  502. package/src/standalone/session-service/quota-resume.mjs +8 -0
  503. package/src/standalone/session-service/retention/busy.mjs +15 -1
  504. package/src/standalone/session-service/session-calls/session-action.mjs +18 -3
  505. package/src/standalone/session-service-api.mjs +11 -1
  506. package/src/standalone/session-service.mjs +1 -0
  507. package/src/standalone/session-state-patch.d.mts +2 -1
  508. package/src/standalone/session-state-patch.mjs +41 -1
  509. package/src/tui/App.jsx +25 -0
  510. package/src/tui/app/app-view/floating-panel.jsx +26 -1
  511. package/src/tui/app/app-view.jsx +1 -0
  512. package/src/tui/app/btw-controller.mjs +118 -0
  513. package/src/tui/app/create-app-pickers.mjs +6 -0
  514. package/src/tui/app/extension-pickers/mcp-login.mjs +107 -0
  515. package/src/tui/app/extension-pickers/mcp-servers-picker.mjs +2 -1
  516. package/src/tui/app/extension-pickers.mjs +2 -1
  517. package/src/tui/app/model-options.mjs +11 -16
  518. package/src/tui/app/shell-layout/row-budgets.mjs +11 -1
  519. package/src/tui/app/shell-layout/surfaces.mjs +3 -1
  520. package/src/tui/app/slash-commands.mjs +20 -5
  521. package/src/tui/app/slash-dispatch/panel-commands.mjs +16 -1
  522. package/src/tui/app/slash-dispatch/session-commands.mjs +19 -6
  523. package/src/tui/app/use-global-key-input/approval-shortcut.mjs +12 -0
  524. package/src/tui/app/use-global-key-input.mjs +5 -12
  525. package/src/tui/app/use-prompt-handlers.mjs +11 -0
  526. package/src/tui/components/BtwPanel.jsx +53 -0
  527. package/src/tui/components/QuestionPanel.jsx +275 -0
  528. package/src/tui/components/StatusLine.jsx +2 -0
  529. package/src/tui/components/question-panel-model.mjs +156 -0
  530. package/src/tui/components/status-line/local-line.mjs +5 -2
  531. package/src/tui/components/status-line/use-statusline-text.mjs +7 -1
  532. package/src/tui/dist/index.mjs +3221 -1500
  533. package/src/tui/session/context-state.mjs +4 -2
  534. package/src/tui/session/quota-resume.mjs +46 -14
  535. package/src/tui/session/session-api/commands.mjs +43 -24
  536. package/src/tui/session/session-api/controls.mjs +2 -2
  537. package/src/tui/session/session-api/extensions.mjs +22 -0
  538. package/src/tui/session/session-api/integrations.mjs +16 -2
  539. package/src/tui/session/session-api/lifecycle/dispose.mjs +1 -0
  540. package/src/tui/session/session-api/lifecycle.mjs +7 -2
  541. package/src/tui/session/session-api/routes.mjs +71 -7
  542. package/src/tui/session/session-api/settings.mjs +11 -14
  543. package/src/tui/session/session-api.mjs +1 -0
  544. package/src/tui/session/session-flow/drain.mjs +7 -1
  545. package/src/tui/session/session-flow/failed-turn-rewind.mjs +10 -4
  546. package/src/tui/session/tool-approval.mjs +10 -7
  547. package/src/tui/session/transcript-spill.mjs +14 -7
  548. package/src/tui/session/turn-stream.mjs +174 -31
  549. package/src/tui/session/turn.mjs +46 -16
  550. package/src/tui/statusline-ansi-bridge.mjs +1 -1
  551. package/src/ui/statusline-agents.mjs +15 -4
  552. package/src/ui/statusline.mjs +7 -3
  553. package/src/ui/streaming-markdown-heal.mjs +34 -3
  554. package/src/vendor/statusline/src/gateway/claude-current.mjs +14 -4
  555. package/src/workflows/default/WORKFLOW.md +3 -1
  556. package/src/runtime/memory/lib/memory-session-merge.mjs +0 -32
  557. package/src/runtime/shared/llm/usage-session-history.mjs +0 -266
@@ -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,
@@ -24,13 +24,22 @@ import { currentToolExecutionOwner } from '../../../shared/tool-execution-owner.
24
24
  import { positiveInt } from '../../../shared/numbers.mjs';
25
25
  import {
26
26
  providerSafeMcpToolName,
27
+ resolveMcpOAuthUrl,
27
28
  resolveMcpRequestTimeouts,
28
29
  resolveMcpTransportKind,
29
30
  scrubMcpConnectionMessage,
31
+ scrubMcpSecrets,
30
32
  } from './client-config.mjs';
31
33
  import { createLazyOutputValidator } from './lazy-output-validator.mjs';
32
- import { appendMcpStderrTail, createMcpTransport } from './client-transport.mjs';
33
- import { listAllMcpTools, runBoundedHandshake } from './client-handshake.mjs';
34
+ import { appendMcpStderrTail, createMcpTransport, hasConfiguredMcpAuth } from './client-transport.mjs';
35
+ import { listAllMcpTools, MCP_CONNECT_CANCELLED, runBoundedHandshake } from './client-handshake.mjs';
36
+ import {
37
+ clearMcpOAuth,
38
+ hasStoredMcpOAuthTokens,
39
+ isMcpOAuthReauthenticationRequired,
40
+ MCP_OAUTH_CALLBACK_NOT_PENDING,
41
+ } from './oauth.mjs';
42
+ export { probeMcpServerAuth } from './oauth-probe.mjs';
34
43
 
35
44
  // Config interpretation (transport kind, URL policy, env expansion, headers,
36
45
  // timeouts, log scrubbing) lives in client-config.mjs; re-exported so every
@@ -103,6 +112,20 @@ function mcpLog(line) {
103
112
  if (process.env.MIXDOG_QUIET_MCP_LOG) return;
104
113
  process.stderr.write(line);
105
114
  }
115
+ // OAuth tokens belong to the live connection, not to its config: a server may
116
+ // echo the request (Authorization included) into an error body or result.
117
+ function oauthSecrets(...transports) {
118
+ return transports.flatMap((transport) => transport?.oauth?.secrets() || []);
119
+ }
120
+ function scrubMcpOAuthError(error, cfg, secrets) {
121
+ if (secrets.length === 0) return error;
122
+ if (!(error instanceof Error)) return new Error(scrubMcpConnectionMessage(String(error), cfg, secrets));
123
+ error.message = scrubMcpConnectionMessage(error.message, cfg, secrets);
124
+ if (typeof error.stack === 'string') error.stack = scrubMcpConnectionMessage(error.stack, cfg, secrets);
125
+ // A JSON-RPC error carries the server's own payload (McpError.data).
126
+ if (error.data !== undefined) error.data = scrubMcpSecrets(error.data, secrets);
127
+ return error;
128
+ }
106
129
 
107
130
  async function loadMcpSdk() {
108
131
  mcpSdkPromise ??= Promise.all([
@@ -148,14 +171,16 @@ export async function connectMcpServers(config, options = {}) {
148
171
  });
149
172
  // Connect all servers in PARALLEL: a slow/hung server (bounded by its
150
173
  // per-server startup timeout) must never delay the others' handshakes.
151
- const settled = await Promise.allSettled(entries.map(([name, cfg]) => connectServer(name, cfg, scopeId, genAtStart)));
174
+ const settled = await Promise.allSettled(
175
+ entries.map(([name, cfg]) => connectServer(name, cfg, scopeId, genAtStart, options))
176
+ );
152
177
  settled.forEach((res, i) => {
153
178
  if (res.status !== 'rejected') return;
154
179
  const [name, cfg] = entries[i];
155
180
  const rawMessage = res.reason instanceof Error ? res.reason.message : String(res.reason);
156
181
  const msg = scrubMcpConnectionMessage(rawMessage, cfg);
157
182
  mcpLog(`[mcp-client] Failed to connect "${name}": ${msg}\n`);
158
- failures.push({ name, msg });
183
+ failures.push({ name, msg, ...(res.reason?.authRequired ? { authRequired: true } : {}) });
159
184
  });
160
185
  if (failures.length > 0) {
161
186
  const detail = failures.map((f) => `${f.name}: ${f.msg}`).join('; ');
@@ -330,7 +355,7 @@ export function getMcpServerInstructionsMap(scopeId = DEFAULT_MCP_SCOPE_ID) {
330
355
  // shutdown generation captured BEFORE the caller queued/closed anything: a
331
356
  // disconnectAll() landing during the close (or the connect) bumps it, and the
332
357
  // replacement must then never register (no resurrection after shutdown).
333
- async function replaceServerConnection(scopeId, name, current, cfg, genAtStart) {
358
+ async function replaceServerConnection(scopeId, name, current, cfg, genAtStart, options = {}) {
334
359
  const registryKey = mcpServerRegistryKey(scopeId, name);
335
360
  if (current) {
336
361
  await _closeServer(current);
@@ -343,10 +368,12 @@ async function replaceServerConnection(scopeId, name, current, cfg, genAtStart)
343
368
  try {
344
369
  // connectServer re-checks the same generation after loading the SDK and
345
370
  // again right before registering, so a shutdown mid-handshake also aborts.
346
- await connectServer(name, cfg, scopeId, genAtStart);
371
+ await connectServer(name, cfg, scopeId, genAtStart, options);
347
372
  } catch (reason) {
348
373
  const rawMessage = reason instanceof Error ? reason.message : String(reason);
349
- throw new Error(scrubMcpConnectionMessage(rawMessage, cfg));
374
+ const error = new Error(scrubMcpConnectionMessage(rawMessage, cfg));
375
+ if (reason?.authRequired) error.authRequired = true;
376
+ throw error;
350
377
  }
351
378
  }
352
379
 
@@ -356,6 +383,7 @@ async function replaceServerConnection(scopeId, name, current, cfg, genAtStart)
356
383
  // connect per server is ever running (no doubly registered transports).
357
384
  async function runExclusiveServerOp(registryKey, task) {
358
385
  for (let pending = reconnects.wait(registryKey); pending; pending = reconnects.wait(registryKey)) {
386
+ // Only waiting for the reconnect to settle; its failure belongs to its caller.
359
387
  await pending.catch(() => {});
360
388
  }
361
389
  return reconnects.run(registryKey, task);
@@ -396,7 +424,7 @@ async function reconnectMcpServer(scopeId, serverName, failedServer) {
396
424
  * rejects (credential-scrubbed message) when the connect fails, leaving the
397
425
  * server unregistered.
398
426
  */
399
- export async function reconnectSingleMcpServer(scopeId, name, cfg = undefined) {
427
+ export async function reconnectSingleMcpServer(scopeId, name, cfg = undefined, options = {}) {
400
428
  const normalized = normalizeMcpScopeId(scopeId);
401
429
  _knownMcpScopes.add(normalized);
402
430
  const registryKey = mcpServerRegistryKey(normalized, name);
@@ -415,7 +443,7 @@ export async function reconnectSingleMcpServer(scopeId, name, cfg = undefined) {
415
443
  closeCallAdmission(normalized, name, `MCP ${name} disabled`);
416
444
  return false;
417
445
  }
418
- await replaceServerConnection(normalized, name, current, nextCfg, genAtStart);
446
+ await replaceServerConnection(normalized, name, current, nextCfg, genAtStart, options);
419
447
  return true;
420
448
  });
421
449
  }
@@ -438,96 +466,101 @@ export async function executeMcpTool(name, args, options = {}) {
438
466
  definition?.mcpOperation
439
467
  ? _callMcpFeatureWithTimeout(target, definition.mcpOperation, args, callSignal)
440
468
  : _callToolWithTimeout(target, toolName, args, callSignal);
469
+ let retryServer = null;
470
+ const secrets = () => oauthSecrets(server.transport, retryServer?.transport);
441
471
  const gate = callAdmissionFor(server);
442
- return gate.run(
443
- options?.ownerKey || currentToolExecutionOwner(),
444
- async () => {
445
- // The gate stops forwarding the caller's abort once this task is
446
- // admitted, so the task itself observes the signal for its whole run.
447
- if (callSignal?.aborted) {
448
- throw mcpAbortError(
449
- callSignal,
450
- `MCP tool call aborted before dispatch (server="${serverName}", tool="${toolName}")`
451
- );
452
- }
453
- let result;
454
- try {
455
- result = await dispatch(server);
456
- } catch (firstErr) {
457
- const firstMsg = firstErr instanceof Error ? firstErr.message : String(firstErr);
458
- if (isMcpCallAbortError(firstErr) || callSignal?.aborted) {
459
- // A cancelled call is not a transport failure: reconnecting and
460
- // replaying it would duplicate the side effect the caller just
461
- // walked away from.
462
- mcpLog(`[mcp-client] Tool call aborted by caller for "${serverName}/${toolName}".\n`);
463
- throw firstErr;
464
- }
465
- if (isMcpToolCallTimeoutError(firstErr)) {
466
- mcpLog(`[mcp-client] Tool call timed out; skipping reconnect retry for "${serverName}/${toolName}".\n`);
467
- throw firstErr;
468
- }
469
- const serverDead = server.liveness?.alive === false;
470
- if (!serverDead && isMcpServerResponseError(firstErr)) {
471
- // The server answered (JSON-RPC error): it is alive — nothing to
472
- // reconnect, and the call already ran its course.
473
- throw firstErr;
474
- }
475
- // Only a failure that proves the request never left this process
476
- // may be replayed; anything else may already have run server-side.
477
- const undelivered = isMcpUndeliveredError(firstErr);
478
- const deadTail = serverDead ? server.stderrTail?.() : '';
479
- mcpLog(`[mcp-client] Tool call failed, attempting shared reconnect...\n`);
480
- let retryServer;
481
- try {
482
- // The reconnect is SHARED (singleflight): the race only stops
483
- // THIS caller from waiting on it — the reconnect keeps running
484
- // for its other waiters. Without this, an abort mid-reconnect
485
- // could not settle the call and the admission slot stayed held
486
- // until the reconnect finished, or forever if it hung.
487
- retryServer = await raceMcpAbort(
488
- reconnectMcpServer(scopeId, serverName, server),
489
- callSignal,
490
- `MCP tool call aborted during reconnect (server="${serverName}", tool="${toolName}")`
491
- );
492
- } catch (reconnectErr) {
493
- if (isMcpCallAbortError(reconnectErr) || callSignal?.aborted) throw reconnectErr;
494
- const reconnectMsg = reconnectErr instanceof Error ? reconnectErr.message : String(reconnectErr);
495
- throw new Error(
496
- appendMcpStderrTail(`Tool call failed: ${firstMsg}; reconnect also failed: ${reconnectMsg}`, deadTail)
497
- );
498
- }
499
- if (!undelivered) {
500
- throw new Error(
501
- appendMcpStderrTail(
502
- `Tool call failed: ${firstMsg}; MCP server "${serverName}" was reconnected for the next call, but this call was not replayed because the request may already have reached the server`,
503
- deadTail
504
- )
505
- );
506
- }
507
- // A reconnect that outlived the caller's abort must not dispatch the
508
- // retry: that would run the side effect after cancellation.
472
+ return gate
473
+ .run(
474
+ options?.ownerKey || currentToolExecutionOwner(),
475
+ async () => {
476
+ // The gate stops forwarding the caller's abort once this task is
477
+ // admitted, so the task itself observes the signal for its whole run.
509
478
  if (callSignal?.aborted) {
510
479
  throw mcpAbortError(
511
480
  callSignal,
512
- `MCP tool call aborted after reconnect (server="${serverName}", tool="${toolName}")`
481
+ `MCP tool call aborted before dispatch (server="${serverName}", tool="${toolName}")`
513
482
  );
514
483
  }
484
+ let result;
515
485
  try {
516
- result = await dispatch(retryServer);
517
- } catch (retryErr) {
518
- if (isMcpCallAbortError(retryErr) || callSignal?.aborted) throw retryErr;
519
- const retryMsg = retryErr instanceof Error ? retryErr.message : String(retryErr);
520
- throw new Error(`Tool call failed: ${firstMsg}; retry after reconnect also failed: ${retryMsg}`);
486
+ result = await dispatch(server);
487
+ } catch (firstErr) {
488
+ const firstMsg = firstErr instanceof Error ? firstErr.message : String(firstErr);
489
+ if (isMcpCallAbortError(firstErr) || callSignal?.aborted) {
490
+ // A cancelled call is not a transport failure: reconnecting and
491
+ // replaying it would duplicate the side effect the caller just
492
+ // walked away from.
493
+ mcpLog(`[mcp-client] Tool call aborted by caller for "${serverName}/${toolName}".\n`);
494
+ throw firstErr;
495
+ }
496
+ if (isMcpToolCallTimeoutError(firstErr)) {
497
+ mcpLog(`[mcp-client] Tool call timed out; skipping reconnect retry for "${serverName}/${toolName}".\n`);
498
+ throw firstErr;
499
+ }
500
+ const serverDead = server.liveness?.alive === false;
501
+ if (!serverDead && isMcpServerResponseError(firstErr)) {
502
+ // The server answered (JSON-RPC error): it is alive — nothing to
503
+ // reconnect, and the call already ran its course.
504
+ throw firstErr;
505
+ }
506
+ // Only a failure that proves the request never left this process
507
+ // may be replayed; anything else may already have run server-side.
508
+ const undelivered = isMcpUndeliveredError(firstErr);
509
+ const deadTail = serverDead ? server.stderrTail?.() : '';
510
+ mcpLog(`[mcp-client] Tool call failed, attempting shared reconnect...\n`);
511
+ try {
512
+ // The reconnect is SHARED (singleflight): the race only stops
513
+ // THIS caller from waiting on it — the reconnect keeps running
514
+ // for its other waiters. Without this, an abort mid-reconnect
515
+ // could not settle the call and the admission slot stayed held
516
+ // until the reconnect finished, or forever if it hung.
517
+ retryServer = await raceMcpAbort(
518
+ reconnectMcpServer(scopeId, serverName, server),
519
+ callSignal,
520
+ `MCP tool call aborted during reconnect (server="${serverName}", tool="${toolName}")`
521
+ );
522
+ } catch (reconnectErr) {
523
+ if (isMcpCallAbortError(reconnectErr) || callSignal?.aborted) throw reconnectErr;
524
+ const reconnectMsg = reconnectErr instanceof Error ? reconnectErr.message : String(reconnectErr);
525
+ throw new Error(
526
+ appendMcpStderrTail(`Tool call failed: ${firstMsg}; reconnect also failed: ${reconnectMsg}`, deadTail)
527
+ );
528
+ }
529
+ if (!undelivered) {
530
+ throw new Error(
531
+ appendMcpStderrTail(
532
+ `Tool call failed: ${firstMsg}; MCP server "${serverName}" was reconnected for the next call, but this call was not replayed because the request may already have reached the server`,
533
+ deadTail
534
+ )
535
+ );
536
+ }
537
+ // A reconnect that outlived the caller's abort must not dispatch the
538
+ // retry: that would run the side effect after cancellation.
539
+ if (callSignal?.aborted) {
540
+ throw mcpAbortError(
541
+ callSignal,
542
+ `MCP tool call aborted after reconnect (server="${serverName}", tool="${toolName}")`
543
+ );
544
+ }
545
+ try {
546
+ result = await dispatch(retryServer);
547
+ } catch (retryErr) {
548
+ if (isMcpCallAbortError(retryErr) || callSignal?.aborted) throw retryErr;
549
+ const retryMsg = retryErr instanceof Error ? retryErr.message : String(retryErr);
550
+ throw new Error(`Tool call failed: ${firstMsg}; retry after reconnect also failed: ${retryMsg}`);
551
+ }
521
552
  }
553
+ const normalized = normalizeToolEnvelope(await normalizeMcpToolResult(scrubMcpSecrets(result, secrets())));
554
+ const text = capMcpResult(normalized.result);
555
+ return normalized.explicitSuccess ? makeToolEnvelope(text, [], { explicitSuccess: true }) : text;
556
+ },
557
+ {
558
+ signal: callSignal,
522
559
  }
523
- const normalized = normalizeToolEnvelope(await normalizeMcpToolResult(result));
524
- const text = capMcpResult(normalized.result);
525
- return normalized.explicitSuccess ? makeToolEnvelope(text, [], { explicitSuccess: true }) : text;
526
- },
527
- {
528
- signal: callSignal,
529
- }
530
- );
560
+ )
561
+ .catch((error) => {
562
+ throw scrubMcpOAuthError(error, server.cfg, secrets());
563
+ });
531
564
  }
532
565
 
533
566
  const MCP_FEATURE_REQUESTS = {
@@ -903,6 +936,7 @@ export function raceMcpAbort(promise, signal, message) {
903
936
  if (!signal) return promise;
904
937
  allowConcurrentAbortListeners(signal);
905
938
  if (signal.aborted) {
939
+ // The abort wins; the abandoned promise's late rejection must not go unhandled.
906
940
  void Promise.resolve(promise).catch(() => {});
907
941
  return Promise.reject(mcpAbortError(signal, message));
908
942
  }
@@ -1090,6 +1124,7 @@ export async function disconnectAll(options = undefined) {
1090
1124
  /* ignore */
1091
1125
  }
1092
1126
  try {
1127
+ entry.transport.oauth?.close();
1093
1128
  void entry.client.close().catch(() => {
1094
1129
  /* ignore */
1095
1130
  });
@@ -1118,6 +1153,9 @@ export async function disconnectAll(options = undefined) {
1118
1153
  export async function disconnectMcpServer(name, options = {}) {
1119
1154
  const scopeId = normalizeMcpScopeId(options);
1120
1155
  const registryKey = mcpServerRegistryKey(scopeId, name);
1156
+ // An exclusive reconnect may currently be waiting for human authorization.
1157
+ // Cancel before queueing the disconnect behind that operation.
1158
+ cancelMcpLogin(name, options);
1121
1159
  return runExclusiveServerOp(registryKey, async () => {
1122
1160
  const server = servers.get(registryKey);
1123
1161
  if (!server) return false;
@@ -1132,6 +1170,73 @@ export async function disconnectMcpServer(name, options = {}) {
1132
1170
  return true;
1133
1171
  });
1134
1172
  }
1173
+ /**
1174
+ * Cancel the interactive OAuth login of a connect of `name` still in its
1175
+ * handshake; that connect then fails. Callers that queue behind the connect
1176
+ * (disable, remove) call this first so they never wait out the login.
1177
+ */
1178
+ export function cancelMcpLogin(name, options = {}) {
1179
+ const scopeId = normalizeMcpScopeId(options);
1180
+ for (const entry of _pendingConnects) {
1181
+ if (entry.scopeId === scopeId && entry.name === name) entry.transport.oauth?.close();
1182
+ }
1183
+ }
1184
+ /**
1185
+ * Answer the interactive OAuth login of a connect of `name` still in its
1186
+ * handshake with the redirect URL pasted from the browser (remote / headless
1187
+ * sign-in); that connect then proceeds as after the loopback callback. Throws
1188
+ * a fixed message, never the pasted URL, for an invalid paste (the login stays
1189
+ * pending) or when no login is waiting.
1190
+ */
1191
+ export function completeMcpLogin(name, callbackUrl, options = {}) {
1192
+ const scopeId = normalizeMcpScopeId(options);
1193
+ for (const entry of _pendingConnects) {
1194
+ if (entry.scopeId === scopeId && entry.name === name && entry.transport.oauth?.loginStarted) {
1195
+ entry.transport.oauth.completeCallback(callbackUrl);
1196
+ return;
1197
+ }
1198
+ }
1199
+ throw new Error(MCP_OAUTH_CALLBACK_NOT_PENDING);
1200
+ }
1201
+ /** Whether a connect of `name` still in its handshake is waiting for an
1202
+ * interactive (browser) OAuth login. */
1203
+ export function isMcpLoginPending(name, options = {}) {
1204
+ const scopeId = normalizeMcpScopeId(options);
1205
+ for (const entry of _pendingConnects) {
1206
+ if (entry.scopeId === scopeId && entry.name === name && entry.transport.oauth?.loginStarted) return true;
1207
+ }
1208
+ return false;
1209
+ }
1210
+ /**
1211
+ * Stored OAuth sign-in state of an HTTP/SSE server without configured
1212
+ * authentication: `{ signedIn }` (tokens stored for its exact URL), or null
1213
+ * for servers that never use MCP OAuth.
1214
+ */
1215
+ export function getMcpServerOAuthState(cfg) {
1216
+ let url;
1217
+ try {
1218
+ url = resolveMcpOAuthUrl(cfg);
1219
+ } catch {
1220
+ return null;
1221
+ }
1222
+ if (!url || hasConfiguredMcpAuth(cfg)) return null;
1223
+ return { signedIn: hasStoredMcpOAuthTokens(url) };
1224
+ }
1225
+ /**
1226
+ * Log out of an HTTP/SSE server URL: delete the OAuth tokens and client
1227
+ * registration stored for it, shared by every server configured with that
1228
+ * exact URL, and drop them from live connections.
1229
+ */
1230
+ export async function clearMcpServerOAuth(cfg) {
1231
+ let url;
1232
+ try {
1233
+ url = resolveMcpOAuthUrl(cfg);
1234
+ } catch {
1235
+ // A config without a valid remote URL never connected, so never logged in.
1236
+ return;
1237
+ }
1238
+ if (url) await clearMcpOAuth({ url });
1239
+ }
1135
1240
  /**
1136
1241
  * Close a single server: for stdio transports first shut down the full child
1137
1242
  * process tree (close stdin -> grace -> tree kill) so wrapper chains such as
@@ -1143,6 +1248,7 @@ async function _closeServer(server) {
1143
1248
  // A deliberate close is not a crash: suppress the unexpected-close report.
1144
1249
  if (server?.liveness) server.liveness.closing = true;
1145
1250
  const transport = server?.transport;
1251
+ transport?.oauth?.close();
1146
1252
  // Live stdio transports expose the spawned ChildProcess on _process.
1147
1253
  if (transport?._process) {
1148
1254
  try {
@@ -1165,7 +1271,8 @@ async function connectServer(
1165
1271
  name,
1166
1272
  cfg,
1167
1273
  scopeId = DEFAULT_MCP_SCOPE_ID,
1168
- genAtStart = currentConnectAbortGeneration(scopeId)
1274
+ genAtStart = currentConnectAbortGeneration(scopeId),
1275
+ options = {}
1169
1276
  ) {
1170
1277
  scopeId = normalizeMcpScopeId(scopeId);
1171
1278
  _knownMcpScopes.add(scopeId);
@@ -1182,8 +1289,14 @@ async function connectServer(
1182
1289
  sdk,
1183
1290
  log: mcpLog,
1184
1291
  strictEnv: cfg?._mixdogStrictEnv === true,
1292
+ // Only an explicit sign-in (beginMcpOAuthLogin) is interactive; it may
1293
+ // supply its own browser launcher to capture the authorization URL.
1294
+ oauthOptions: {
1295
+ interactive: options.interactive === true,
1296
+ ...(typeof options.openBrowser === 'function' ? { openBrowser: options.openBrowser } : {}),
1297
+ },
1185
1298
  });
1186
- const liveness = trackMcpLiveness(client, name, cfg, stderrTail);
1299
+ const liveness = trackMcpLiveness(client, name, cfg, stderrTail, transport);
1187
1300
  const pending = { scopeId, name, client, transport };
1188
1301
  _pendingConnects.add(pending);
1189
1302
  try {
@@ -1237,6 +1350,22 @@ async function connectServer(
1237
1350
  }
1238
1351
  _invalidateMcpToolFieldMemo();
1239
1352
  mcpLog(`[mcp] connected: ${tools.length} tools — ${tools.map((t) => t.name).join(', ')}\n`);
1353
+ } catch (error) {
1354
+ await _closeServer({ client, transport, liveness });
1355
+ const scrubbed = scrubMcpOAuthError(error, cfg, oauthSecrets(transport));
1356
+ // A server that only needs an OAuth sign-in is not a broken server: the
1357
+ // sign-in was required, or an explicit one did not complete (cancelled,
1358
+ // denied, timed out or a failed exchange after it started, or cancelled
1359
+ // before it could). A failure after the sign-in obtained tokens is real.
1360
+ const signInIncomplete =
1361
+ transport.oauth &&
1362
+ (isMcpOAuthReauthenticationRequired(error) ||
1363
+ (transport.oauth.loginStarted && !transport.oauth.loginCompleted) ||
1364
+ (transport.oauth.interactive && error?.code === MCP_CONNECT_CANCELLED));
1365
+ if (signInIncomplete && scrubbed && typeof scrubbed === 'object') {
1366
+ scrubbed.authRequired = true;
1367
+ }
1368
+ throw scrubbed;
1240
1369
  } finally {
1241
1370
  _pendingConnects.delete(pending);
1242
1371
  }
@@ -1249,7 +1378,7 @@ async function connectServer(
1249
1378
  * records the last error (parse/send hiccups do not mean the server died).
1250
1379
  * Handlers already installed on the client are chained, not replaced.
1251
1380
  */
1252
- function trackMcpLiveness(client, name, cfg, stderrTail) {
1381
+ function trackMcpLiveness(client, name, cfg, stderrTail, transport) {
1253
1382
  const liveness = { alive: true, closing: false, closedAt: 0, lastError: '' };
1254
1383
  const priorClose = client.onclose;
1255
1384
  const priorError = client.onerror;
@@ -1263,7 +1392,11 @@ function trackMcpLiveness(client, name, cfg, stderrTail) {
1263
1392
  if (typeof priorClose === 'function') priorClose.apply(client, args);
1264
1393
  };
1265
1394
  client.onerror = (error, ...rest) => {
1266
- liveness.lastError = scrubMcpConnectionMessage(error instanceof Error ? error.message : String(error), cfg);
1395
+ liveness.lastError = scrubMcpConnectionMessage(
1396
+ error instanceof Error ? error.message : String(error),
1397
+ cfg,
1398
+ oauthSecrets(transport)
1399
+ );
1267
1400
  if (typeof priorError === 'function') priorError.call(client, error, ...rest);
1268
1401
  };
1269
1402
  return liveness;
@@ -1316,6 +1449,7 @@ export function setMcpScopeRoot(scopeId, root) {
1316
1449
  if (server.cfg?._mixdogRootsFollowScope !== true) continue;
1317
1450
  Promise.resolve()
1318
1451
  .then(() => server.client?.sendRootsListChanged?.())
1452
+ // A server that rejects the roots notification keeps its previous roots.
1319
1453
  .catch(() => {});
1320
1454
  }
1321
1455
  }
@@ -1383,7 +1517,7 @@ async function runToolRefresh(registryKey, live, name, cfg, capabilities) {
1383
1517
  _invalidateMcpToolFieldMemo();
1384
1518
  } catch (error) {
1385
1519
  mcpLog(
1386
- `[mcp-client] Failed to refresh tools for "${name}": ${scrubMcpConnectionMessage(error?.message || error, cfg)}\n`
1520
+ `[mcp-client] Failed to refresh tools for "${name}": ${scrubMcpConnectionMessage(error?.message || error, cfg, oauthSecrets(live.transport))}\n`
1387
1521
  );
1388
1522
  }
1389
1523
  }
@@ -1419,7 +1553,7 @@ export function _registerMcpServerForTest(scopeId, name, rawTools = [], options
1419
1553
  capabilities,
1420
1554
  liveness: { alive: true, closing: false, closedAt: 0, lastError: '' },
1421
1555
  stderrTail: typeof options.stderrTail === 'function' ? options.stderrTail : () => '',
1422
- transport: null,
1556
+ transport: options.transport || null,
1423
1557
  client: {
1424
1558
  callTool:
1425
1559
  typeof options.callTool === 'function'