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.
- package/README.md +18 -0
- package/package.json +2 -1
- package/scripts/gate-local.mjs +19 -3
- package/scripts/model-catalog-audit.mjs +1 -15
- package/scripts/provider-pingpong-live.mjs +509 -0
- package/src/agents/advisor/AGENT.md +1 -1
- package/src/agents/advisor/agent.json +1 -1
- package/src/agents/reviewer/AGENT.md +3 -3
- package/src/agents/reviewer/agent.json +1 -1
- package/src/defaults/shipped-history.json +6 -0
- package/src/defaults/skills/setup/SKILL.md +6 -1
- package/src/defaults/skills/setup/references/actions.md +44 -22
- package/src/defaults/skills/setup/references/surfaces.md +10 -8
- package/src/headless-exec.mjs +15 -8
- package/src/lib/keychain-cjs.cjs +7 -4
- package/src/output-styles/common.md +11 -10
- package/src/repl/slash-commands.mjs +16 -1
- package/src/rules/agent/AGENT.md +5 -5
- package/src/rules/lead/LEAD.md +5 -5
- package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +29 -4
- package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +10 -0
- package/src/runtime/agent/orchestrator/agent-trace-io.mjs +92 -61
- package/src/runtime/agent/orchestrator/cache-mtime.mjs +10 -1
- package/src/runtime/agent/orchestrator/config-providers-merge.mjs +7 -0
- package/src/runtime/agent/orchestrator/config.mjs +5 -0
- package/src/runtime/agent/orchestrator/context/collect.mjs +1 -0
- package/src/runtime/agent/orchestrator/context/deferred-tools.mjs +26 -7
- package/src/runtime/agent/orchestrator/context/skill-catalog.mjs +112 -7
- package/src/runtime/agent/orchestrator/mcp/client-config.mjs +36 -7
- package/src/runtime/agent/orchestrator/mcp/client-handshake.mjs +49 -10
- package/src/runtime/agent/orchestrator/mcp/client-transport.mjs +40 -3
- package/src/runtime/agent/orchestrator/mcp/client.mjs +230 -96
- package/src/runtime/agent/orchestrator/mcp/oauth-callback.mjs +103 -0
- package/src/runtime/agent/orchestrator/mcp/oauth-credentials.mjs +150 -0
- package/src/runtime/agent/orchestrator/mcp/oauth-login.mjs +111 -0
- package/src/runtime/agent/orchestrator/mcp/oauth-probe.mjs +87 -0
- package/src/runtime/agent/orchestrator/mcp/oauth-provider.mjs +95 -0
- package/src/runtime/agent/orchestrator/mcp/oauth-request.mjs +122 -0
- package/src/runtime/agent/orchestrator/mcp/oauth-shared.mjs +9 -0
- package/src/runtime/agent/orchestrator/mcp/oauth.mjs +229 -0
- package/src/runtime/agent/orchestrator/providers/account-pool.mjs +188 -29
- package/src/runtime/agent/orchestrator/providers/anthropic-api-recovery.mjs +2 -1
- package/src/runtime/agent/orchestrator/providers/anthropic-api-request.mjs +48 -20
- package/src/runtime/agent/orchestrator/providers/anthropic-api-transport.mjs +94 -31
- package/src/runtime/agent/orchestrator/providers/anthropic-error-contract.mjs +98 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-fast-mode.mjs +39 -18
- package/src/runtime/agent/orchestrator/providers/anthropic-max-tokens-recovery.mjs +55 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-max-tokens.mjs +48 -37
- package/src/runtime/agent/orchestrator/providers/anthropic-messages.mjs +10 -7
- package/src/runtime/agent/orchestrator/providers/anthropic-midstream-recovery.mjs +18 -4
- package/src/runtime/agent/orchestrator/providers/anthropic-model-resolve.mjs +13 -1
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth-client-version.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth-midstream.mjs +10 -1
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth-recovery.mjs +23 -9
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth-request/initial-status.mjs +46 -20
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth-request.mjs +14 -9
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +102 -45
- package/src/runtime/agent/orchestrator/providers/anthropic-overload-fallback.mjs +69 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-server-fallback.mjs +4 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-sse-turn/turn-blocks.mjs +20 -1
- package/src/runtime/agent/orchestrator/providers/anthropic-sse-turn/turn-events/content-block-delta.mjs +15 -2
- package/src/runtime/agent/orchestrator/providers/anthropic-sse-turn/turn-events/content-block-start.mjs +11 -4
- package/src/runtime/agent/orchestrator/providers/anthropic-sse-turn/turn-events/message-events.mjs +3 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-sse-turn/turn-events.mjs +12 -5
- package/src/runtime/agent/orchestrator/providers/anthropic-sse-turn/turn-outcome.mjs +1 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-sse-turn/turn-state.mjs +14 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +10 -1
- package/src/runtime/agent/orchestrator/providers/anthropic-turn-result.mjs +9 -0
- package/src/runtime/agent/orchestrator/providers/anthropic.mjs +40 -7
- package/src/runtime/agent/orchestrator/providers/antigravity-oauth-catalog.mjs +30 -9
- package/src/runtime/agent/orchestrator/providers/antigravity-oauth.mjs +7 -0
- package/src/runtime/agent/orchestrator/providers/antigravity-request.mjs +88 -21
- package/src/runtime/agent/orchestrator/providers/antigravity-response.mjs +30 -10
- package/src/runtime/agent/orchestrator/providers/antigravity-retry-policy.mjs +106 -0
- package/src/runtime/agent/orchestrator/providers/antigravity-stream.mjs +10 -61
- package/src/runtime/agent/orchestrator/providers/antigravity-transport.mjs +94 -15
- package/src/runtime/agent/orchestrator/providers/codex-client-meta.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/command-code-oauth.mjs +117 -72
- package/src/runtime/agent/orchestrator/providers/compat-request-policy.mjs +78 -2
- package/src/runtime/agent/orchestrator/providers/cursor-auth.mjs +75 -10
- package/src/runtime/agent/orchestrator/providers/cursor-client-version.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/cursor-wire-guards.mjs +97 -2
- package/src/runtime/agent/orchestrator/providers/cursor-wire-stream-bridge.mjs +103 -23
- package/src/runtime/agent/orchestrator/providers/cursor-wire-stream-messages.mjs +43 -2
- package/src/runtime/agent/orchestrator/providers/cursor-wire-transport.mjs +103 -33
- package/src/runtime/agent/orchestrator/providers/cursor-wire.mjs +32 -6
- package/src/runtime/agent/orchestrator/providers/cursor.mjs +27 -3
- package/src/runtime/agent/orchestrator/providers/custom-provider-config.mjs +64 -1
- package/src/runtime/agent/orchestrator/providers/custom-provider.mjs +70 -6
- package/src/runtime/agent/orchestrator/providers/custom-reasoning.mjs +34 -7
- package/src/runtime/agent/orchestrator/providers/custom-tool-wire.mjs +32 -6
- package/src/runtime/agent/orchestrator/providers/deepseek.mjs +67 -0
- package/src/runtime/agent/orchestrator/providers/effort-configuration.mjs +80 -18
- package/src/runtime/agent/orchestrator/providers/gemini-cache-policy.mjs +11 -4
- package/src/runtime/agent/orchestrator/providers/gemini-cache.mjs +46 -8
- package/src/runtime/agent/orchestrator/providers/gemini-error-contract.mjs +273 -0
- package/src/runtime/agent/orchestrator/providers/gemini-family.mjs +9 -0
- package/src/runtime/agent/orchestrator/providers/gemini-history-signatures.mjs +9 -4
- package/src/runtime/agent/orchestrator/providers/gemini-request-body.mjs +41 -5
- package/src/runtime/agent/orchestrator/providers/gemini-response.mjs +3 -1
- package/src/runtime/agent/orchestrator/providers/gemini-retry-policy.mjs +180 -0
- package/src/runtime/agent/orchestrator/providers/gemini-schema.mjs +369 -112
- package/src/runtime/agent/orchestrator/providers/gemini-sdk-request.mjs +3 -1
- package/src/runtime/agent/orchestrator/providers/gemini-stream.mjs +140 -35
- package/src/runtime/agent/orchestrator/providers/gemini-thinking.mjs +70 -24
- package/src/runtime/agent/orchestrator/providers/gemini.mjs +80 -29
- package/src/runtime/agent/orchestrator/providers/grok-client-version.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/grok-oauth-tokens.mjs +3 -0
- package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +69 -19
- package/src/runtime/agent/orchestrator/providers/lib/action-input-contract.mjs +60 -0
- package/src/runtime/agent/orchestrator/providers/lib/anthropic-native-blocks.mjs +1 -0
- package/src/runtime/agent/orchestrator/providers/lib/anthropic-request-utils.mjs +246 -42
- package/src/runtime/agent/orchestrator/providers/lib/anthropic-server-tool-usage.mjs +18 -0
- package/src/runtime/agent/orchestrator/providers/lib/anthropic-system-blocks.mjs +27 -6
- package/src/runtime/agent/orchestrator/providers/lib/claude-bridge-schema.mjs +388 -0
- package/src/runtime/agent/orchestrator/providers/lib/inclusive-usage.mjs +54 -0
- package/src/runtime/agent/orchestrator/providers/lib/origin-bound-fetch.mjs +16 -0
- package/src/runtime/agent/orchestrator/providers/lib/responses-terminal-fields.mjs +11 -0
- package/src/runtime/agent/orchestrator/providers/lib/usage-primitives.mjs +8 -0
- package/src/runtime/agent/orchestrator/providers/lib/wire-pairing.mjs +48 -22
- package/src/runtime/agent/orchestrator/providers/media-normalization.mjs +119 -18
- package/src/runtime/agent/orchestrator/providers/mimo-oauth-profile.mjs +13 -15
- package/src/runtime/agent/orchestrator/providers/mimo-oauth.mjs +3 -0
- package/src/runtime/agent/orchestrator/providers/model-list-sanitize.mjs +2 -1
- package/src/runtime/agent/orchestrator/providers/model-proxy-channel.d.mts +1 -1
- package/src/runtime/agent/orchestrator/providers/model-proxy-channel.mjs +1 -0
- package/src/runtime/agent/orchestrator/providers/model-proxy-provider.mjs +33 -1
- package/src/runtime/agent/orchestrator/providers/muse-code-oauth.mjs +4 -9
- package/src/runtime/agent/orchestrator/providers/oauth-credential-probes.mjs +2 -0
- package/src/runtime/agent/orchestrator/providers/openai-codex-metadata.mjs +2 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-chat-send.mjs +123 -61
- package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream/chunk-events.mjs +35 -14
- package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream/stream-result.mjs +27 -2
- package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream/stream-state.mjs +37 -7
- package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream/tool-call-acc.mjs +2 -9
- package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream.mjs +1 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-error-contract.mjs +50 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-models.mjs +47 -2
- package/src/runtime/agent/orchestrator/providers/openai-compat-options.mjs +47 -25
- package/src/runtime/agent/orchestrator/providers/openai-compat-presets.mjs +5 -1
- package/src/runtime/agent/orchestrator/providers/openai-compat-reasoning-field.mjs +151 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-response-normalization.mjs +64 -23
- package/src/runtime/agent/orchestrator/providers/openai-compat-responses-events.mjs +79 -33
- package/src/runtime/agent/orchestrator/providers/openai-compat-responses-head-parity.json +2901 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-responses-parity.fixtures.mjs +172 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-responses-state.mjs +3 -2
- package/src/runtime/agent/orchestrator/providers/openai-compat-responses.mjs +144 -15
- package/src/runtime/agent/orchestrator/providers/openai-compat-retry-profile.mjs +193 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-stream-common.mjs +66 -2
- package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +9 -2
- package/src/runtime/agent/orchestrator/providers/openai-compat-trace.mjs +2 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-wire.mjs +109 -26
- package/src/runtime/agent/orchestrator/providers/openai-compat-xai-send.mjs +27 -6
- package/src/runtime/agent/orchestrator/providers/openai-compat-xai.mjs +34 -20
- package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +23 -4
- package/src/runtime/agent/orchestrator/providers/openai-direct-request.mjs +37 -28
- package/src/runtime/agent/orchestrator/providers/openai-error-contract.mjs +274 -0
- package/src/runtime/agent/orchestrator/providers/openai-http-sse-request.mjs +72 -32
- package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-events/output-items.mjs +23 -10
- package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-events/terminal-frames.mjs +43 -15
- package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-events.mjs +10 -0
- package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-items.mjs +18 -7
- package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-outcome.mjs +5 -2
- package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-stamps.mjs +2 -2
- package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-state.mjs +7 -3
- package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/text-relay.mjs +9 -1
- package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state.mjs +2 -2
- package/src/runtime/agent/orchestrator/providers/openai-http-sse-watchdogs.mjs +1 -3
- package/src/runtime/agent/orchestrator/providers/openai-model-capabilities.mjs +80 -0
- package/src/runtime/agent/orchestrator/providers/openai-oauth-dispatch.mjs +37 -4
- package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +12 -1
- package/src/runtime/agent/orchestrator/providers/openai-oauth-tokens.mjs +22 -2
- package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +12 -1
- package/src/runtime/agent/orchestrator/providers/openai-responses-input.mjs +76 -33
- package/src/runtime/agent/orchestrator/providers/openai-responses-payload-parity.fixture.json +323 -0
- package/src/runtime/agent/orchestrator/providers/openai-responses-payload.mjs +44 -16
- package/src/runtime/agent/orchestrator/providers/openai-retry-policy.mjs +9 -0
- package/src/runtime/agent/orchestrator/providers/openai-ws-delta.mjs +7 -16
- package/src/runtime/agent/orchestrator/providers/openai-ws-handshake.mjs +16 -8
- package/src/runtime/agent/orchestrator/providers/openai-ws-response-state/refusals.mjs +60 -0
- package/src/runtime/agent/orchestrator/providers/openai-ws-response-state/tool-calls.mjs +51 -35
- package/src/runtime/agent/orchestrator/providers/openai-ws-response-state.mjs +30 -0
- package/src/runtime/agent/orchestrator/providers/openai-ws-send-attempts/attempt-context.mjs +1 -2
- package/src/runtime/agent/orchestrator/providers/openai-ws-send-attempts/policy.mjs +25 -14
- package/src/runtime/agent/orchestrator/providers/openai-ws-stream/event-switch.mjs +12 -0
- package/src/runtime/agent/orchestrator/providers/openai-ws-stream/terminal-frames.mjs +25 -6
- package/src/runtime/agent/orchestrator/providers/openai-ws-terminal.mjs +18 -11
- package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +41 -22
- package/src/runtime/agent/orchestrator/providers/opencode-go-gemini.mjs +170 -0
- package/src/runtime/agent/orchestrator/providers/opencode-go.mjs +75 -70
- package/src/runtime/agent/orchestrator/providers/openrouter.mjs +75 -0
- package/src/runtime/agent/orchestrator/providers/provider-error-kind.mjs +720 -0
- package/src/runtime/agent/orchestrator/providers/registry.mjs +4 -1
- package/src/runtime/agent/orchestrator/providers/retry-backoff.mjs +46 -7
- package/src/runtime/agent/orchestrator/providers/retry-classification.mjs +123 -219
- package/src/runtime/agent/orchestrator/providers/retry-classifier.mjs +2 -0
- package/src/runtime/agent/orchestrator/providers/retry-executor.mjs +148 -45
- package/src/runtime/agent/orchestrator/providers/retry-midstream.mjs +81 -16
- package/src/runtime/agent/orchestrator/providers/transport-scope.d.mts +6 -0
- package/src/runtime/agent/orchestrator/providers/transport-scope.mjs +28 -0
- package/src/runtime/agent/orchestrator/providers/wire-gateway.mjs +157 -0
- package/src/runtime/agent/orchestrator/providers/wire-route.mjs +512 -0
- package/src/runtime/agent/orchestrator/providers/xai-tool-search-head-parity.json +1131 -0
- package/src/runtime/agent/orchestrator/providers/xai-tool-search.fixtures.mjs +183 -0
- package/src/runtime/agent/orchestrator/runtime-core/ask-user-tool-defs.mjs +100 -0
- package/src/runtime/agent/orchestrator/runtime-core/config-helpers.mjs +3 -0
- package/src/runtime/agent/orchestrator/runtime-core/deferred-tool-availability.mjs +21 -0
- package/src/runtime/agent/orchestrator/runtime-core/effort.mjs +15 -0
- package/src/runtime/agent/orchestrator/runtime-core/goal-reminder.mjs +0 -18
- package/src/runtime/agent/orchestrator/runtime-core/model-capabilities.mjs +57 -28
- package/src/runtime/agent/orchestrator/runtime-core/orchestration.mjs +19 -16
- package/src/runtime/agent/orchestrator/runtime-core/plugin-mcp.mjs +1 -97
- package/src/runtime/agent/orchestrator/runtime-core/provider-request-snapshot.mjs +7 -3
- package/src/runtime/agent/orchestrator/runtime-core/session-title.mjs +21 -1
- package/src/runtime/agent/orchestrator/runtime-core/skill-tool-loading.mjs +14 -3
- package/src/runtime/agent/orchestrator/runtime-core/tool-bridge.mjs +526 -0
- package/src/runtime/agent/orchestrator/runtime-core/tool-catalog-data.mjs +3 -0
- package/src/runtime/agent/orchestrator/runtime-core/tool-catalog-schema.mjs +10 -7
- package/src/runtime/agent/orchestrator/runtime-core/tool-catalog.mjs +176 -71
- package/src/runtime/agent/orchestrator/runtime-core/tool-defs.mjs +62 -10
- package/src/runtime/agent/orchestrator/runtime-core/tool-exposure.mjs +96 -0
- package/src/runtime/agent/orchestrator/session/catalog-db/schema.mjs +5 -1
- package/src/runtime/agent/orchestrator/session/compact/constants.mjs +3 -2
- package/src/runtime/agent/orchestrator/session/compact/messages.mjs +14 -93
- package/src/runtime/agent/orchestrator/session/compact/summary-anchor.mjs +5 -0
- package/src/runtime/agent/orchestrator/session/compact/user-message-kind.mjs +120 -0
- package/src/runtime/agent/orchestrator/session/context-tool-schema.mjs +17 -2
- package/src/runtime/agent/orchestrator/session/context-utils.mjs +8 -4
- package/src/runtime/agent/orchestrator/session/eager-dispatch/admission.mjs +2 -1
- package/src/runtime/agent/orchestrator/session/eager-dispatch.mjs +6 -1
- package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +91 -1
- package/src/runtime/agent/orchestrator/session/loop/completion-guards.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/loop/context-overflow.mjs +56 -0
- package/src/runtime/agent/orchestrator/session/loop/deferred-call-through.mjs +18 -8
- package/src/runtime/agent/orchestrator/session/loop/loop-state.mjs +1 -0
- package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +21 -10
- package/src/runtime/agent/orchestrator/session/loop/provider-tool-exclusions.mjs +132 -0
- package/src/runtime/agent/orchestrator/session/loop/request-boundary.mjs +30 -0
- package/src/runtime/agent/orchestrator/session/loop/send-phase.mjs +67 -20
- package/src/runtime/agent/orchestrator/session/loop/step-auto-effort.mjs +104 -19
- package/src/runtime/agent/orchestrator/session/loop/tool-call-resolution.mjs +95 -0
- package/src/runtime/agent/orchestrator/session/loop/tool-exec/before-hook.mjs +30 -3
- package/src/runtime/agent/orchestrator/session/loop/tool-exec/dispatch.mjs +15 -4
- package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +15 -1
- package/src/runtime/agent/orchestrator/session/loop/tool-exposure-migration.mjs +150 -0
- package/src/runtime/agent/orchestrator/session/loop/tool-phase.mjs +7 -2
- package/src/runtime/agent/orchestrator/session/manager/ask-queue.mjs +30 -2
- package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +11 -2
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-auto-effort.mjs +2 -1
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-commit.mjs +34 -12
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-execute.mjs +57 -21
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-loop-options.mjs +57 -25
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-prepare.mjs +6 -0
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-settle.mjs +4 -0
- package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +8 -0
- package/src/runtime/agent/orchestrator/session/manager/pending-message-entry.mjs +5 -10
- package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +208 -14
- package/src/runtime/agent/orchestrator/session/manager/session-close.mjs +16 -4
- package/src/runtime/agent/orchestrator/session/manager/session-crud.mjs +25 -3
- package/src/runtime/agent/orchestrator/session/manager/session-fallback-title.mjs +82 -0
- package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +19 -2
- package/src/runtime/agent/orchestrator/session/manager/session-record.mjs +3 -0
- package/src/runtime/agent/orchestrator/session/manager/tool-resolution.mjs +4 -1
- package/src/runtime/agent/orchestrator/session/manager/turn-interruption.mjs +18 -7
- package/src/runtime/agent/orchestrator/session/manager.mjs +2 -1
- package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +6 -0
- package/src/runtime/agent/orchestrator/session/send-with-recovery.mjs +168 -29
- package/src/runtime/agent/orchestrator/session/side-question-store.mjs +96 -0
- package/src/runtime/agent/orchestrator/session/side-question.mjs +192 -0
- package/src/runtime/agent/orchestrator/session/store/lifecycle-barriers.mjs +17 -3
- package/src/runtime/agent/orchestrator/session/store/live-state.mjs +23 -0
- package/src/runtime/agent/orchestrator/session/store/paths-heartbeat.mjs +58 -2
- package/src/runtime/agent/orchestrator/session/store/save-worker.mjs +6 -3
- package/src/runtime/agent/orchestrator/session/store/session-delete.mjs +41 -28
- package/src/runtime/agent/orchestrator/session/store/session-observers.mjs +4 -2
- package/src/runtime/agent/orchestrator/session/store/sweep/sweep-finish.mjs +12 -1
- package/src/runtime/agent/orchestrator/session/store/write-admission.mjs +3 -1
- package/src/runtime/agent/orchestrator/session/store/write-guards.mjs +16 -0
- package/src/runtime/agent/orchestrator/session/store-agent-worker-rows.mjs +22 -10
- package/src/runtime/agent/orchestrator/session/store-summary-archived-agent.mjs +34 -15
- package/src/runtime/agent/orchestrator/session/store-summary-catalog.mjs +6 -0
- package/src/runtime/agent/orchestrator/session/store-summary-fields.mjs +13 -0
- package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +13 -1
- package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +10 -2
- package/src/runtime/agent/orchestrator/session/store.mjs +19 -3
- package/src/runtime/agent/orchestrator/session/tool-batch/execute-call.mjs +1 -0
- package/src/runtime/agent/orchestrator/session/tool-batch/finalize.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/tool-batch/record-outcome.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/tool-batch/state.mjs +7 -1
- package/src/runtime/agent/orchestrator/session/tool-result-offload.mjs +4 -0
- package/src/runtime/agent/orchestrator/session/tool-schema-rejection.mjs +77 -0
- package/src/runtime/agent/orchestrator/session/transcript-restore/restore.mjs +20 -1
- package/src/runtime/agent/orchestrator/tools/bash-policy-scan.mjs +8 -2
- package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +2 -2
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/command-preflight.mjs +3 -3
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/deadline-plan.mjs +12 -15
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +4 -1
- package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +5 -5
- package/src/runtime/agent/orchestrator/tools/builtin/external-tool-adapters.mjs +14 -10
- package/src/runtime/agent/orchestrator/tools/builtin/lib/shell-job-records.mjs +49 -34
- package/src/runtime/agent/orchestrator/tools/builtin/runtime-capabilities.mjs +23 -1
- package/src/runtime/agent/orchestrator/tools/code-graph/dispatch/root-resolution.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/code-graph/search.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/patch/ui-diff.mjs +73 -39
- package/src/runtime/agent/orchestrator/tools/progress-message.mjs +18 -1
- package/src/runtime/agent/orchestrator/tools/shell-command.mjs +7 -4
- package/src/runtime/agent/orchestrator/tools/shell-exec-policy.mjs +88 -12
- package/src/runtime/agent/orchestrator/tools/shell-policy.mjs +2 -0
- package/src/runtime/computer-bridge/action-schema.mjs +44 -21
- package/src/runtime/github/tool.mjs +92 -2
- package/src/runtime/memory/index.mjs +1 -0
- package/src/runtime/memory/lib/http-router/ingest-routes.mjs +22 -1
- package/src/runtime/memory/lib/ingest/transcript-offsets.mjs +2 -0
- package/src/runtime/memory/lib/pg/adapter.mjs +98 -1
- package/src/runtime/memory/lib/pg/process.mjs +65 -50
- package/src/runtime/memory/lib/pg/run-child.mjs +49 -0
- package/src/runtime/memory/lib/pg/supervisor.mjs +124 -16
- package/src/runtime/memory/lib/runtime-fetcher.mjs +12 -13
- package/src/runtime/memory/lib/transcript-ingest.mjs +14 -0
- package/src/runtime/office/README.md +2 -2
- package/src/runtime/office/authoring/html-document-author.mjs +2 -0
- package/src/runtime/office/authoring/pptx-contact-sheet.mjs +2 -0
- package/src/runtime/office/authoring/pptx-html-measure.mjs +2 -0
- package/src/runtime/office/authoring/pptx-html-runner.mjs +2 -0
- package/src/runtime/office/authoring/xlsx-html-runner.mjs +2 -0
- package/src/runtime/office/core/office-actions-read.mjs +3 -0
- package/src/runtime/office/core/office-render-preview.mjs +2 -0
- package/src/runtime/office/core/office-session-dispatch.mjs +7 -0
- package/src/runtime/office/core/office-sessionless-actions.mjs +8 -0
- package/src/runtime/office/core/office-sessions.mjs +5 -0
- package/src/runtime/office/core/office-transactions.mjs +3 -0
- package/src/runtime/office/core/office-write-ledger.mjs +98 -0
- package/src/runtime/office/design/library/templates/mixdog-executive.mixdog-edit.pptx +0 -0
- package/src/runtime/office/index.mjs +30 -0
- package/src/runtime/office/pdf/pdf-edit-document.mjs +2 -0
- package/src/runtime/office/pdf/pdf-render.mjs +23 -7
- package/src/runtime/office/pdf/pdf-security.mjs +2 -0
- package/src/runtime/office/portable/portable-opc.mjs +4 -1
- package/src/runtime/office/quality/visual-diff.mjs +2 -0
- package/src/runtime/office/shared/values.mjs +0 -5
- package/src/runtime/shared/background-tasks.mjs +92 -14
- package/src/runtime/shared/bounded-file-read.mjs +13 -0
- package/src/runtime/shared/bounded-line-diff.mjs +182 -0
- package/src/runtime/shared/buffered-appender.mjs +18 -0
- package/src/runtime/shared/config.mjs +78 -18
- package/src/runtime/shared/err-text.mjs +31 -0
- package/src/runtime/shared/error-presentation.mjs +159 -19
- package/src/runtime/shared/file-permissions.d.mts +6 -1
- package/src/runtime/shared/file-permissions.mjs +54 -6
- package/src/runtime/shared/llm/anthropic-betas.mjs +4 -3
- package/src/runtime/shared/llm/anthropic-effort.mjs +129 -134
- package/src/runtime/shared/llm/anthropic-thinking-contract.mjs +50 -14
- package/src/runtime/shared/llm/model-catalog.mjs +53 -40
- package/src/runtime/shared/llm/openai-daybreak.mjs +4 -16
- package/src/runtime/shared/llm/provider-catalog-registry.mjs +11 -0
- package/src/runtime/shared/llm/provider-model-identities.mjs +13 -4
- package/src/runtime/shared/llm/usage-accounting.mjs +5 -2
- package/src/runtime/shared/llm/usage-ledger-rollup.mjs +3 -0
- package/src/runtime/shared/llm/usage-rollup.mjs +0 -1
- package/src/runtime/shared/loopback-listener.mjs +9 -0
- package/src/runtime/shared/model-display.mjs +13 -0
- package/src/runtime/shared/provider-api-key.mjs +2 -0
- package/src/runtime/shared/quota-resume-store.mjs +11 -0
- package/src/runtime/shared/review-diff.mjs +52 -0
- package/src/runtime/shared/session-baseline-store.mjs +960 -0
- package/src/runtime/shared/session-sidebar-host.d.mts +4 -0
- package/src/runtime/shared/session-sidebar-host.mjs +38 -0
- package/src/runtime/shared/slow-span.mjs +114 -0
- package/src/runtime/shared/statusline/statusline-format.mjs +4 -4
- package/src/runtime/shared/statusline/statusline-segments.mjs +28 -4
- package/src/runtime/shared/task-notification-envelope.mjs +2 -2
- package/src/runtime/shared/tool-card-model/card-header.mjs +6 -1
- package/src/runtime/shared/tool-card-model/generic-detail.mjs +39 -5
- package/src/runtime/shared/tool-card-model.mjs +5 -2
- package/src/runtime/shared/tool-output-limit.mjs +4 -0
- package/src/runtime/shared/tool-primitives.mjs +13 -0
- package/src/runtime/shared/tool-result-summary.mjs +8 -1
- package/src/runtime/shared/tool-surface.mjs +18 -3
- package/src/runtime/shared/tool-work-units.mjs +40 -4
- package/src/runtime/shared/transcript-writer/purge.mjs +74 -0
- package/src/runtime/shared/transcript-writer.mjs +10 -2
- package/src/runtime/shared/turn-changes.mjs +670 -274
- package/src/runtime/shared/turn-review-reply.mjs +8 -39
- package/src/runtime/shared/turn-snapshot-store.mjs +94 -2
- package/src/runtime/shared/turn-snapshot.mjs +986 -690
- package/src/runtime/shared/turn-worktree-snapshot.mjs +279 -262
- package/src/runtime/shared/user-data-guard.mjs +262 -49
- package/src/runtime/shared/worktrees/carry.mjs +531 -0
- package/src/runtime/shared/worktrees/contract.mjs +34 -24
- package/src/runtime/shared/worktrees/file-recovery.mjs +68 -0
- package/src/runtime/shared/worktrees/git-head.mjs +205 -0
- package/src/runtime/shared/worktrees/git.mjs +8 -3
- package/src/runtime/shared/worktrees/maintenance.mjs +1 -1
- package/src/runtime/shared/worktrees/manager.mjs +156 -8
- package/src/runtime/shared/worktrees/merge.mjs +31 -21
- package/src/runtime/shared/worktrees/paths.d.mts +6 -0
- package/src/runtime/shared/worktrees/test-support.mjs +19 -4
- package/src/runtime/terminal-bridge/client.mjs +44 -11
- package/src/runtime/terminal-bridge/tool-defs.mjs +13 -6
- package/src/runtime/tidy/apply.mjs +47 -2
- package/src/runtime/tidy/run-engines.mjs +11 -5
- package/src/session-runtime/agent-tool-routing.mjs +19 -13
- package/src/session-runtime/boot/apis.mjs +19 -5
- package/src/session-runtime/boot/facade.mjs +1 -0
- package/src/session-runtime/boot/infrastructure.mjs +12 -1
- package/src/session-runtime/boot/session.mjs +0 -1
- package/src/session-runtime/boot/tools.mjs +4 -1
- package/src/session-runtime/cwd-plugins/cwd-apply.mjs +12 -5
- package/src/session-runtime/internal-tool-executor/ask-user-tool.mjs +65 -0
- package/src/session-runtime/internal-tool-executor/bridge-tools.mjs +3 -1
- package/src/session-runtime/internal-tool-executor/knowledge-tools.mjs +11 -4
- package/src/session-runtime/internal-tool-executor/workspace-tools.mjs +115 -18
- package/src/session-runtime/internal-tool-executor.mjs +6 -2
- package/src/session-runtime/lifecycle/cold-heir.mjs +188 -0
- package/src/session-runtime/lifecycle/inheritance.mjs +71 -27
- package/src/session-runtime/lifecycle/session-catalog.mjs +14 -3
- package/src/session-runtime/lifecycle/session-switch/resume.mjs +6 -0
- package/src/session-runtime/lifecycle/session-switch/session-delete.mjs +29 -6
- package/src/session-runtime/lifecycle/teardown.mjs +73 -8
- package/src/session-runtime/lifecycle-api.mjs +1 -0
- package/src/session-runtime/mcp-glue/connect.mjs +46 -14
- package/src/session-runtime/mcp-glue/server-catalog.mjs +20 -3
- package/src/session-runtime/mcp-glue.mjs +3 -0
- package/src/session-runtime/mcp-oauth-login.mjs +174 -0
- package/src/session-runtime/model-route/model-switch-target.mjs +34 -0
- package/src/session-runtime/model-route/set-route.mjs +85 -17
- package/src/session-runtime/model-route/web-search-route.mjs +1 -1
- package/src/session-runtime/native-web-search/routes.mjs +22 -3
- package/src/session-runtime/native-web-search-usage.mjs +51 -0
- package/src/session-runtime/native-web-search.mjs +10 -1
- package/src/session-runtime/new-session-config.mjs +4 -2
- package/src/session-runtime/provider-auth/login.mjs +4 -0
- package/src/session-runtime/provider-models/model-meta.mjs +12 -3
- package/src/session-runtime/quick-model-rows/web-search-rows.mjs +13 -8
- package/src/session-runtime/quick-web-search-models.mjs +13 -38
- package/src/session-runtime/resource-extension-sync.mjs +4 -0
- package/src/session-runtime/resource-mcp-api.mjs +98 -2
- package/src/session-runtime/resource-scope-decorate.mjs +10 -3
- package/src/session-runtime/runtime-review-api.mjs +21 -2
- package/src/session-runtime/services/agent-tool/index-write-queue.mjs +81 -20
- package/src/session-runtime/services/agent-tool/job-task-reconcile.mjs +13 -1
- package/src/session-runtime/services/agent-tool/job-views/agent-list.mjs +7 -2
- package/src/session-runtime/services/agent-tool/lead-worker-index/forgotten-leads.mjs +29 -0
- package/src/session-runtime/services/agent-tool/lead-worker-index/index-file.mjs +1 -1
- package/src/session-runtime/services/agent-tool/lead-worker-index.mjs +73 -7
- package/src/session-runtime/services/agent-tool/send-flow.mjs +22 -4
- package/src/session-runtime/services/agent-tool/spawn-flow/job-start.mjs +2 -0
- package/src/session-runtime/services/agent-tool/spawn-flow/spawn-run.mjs +22 -5
- package/src/session-runtime/services/agent-tool/tag-registry.mjs +15 -1
- package/src/session-runtime/services/agent-tool/worker-index/row-liveness.mjs +8 -1
- package/src/session-runtime/services/agent-tool/worker-index/row-shape.mjs +2 -2
- package/src/session-runtime/services/agent-tool/worker-index/row-store.mjs +10 -1
- package/src/session-runtime/services/agent-tool/worker-index.mjs +46 -7
- package/src/session-runtime/services/agent-tool/worker-rows.mjs +17 -0
- package/src/session-runtime/services/agent-tool.mjs +3 -0
- package/src/session-runtime/services/channel-admin.mjs +19 -3
- package/src/session-runtime/services/memory-runtime-proxy/calls.mjs +4 -0
- package/src/session-runtime/services/memory-runtime-proxy.mjs +1 -0
- package/src/session-runtime/services/provider-admin.mjs +81 -15
- package/src/session-runtime/session-transcript-purge.mjs +32 -0
- package/src/session-runtime/session-turn-api.mjs +14 -5
- package/src/session-runtime/settings-builtin-tools-api.mjs +9 -0
- package/src/session-runtime/settings-compaction-api.mjs +1 -0
- package/src/session-runtime/setup-tool/executor.mjs +36 -6
- package/src/session-runtime/setup-tool/extended-actions.mjs +86 -6
- package/src/session-runtime/setup-tool/settings-contract.mjs +13 -3
- package/src/session-runtime/setup-tool/tool-defs.mjs +13 -4
- package/src/session-runtime/skills-api.mjs +3 -5
- package/src/session-runtime/tool-surface.mjs +5 -1
- package/src/session-runtime/turn/compaction-gate.mjs +25 -0
- package/src/session-runtime/turn/session-ops.mjs +7 -5
- package/src/session-runtime/turn/side-question-ops.mjs +22 -0
- package/src/session-runtime/turn/turn-run/ask-callbacks.mjs +5 -1
- package/src/session-runtime/turn/turn-run/turn-prepare.mjs +8 -3
- package/src/session-runtime/turn/turn-run/turn-settle.mjs +10 -0
- package/src/session-runtime/turn/turn-run/turn-timing.mjs +4 -3
- package/src/session-runtime/turn/turn-run.mjs +25 -9
- package/src/session-runtime/usage-stats-api.mjs +43 -11
- package/src/session-runtime/workspace/project-branches.mjs +7 -5
- package/src/session-runtime/workspace/transition-steps.mjs +129 -31
- package/src/session-runtime/workspace/transitions.mjs +190 -15
- package/src/standalone/daemon-agent-control.mjs +9 -1
- package/src/standalone/daemon-drain-policy.mjs +8 -0
- package/src/standalone/daemon-local-session-bridge.mjs +14 -1
- package/src/standalone/daemon-owner.mjs +17 -4
- package/src/standalone/daemon-pg-stop.mjs +51 -0
- package/src/standalone/daemon-stop-budget.d.mts +2 -0
- package/src/standalone/daemon-stop-budget.mjs +18 -0
- package/src/standalone/daemon-stored-session-views.mjs +2 -0
- package/src/standalone/daemon-telemetry.mjs +74 -0
- package/src/standalone/daemon.mjs +64 -8
- package/src/standalone/desktop-service-registry.mjs +5 -0
- package/src/standalone/local-session-runtime.mjs +70 -19
- package/src/standalone/session-client.mjs +42 -0
- package/src/standalone/session-protocol.mjs +14 -0
- package/src/standalone/session-proxy/projection.mjs +41 -4
- package/src/standalone/session-runtime-inline-host/boot-loaders.mjs +15 -1
- package/src/standalone/session-runtime-inline-host.mjs +1 -0
- package/src/standalone/session-service/agent-tree/agent-cancel.mjs +33 -2
- package/src/standalone/session-service/agent-tree/agent-turns.mjs +26 -10
- package/src/standalone/session-service/agent-tree.mjs +12 -0
- package/src/standalone/session-service/quota-resume.mjs +8 -0
- package/src/standalone/session-service/retention/busy.mjs +15 -1
- package/src/standalone/session-service/session-calls/session-action.mjs +18 -3
- package/src/standalone/session-service-api.mjs +11 -1
- package/src/standalone/session-service.mjs +1 -0
- package/src/standalone/session-state-patch.d.mts +2 -1
- package/src/standalone/session-state-patch.mjs +41 -1
- package/src/tui/App.jsx +25 -0
- package/src/tui/app/app-view/floating-panel.jsx +26 -1
- package/src/tui/app/app-view.jsx +1 -0
- package/src/tui/app/btw-controller.mjs +118 -0
- package/src/tui/app/create-app-pickers.mjs +6 -0
- package/src/tui/app/extension-pickers/mcp-login.mjs +107 -0
- package/src/tui/app/extension-pickers/mcp-servers-picker.mjs +2 -1
- package/src/tui/app/extension-pickers.mjs +2 -1
- package/src/tui/app/model-options.mjs +11 -16
- package/src/tui/app/shell-layout/row-budgets.mjs +11 -1
- package/src/tui/app/shell-layout/surfaces.mjs +3 -1
- package/src/tui/app/slash-commands.mjs +20 -5
- package/src/tui/app/slash-dispatch/panel-commands.mjs +16 -1
- package/src/tui/app/slash-dispatch/session-commands.mjs +19 -6
- package/src/tui/app/use-global-key-input/approval-shortcut.mjs +12 -0
- package/src/tui/app/use-global-key-input.mjs +5 -12
- package/src/tui/app/use-prompt-handlers.mjs +11 -0
- package/src/tui/components/BtwPanel.jsx +53 -0
- package/src/tui/components/QuestionPanel.jsx +275 -0
- package/src/tui/components/StatusLine.jsx +2 -0
- package/src/tui/components/question-panel-model.mjs +156 -0
- package/src/tui/components/status-line/local-line.mjs +5 -2
- package/src/tui/components/status-line/use-statusline-text.mjs +7 -1
- package/src/tui/dist/index.mjs +3221 -1500
- package/src/tui/session/context-state.mjs +4 -2
- package/src/tui/session/quota-resume.mjs +46 -14
- package/src/tui/session/session-api/commands.mjs +43 -24
- package/src/tui/session/session-api/controls.mjs +2 -2
- package/src/tui/session/session-api/extensions.mjs +22 -0
- package/src/tui/session/session-api/integrations.mjs +16 -2
- package/src/tui/session/session-api/lifecycle/dispose.mjs +1 -0
- package/src/tui/session/session-api/lifecycle.mjs +7 -2
- package/src/tui/session/session-api/routes.mjs +71 -7
- package/src/tui/session/session-api/settings.mjs +11 -14
- package/src/tui/session/session-api.mjs +1 -0
- package/src/tui/session/session-flow/drain.mjs +7 -1
- package/src/tui/session/session-flow/failed-turn-rewind.mjs +10 -4
- package/src/tui/session/tool-approval.mjs +10 -7
- package/src/tui/session/transcript-spill.mjs +14 -7
- package/src/tui/session/turn-stream.mjs +174 -31
- package/src/tui/session/turn.mjs +46 -16
- package/src/tui/statusline-ansi-bridge.mjs +1 -1
- package/src/ui/statusline-agents.mjs +15 -4
- package/src/ui/statusline.mjs +7 -3
- package/src/ui/streaming-markdown-heal.mjs +34 -3
- package/src/vendor/statusline/src/gateway/claude-current.mjs +14 -4
- package/src/workflows/default/WORKFLOW.md +3 -1
- package/src/runtime/memory/lib/memory-session-merge.mjs +0 -32
- 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
|
-
|
|
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 =
|
|
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
|
|
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(
|
|
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
|
-
|
|
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
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
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
|
|
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(
|
|
517
|
-
} catch (
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
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
|
-
|
|
524
|
-
|
|
525
|
-
|
|
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(
|
|
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'
|