mixdog 1.2.0 → 1.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -5
- package/package.json +1 -1
- package/scripts/gate-local.mjs +19 -3
- package/scripts/model-catalog-audit.mjs +1 -15
- package/src/agents/reviewer/AGENT.md +3 -3
- package/src/agents/reviewer/agent.json +1 -1
- package/src/defaults/shipped-history.json +5 -0
- package/src/defaults/skills/pptx/SKILL.md +6 -6
- package/src/defaults/skills/pptx/references/review.md +6 -4
- package/src/defaults/skills/pptx/scripts/source-extract.mjs +1 -1
- package/src/defaults/skills/setup/references/actions.md +13 -3
- package/src/defaults/skills/visualize/SKILL.md +12 -9
- package/src/repl/slash-commands.mjs +16 -1
- package/src/rules/shared/60-verification.md +3 -1
- package/src/rules/shared/70-delivery.md +4 -2
- 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/cache-mtime.mjs +26 -5
- package/src/runtime/agent/orchestrator/config.mjs +10 -4
- package/src/runtime/agent/orchestrator/context/collect.mjs +2 -1
- package/src/runtime/agent/orchestrator/context/deferred-tools.mjs +11 -5
- package/src/runtime/agent/orchestrator/context/skill-catalog.mjs +74 -21
- package/src/runtime/agent/orchestrator/mcp/client-config.mjs +122 -15
- package/src/runtime/agent/orchestrator/mcp/client-handshake.mjs +54 -11
- package/src/runtime/agent/orchestrator/mcp/client-transport.mjs +104 -9
- package/src/runtime/agent/orchestrator/mcp/client.mjs +358 -140
- package/src/runtime/agent/orchestrator/mcp/lazy-output-validator.mjs +25 -0
- 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/mcp/plugin-placeholders.mjs +54 -0
- package/src/runtime/agent/orchestrator/providers/account-pool.mjs +294 -44
- package/src/runtime/agent/orchestrator/providers/anthropic-api-recovery.mjs +2 -1
- package/src/runtime/agent/orchestrator/providers/anthropic-api-transport.mjs +94 -31
- package/src/runtime/agent/orchestrator/providers/anthropic-error-contract.mjs +96 -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 +25 -21
- package/src/runtime/agent/orchestrator/providers/anthropic-messages.mjs +3 -1
- 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 +73 -35
- 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.mjs +8 -1
- package/src/runtime/agent/orchestrator/providers/anthropic.mjs +53 -18
- 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 +30 -14
- package/src/runtime/agent/orchestrator/providers/antigravity-response.mjs +2 -1
- package/src/runtime/agent/orchestrator/providers/antigravity-retry-policy.mjs +93 -0
- package/src/runtime/agent/orchestrator/providers/antigravity-transport.mjs +82 -9
- package/src/runtime/agent/orchestrator/providers/auto-effort.mjs +9 -0
- package/src/runtime/agent/orchestrator/providers/command-code-oauth-login.mjs +337 -0
- package/src/runtime/agent/orchestrator/providers/command-code-oauth-tokens.mjs +159 -0
- package/src/runtime/agent/orchestrator/providers/command-code-oauth.mjs +447 -0
- package/src/runtime/agent/orchestrator/providers/cursor-auth.mjs +38 -5
- 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 +102 -23
- package/src/runtime/agent/orchestrator/providers/cursor-wire-transport.mjs +103 -33
- package/src/runtime/agent/orchestrator/providers/cursor-wire.mjs +29 -6
- package/src/runtime/agent/orchestrator/providers/cursor.mjs +18 -1
- package/src/runtime/agent/orchestrator/providers/custom-provider.mjs +7 -4
- package/src/runtime/agent/orchestrator/providers/effort-configuration.mjs +86 -19
- package/src/runtime/agent/orchestrator/providers/gemini-cache-policy.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/gemini-cache.mjs +29 -8
- package/src/runtime/agent/orchestrator/providers/gemini-error-contract.mjs +191 -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-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 +21 -82
- package/src/runtime/agent/orchestrator/providers/gemini-sdk-request.mjs +4 -1
- package/src/runtime/agent/orchestrator/providers/gemini-stream.mjs +71 -12
- package/src/runtime/agent/orchestrator/providers/gemini-thinking.mjs +21 -20
- package/src/runtime/agent/orchestrator/providers/gemini.mjs +97 -51
- package/src/runtime/agent/orchestrator/providers/grok-oauth-tokens.mjs +3 -0
- package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +68 -19
- package/src/runtime/agent/orchestrator/providers/lib/gemini-model-catalog.mjs +7 -1
- 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-bridge.mjs +1080 -0
- package/src/runtime/agent/orchestrator/providers/model-proxy-channel.d.mts +15 -0
- package/src/runtime/agent/orchestrator/providers/model-proxy-channel.mjs +35 -0
- package/src/runtime/agent/orchestrator/providers/muse-code-oauth.mjs +4 -9
- package/src/runtime/agent/orchestrator/providers/oauth-credential-probes.mjs +12 -0
- package/src/runtime/agent/orchestrator/providers/oauth-usage.mjs +6 -5
- package/src/runtime/agent/orchestrator/providers/openai-compat-chat-send.mjs +5 -3
- package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream/stream-result.mjs +22 -0
- 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 +6 -2
- package/src/runtime/agent/orchestrator/providers/openai-compat-options.mjs +11 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-response-normalization.mjs +23 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-responses-events.mjs +16 -8
- package/src/runtime/agent/orchestrator/providers/openai-compat-responses-state.mjs +3 -2
- package/src/runtime/agent/orchestrator/providers/openai-compat-responses.mjs +94 -13
- 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 +2 -1
- package/src/runtime/agent/orchestrator/providers/openai-compat-wire.mjs +6 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-xai-send.mjs +6 -3
- package/src/runtime/agent/orchestrator/providers/openai-compat-xai.mjs +23 -14
- package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +26 -6
- package/src/runtime/agent/orchestrator/providers/openai-direct-request.mjs +42 -25
- 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/terminal-frames.mjs +18 -11
- package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-outcome.mjs +1 -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 +2 -3
- 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-oauth-dispatch.mjs +34 -3
- package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +5 -1
- package/src/runtime/agent/orchestrator/providers/openai-oauth-tokens.mjs +22 -2
- package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +5 -0
- package/src/runtime/agent/orchestrator/providers/openai-responses-input.mjs +5 -9
- package/src/runtime/agent/orchestrator/providers/openai-retry-policy.mjs +9 -0
- package/src/runtime/agent/orchestrator/providers/openai-ws-handshake.mjs +16 -8
- 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-terminal.mjs +18 -11
- package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +35 -24
- package/src/runtime/agent/orchestrator/providers/provider-error-kind.mjs +581 -0
- package/src/runtime/agent/orchestrator/providers/registry.mjs +35 -9
- package/src/runtime/agent/orchestrator/providers/retry-backoff.mjs +46 -7
- package/src/runtime/agent/orchestrator/providers/retry-classification.mjs +118 -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/runtime-core/config-helpers.mjs +3 -0
- package/src/runtime/agent/orchestrator/runtime-core/effort.mjs +3 -0
- package/src/runtime/agent/orchestrator/runtime-core/extension-scopes.mjs +15 -55
- package/src/runtime/agent/orchestrator/runtime-core/goal-control.mjs +15 -2
- 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 +268 -181
- package/src/runtime/agent/orchestrator/runtime-core/skill-tool-loading.mjs +4 -1
- package/src/runtime/agent/orchestrator/runtime-core/tool-catalog-schema.mjs +21 -0
- package/src/runtime/agent/orchestrator/runtime-core/tool-catalog.mjs +54 -3
- package/src/runtime/agent/orchestrator/session/catalog-db/index.mjs +284 -0
- package/src/runtime/agent/orchestrator/session/catalog-db/schema.mjs +270 -0
- package/src/runtime/agent/orchestrator/session/catalog-db/sqlite-gate.mjs +27 -0
- package/src/runtime/agent/orchestrator/session/catalog-db/worker.mjs +628 -0
- package/src/runtime/agent/orchestrator/session/compact/messages.mjs +6 -2
- package/src/runtime/agent/orchestrator/session/conversation-import/claude.mjs +184 -0
- package/src/runtime/agent/orchestrator/session/conversation-import/codex.mjs +136 -0
- package/src/runtime/agent/orchestrator/session/conversation-import/convert.mjs +128 -0
- package/src/runtime/agent/orchestrator/session/conversation-import/imported-session.mjs +127 -0
- package/src/runtime/agent/orchestrator/session/conversation-import/turns.mjs +101 -0
- package/src/runtime/agent/orchestrator/session/explicit-skills.mjs +83 -5
- package/src/runtime/agent/orchestrator/session/imported-message.mjs +11 -0
- package/src/runtime/agent/orchestrator/session/loop/completion-guards.mjs +2 -1
- package/src/runtime/agent/orchestrator/session/loop/loop-state.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/loop/no-tool-turn.mjs +3 -2
- package/src/runtime/agent/orchestrator/session/loop/send-phase.mjs +10 -4
- package/src/runtime/agent/orchestrator/session/loop/steering.mjs +4 -3
- package/src/runtime/agent/orchestrator/session/loop/step-auto-effort.mjs +102 -18
- package/src/runtime/agent/orchestrator/session/loop/tool-helpers.mjs +3 -0
- package/src/runtime/agent/orchestrator/session/loop/tool-phase.mjs +3 -1
- package/src/runtime/agent/orchestrator/session/manager/ask-queue.mjs +42 -5
- package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +14 -3
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-auto-effort.mjs +20 -12
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-execute.mjs +50 -23
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-loop-options.mjs +4 -0
- package/src/runtime/agent/orchestrator/session/manager/pending-message-entry.mjs +22 -14
- package/src/runtime/agent/orchestrator/session/manager/session-close.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/manager/turn-interruption.mjs +18 -7
- package/src/runtime/agent/orchestrator/session/manager.mjs +0 -1
- package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +4 -3
- package/src/runtime/agent/orchestrator/session/send-with-recovery.mjs +155 -29
- package/src/runtime/agent/orchestrator/session/store/listing.mjs +6 -2
- package/src/runtime/agent/orchestrator/session/store/save-worker.mjs +6 -3
- package/src/runtime/agent/orchestrator/session/store/summary-cache.mjs +6 -0
- 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-catalog.mjs +819 -0
- package/src/runtime/agent/orchestrator/session/store-summary-fields.mjs +12 -0
- package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +22 -12
- package/src/runtime/agent/orchestrator/session/store-summary-reader-catalog-fixture.mjs +158 -0
- package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +183 -374
- package/src/runtime/agent/orchestrator/session/synthetic-user-envelope.mjs +3 -1
- package/src/runtime/agent/orchestrator/session/tool-batch/pre-dispatch.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/transcript-restore/restore.mjs +41 -3
- package/src/runtime/agent/orchestrator/session/transcript-restore/user-text.mjs +38 -17
- package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +2 -2
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/deadline-plan.mjs +12 -15
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/invocation-plan.mjs +17 -1
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/spawn-env.mjs +4 -0
- package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +5 -5
- package/src/runtime/agent/orchestrator/tools/builtin/lib/list-helpers.mjs +2 -2
- package/src/runtime/agent/orchestrator/tools/builtin/path-diagnostics.mjs +6 -6
- package/src/runtime/agent/orchestrator/tools/builtin/read-range-index.mjs +173 -22
- package/src/runtime/agent/orchestrator/tools/builtin/read-single-guards.mjs +8 -4
- package/src/runtime/agent/orchestrator/tools/builtin/read-single-tool.mjs +5 -4
- package/src/runtime/agent/orchestrator/tools/builtin/read-source-windows.mjs +15 -2
- package/src/runtime/agent/orchestrator/tools/builtin/search-glob-tool.mjs +13 -10
- package/src/runtime/agent/orchestrator/tools/builtin/search-grep-tool.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/builtin/search-path-diagnostics.mjs +17 -12
- package/src/runtime/agent/orchestrator/tools/code-graph/dispatch/root-resolution.mjs +2 -2
- package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/patch/apply-patch/codex-batch.mjs +4 -4
- package/src/runtime/agent/orchestrator/tools/patch/apply-patch/route.mjs +2 -2
- package/src/runtime/agent/orchestrator/tools/patch/orchestrator.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/patch/read-redirects.mjs +34 -30
- package/src/runtime/agent/orchestrator/tools/patch/sequence/units.mjs +7 -7
- package/src/runtime/agent/orchestrator/tools/patch/sequence.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/shell-snapshot.mjs +4 -0
- package/src/runtime/attachments/store.d.mts +3 -0
- package/src/runtime/channels/lib/owned-runtime/owned-start.mjs +1 -1
- package/src/runtime/channels/lib/webhook/session-dispatch.mjs +16 -4
- package/src/runtime/channels/lib/worker-main.mjs +17 -14
- package/src/runtime/computer-bridge/linux-readiness.mjs +0 -2
- package/src/runtime/effort-judge/judge-client.mjs +56 -18
- package/src/runtime/memory/lib/memory.mjs +1 -1
- package/src/runtime/memory/lib/pg/process.mjs +34 -24
- package/src/runtime/memory/lib/pg/supervisor.mjs +16 -13
- package/src/runtime/memory/lib/session-ingest.mjs +2 -2
- package/src/runtime/office/README.md +1 -1
- package/src/runtime/office/shared/values.mjs +0 -5
- package/src/runtime/shared/atomic-file.mjs +25 -4
- package/src/runtime/shared/background-tasks.mjs +92 -14
- package/src/runtime/shared/err-text.mjs +25 -0
- package/src/runtime/shared/error-presentation.mjs +159 -19
- package/src/runtime/shared/extension-scopes.mjs +5 -3
- package/src/runtime/shared/file-lock.mjs +37 -11
- package/src/runtime/shared/llm/anthropic-betas.mjs +4 -3
- package/src/runtime/shared/llm/anthropic-effort.mjs +134 -131
- package/src/runtime/shared/llm/anthropic-thinking-contract.mjs +50 -14
- package/src/runtime/shared/llm/model-catalog.mjs +53 -52
- package/src/runtime/shared/llm/openai-daybreak.mjs +4 -16
- package/src/runtime/shared/llm/provider-catalog-registry.mjs +17 -0
- package/src/runtime/shared/llm/provider-model-identities.mjs +13 -4
- package/src/runtime/shared/llm/usage-rollup.mjs +0 -1
- package/src/runtime/shared/model-display.mjs +13 -0
- package/src/runtime/shared/plugin-manifest.mjs +18 -9
- package/src/runtime/shared/pristine-execution-contract.json +9 -2
- package/src/runtime/shared/pristine-execution.mjs +28 -4
- package/src/runtime/shared/provider-accounts.mjs +124 -20
- package/src/runtime/shared/provider-api-key.mjs +12 -0
- package/src/runtime/shared/quota-resume-store.mjs +11 -0
- package/src/runtime/shared/session-baseline-store.mjs +960 -0
- package/src/runtime/shared/shell-display.mjs +9 -3
- package/src/runtime/shared/skill-mentions.d.mts +10 -0
- package/src/runtime/shared/skill-mentions.mjs +133 -0
- package/src/runtime/shared/skill-script-runtime.mjs +264 -0
- package/src/runtime/shared/statusline/statusline-format.mjs +4 -4
- package/src/runtime/shared/task-notification-envelope.mjs +2 -2
- package/src/runtime/shared/tool-primitives.mjs +35 -2
- package/src/runtime/shared/turn-changes.mjs +447 -219
- package/src/runtime/shared/turn-snapshot-store.mjs +182 -1
- package/src/runtime/shared/turn-snapshot.mjs +170 -48
- package/src/runtime/shared/turn-worktree-snapshot.mjs +160 -51
- package/src/runtime/shared/worker-requests.mjs +15 -2
- package/src/runtime/shared/worktrees/contract.mjs +0 -24
- package/src/runtime/shared/worktrees/test-support.mjs +19 -4
- package/src/session-runtime/automation-agents.mjs +8 -2
- package/src/session-runtime/boot/apis.mjs +61 -0
- package/src/session-runtime/boot/config.mjs +5 -2
- package/src/session-runtime/boot/facade.mjs +1 -0
- package/src/session-runtime/boot/infrastructure.mjs +35 -18
- package/src/session-runtime/conversation-import/discovery.mjs +698 -0
- package/src/session-runtime/conversation-import/service.mjs +369 -0
- package/src/session-runtime/cwd-plugins/cwd-apply.mjs +10 -5
- package/src/session-runtime/cwd-plugins/plugin-status.mjs +47 -42
- package/src/session-runtime/lifecycle/session-catalog.mjs +2 -0
- package/src/session-runtime/lifecycle/teardown.mjs +1 -1
- package/src/session-runtime/mcp-glue/connect.mjs +83 -16
- package/src/session-runtime/mcp-glue/plugin-servers.mjs +499 -0
- package/src/session-runtime/mcp-glue/server-catalog.mjs +135 -33
- package/src/session-runtime/mcp-glue.mjs +25 -2
- 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 +83 -15
- 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/new-session-config.mjs +3 -2
- package/src/session-runtime/notification-bus.mjs +9 -2
- package/src/session-runtime/provider-auth/accounts.mjs +33 -15
- package/src/session-runtime/provider-models/model-meta.mjs +12 -3
- package/src/session-runtime/provider-models/web-search-catalog.mjs +5 -1
- package/src/session-runtime/provider-readiness.mjs +16 -10
- 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 +24 -5
- package/src/session-runtime/resource-mcp-api.mjs +198 -78
- package/src/session-runtime/resource-plugins-api.mjs +29 -41
- package/src/session-runtime/resource-scope-decorate.mjs +6 -8
- package/src/session-runtime/resource-skills-api.mjs +23 -2
- package/src/session-runtime/runtime-review-api.mjs +13 -0
- package/src/session-runtime/schedule-session-run.mjs +1 -1
- 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/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/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-rows.mjs +17 -0
- package/src/session-runtime/services/channel-session-router.mjs +4 -1
- package/src/session-runtime/services/hook-bus/command-handler.mjs +4 -1
- package/src/session-runtime/services/hook-bus/handlers.mjs +7 -3
- package/src/session-runtime/services/provider-admin.mjs +64 -18
- package/src/session-runtime/services/usage-dashboard-model.mjs +3 -0
- package/src/session-runtime/settings-compaction-api.mjs +1 -0
- package/src/session-runtime/setup-tool/executor.mjs +12 -1
- package/src/session-runtime/setup-tool/extended-actions.mjs +1 -0
- package/src/session-runtime/setup-tool/settings-contract.mjs +1 -0
- package/src/session-runtime/setup-tool/tool-defs.mjs +7 -1
- package/src/session-runtime/skills-api.mjs +7 -3
- package/src/session-runtime/turn/turn-run/ask-callbacks.mjs +1 -0
- package/src/session-runtime/webhook-session-run.mjs +16 -4
- package/src/skill-script-entry.mjs +36 -0
- package/src/standalone/daemon-drain-policy.mjs +8 -0
- package/src/standalone/daemon-stored-session-views.mjs +11 -3
- package/src/standalone/daemon-telemetry.mjs +55 -0
- package/src/standalone/daemon.mjs +44 -11
- package/src/standalone/session-protocol.mjs +12 -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 +6 -1
- package/src/standalone/session-service-api.mjs +5 -0
- package/src/standalone/session-service.mjs +1 -0
- package/src/tui/App.jsx +9 -1
- package/src/tui/app/app-view/floating-panel.jsx +9 -1
- package/src/tui/app/app-view.jsx +7 -0
- package/src/tui/app/create-app-pickers.mjs +4 -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/plugins-pickers.mjs +35 -16
- package/src/tui/app/extension-pickers.mjs +2 -1
- package/src/tui/app/model-options.mjs +13 -16
- package/src/tui/app/skill-mention-palette.mjs +88 -0
- package/src/tui/app/slash-commands.mjs +7 -2
- package/src/tui/app/slash-dispatch/panel-commands.mjs +16 -1
- package/src/tui/app/slash-palette.mjs +23 -7
- package/src/tui/app/transcript-row-estimate.mjs +11 -3
- package/src/tui/app/use-prompt-draft-flow.mjs +7 -2
- package/src/tui/app/use-prompt-state.mjs +5 -0
- package/src/tui/components/Message.jsx +22 -1
- package/src/tui/components/PromptInput.jsx +21 -3
- package/src/tui/components/SlashCommandPalette.jsx +12 -3
- package/src/tui/components/StatusLine.jsx +2 -0
- package/src/tui/components/TranscriptItem.jsx +9 -1
- package/src/tui/components/prompt-input/key-handler.mjs +21 -8
- 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/components/user-message-skills.mjs +38 -0
- package/src/tui/dist/index.mjs +1323 -214
- package/src/tui/session/queue-helpers.mjs +7 -2
- package/src/tui/session/quota-resume.mjs +46 -14
- package/src/tui/session/session-api/commands.mjs +33 -24
- package/src/tui/session/session-api/extensions.mjs +26 -2
- package/src/tui/session/session-api/integrations.mjs +7 -0
- package/src/tui/session/session-api/lifecycle.mjs +5 -1
- package/src/tui/session/session-api/routes.mjs +71 -7
- package/src/tui/session/session-api/settings.mjs +12 -14
- package/src/tui/session/session-flow/drain.mjs +21 -5
- package/src/tui/session/session-flow/failed-turn-rewind.mjs +10 -4
- package/src/tui/session/tui-steering-persist.mjs +25 -9
- package/src/tui/session/turn.mjs +55 -15
- 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/runtime/effort-judge/previous-human-request.mjs +0 -162
- package/src/runtime/effort-judge/verified-context-input.mjs +0 -10
- package/src/runtime/memory/lib/memory-session-merge.mjs +0 -32
- package/src/runtime/shared/llm/usage-session-history.mjs +0 -266
|
@@ -22,9 +22,24 @@ import { createKeyedSingleflight } from './reconnect-singleflight.mjs';
|
|
|
22
22
|
import { createOwnerFairGate } from '../../../shared/owner-fair-gate.mjs';
|
|
23
23
|
import { currentToolExecutionOwner } from '../../../shared/tool-execution-owner.mjs';
|
|
24
24
|
import { positiveInt } from '../../../shared/numbers.mjs';
|
|
25
|
-
import {
|
|
26
|
-
|
|
27
|
-
|
|
25
|
+
import {
|
|
26
|
+
providerSafeMcpToolName,
|
|
27
|
+
resolveMcpOAuthUrl,
|
|
28
|
+
resolveMcpRequestTimeouts,
|
|
29
|
+
resolveMcpTransportKind,
|
|
30
|
+
scrubMcpConnectionMessage,
|
|
31
|
+
scrubMcpSecrets,
|
|
32
|
+
} from './client-config.mjs';
|
|
33
|
+
import { createLazyOutputValidator } from './lazy-output-validator.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';
|
|
28
43
|
|
|
29
44
|
// Config interpretation (transport kind, URL policy, env expansion, headers,
|
|
30
45
|
// timeouts, log scrubbing) lives in client-config.mjs; re-exported so every
|
|
@@ -50,6 +65,8 @@ const MCP_TOOL_NAME_RE = /^mcp__(.+?)__(.+)$/;
|
|
|
50
65
|
const _knownMcpScopes = new Set([DEFAULT_MCP_SCOPE_ID]);
|
|
51
66
|
const _connectAbortGenerations = new Map();
|
|
52
67
|
const _pendingConnects = new Set();
|
|
68
|
+
// scopeId -> root path advertised by that scope's scope-following servers.
|
|
69
|
+
const _scopeRoots = new Map();
|
|
53
70
|
let mcpSdkPromise = null;
|
|
54
71
|
// Memo for mcpToolHasField(name, field) — keyed by `${scopeId}|${toolName}|${field}`.
|
|
55
72
|
// The lookup (regex parse + servers Map get + tools.find + schema property
|
|
@@ -58,8 +75,14 @@ let mcpSdkPromise = null;
|
|
|
58
75
|
// mutation point (connectServer / disconnectAll) so a stale positive or
|
|
59
76
|
// negative can never survive a tools-list change.
|
|
60
77
|
const _mcpToolFieldMemo = new Map();
|
|
78
|
+
let _mcpToolGeneration = 0;
|
|
61
79
|
function _invalidateMcpToolFieldMemo() {
|
|
62
80
|
_mcpToolFieldMemo.clear();
|
|
81
|
+
_mcpToolGeneration += 1;
|
|
82
|
+
}
|
|
83
|
+
/** Read-only counter bumped on every MCP tool registry mutation. */
|
|
84
|
+
export function getMcpToolGeneration() {
|
|
85
|
+
return _mcpToolGeneration;
|
|
63
86
|
}
|
|
64
87
|
function normalizeMcpScopeId(value) {
|
|
65
88
|
const raw = value && typeof value === 'object' ? value.scopeId : value;
|
|
@@ -89,6 +112,20 @@ function mcpLog(line) {
|
|
|
89
112
|
if (process.env.MIXDOG_QUIET_MCP_LOG) return;
|
|
90
113
|
process.stderr.write(line);
|
|
91
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
|
+
}
|
|
92
129
|
|
|
93
130
|
async function loadMcpSdk() {
|
|
94
131
|
mcpSdkPromise ??= Promise.all([
|
|
@@ -98,12 +135,15 @@ async function loadMcpSdk() {
|
|
|
98
135
|
import('@modelcontextprotocol/sdk/client/sse.js'),
|
|
99
136
|
import('@modelcontextprotocol/sdk/client/websocket.js'),
|
|
100
137
|
import('@modelcontextprotocol/sdk/types.js'),
|
|
101
|
-
|
|
138
|
+
import('@modelcontextprotocol/sdk/validation/ajv'),
|
|
139
|
+
]).then(([clientMod, stdioMod, httpMod, sseMod, wsMod, typesMod, ajvMod]) => ({
|
|
102
140
|
Client: clientMod.Client,
|
|
141
|
+
AjvJsonSchemaValidator: ajvMod.AjvJsonSchemaValidator,
|
|
103
142
|
StdioClientTransport: stdioMod.StdioClientTransport,
|
|
104
143
|
StreamableHTTPClientTransport: httpMod.StreamableHTTPClientTransport,
|
|
105
144
|
SSEClientTransport: sseMod.SSEClientTransport,
|
|
106
145
|
WebSocketClientTransport: wsMod.WebSocketClientTransport,
|
|
146
|
+
JSONRPCMessageSchema: typesMod.JSONRPCMessageSchema,
|
|
107
147
|
ListRootsRequestSchema: typesMod.ListRootsRequestSchema,
|
|
108
148
|
ToolListChangedNotificationSchema: typesMod.ToolListChangedNotificationSchema,
|
|
109
149
|
}));
|
|
@@ -131,14 +171,16 @@ export async function connectMcpServers(config, options = {}) {
|
|
|
131
171
|
});
|
|
132
172
|
// Connect all servers in PARALLEL: a slow/hung server (bounded by its
|
|
133
173
|
// per-server startup timeout) must never delay the others' handshakes.
|
|
134
|
-
const settled = await Promise.allSettled(
|
|
174
|
+
const settled = await Promise.allSettled(
|
|
175
|
+
entries.map(([name, cfg]) => connectServer(name, cfg, scopeId, genAtStart, options))
|
|
176
|
+
);
|
|
135
177
|
settled.forEach((res, i) => {
|
|
136
178
|
if (res.status !== 'rejected') return;
|
|
137
179
|
const [name, cfg] = entries[i];
|
|
138
180
|
const rawMessage = res.reason instanceof Error ? res.reason.message : String(res.reason);
|
|
139
181
|
const msg = scrubMcpConnectionMessage(rawMessage, cfg);
|
|
140
182
|
mcpLog(`[mcp-client] Failed to connect "${name}": ${msg}\n`);
|
|
141
|
-
failures.push({ name, msg });
|
|
183
|
+
failures.push({ name, msg, ...(res.reason?.authRequired ? { authRequired: true } : {}) });
|
|
142
184
|
});
|
|
143
185
|
if (failures.length > 0) {
|
|
144
186
|
const detail = failures.map((f) => `${f.name}: ${f.msg}`).join('; ');
|
|
@@ -163,8 +205,8 @@ function mcpFeatureTools(serverName, capabilities, protocolTools) {
|
|
|
163
205
|
const tools = [];
|
|
164
206
|
const used = new Set(protocolTools.map((tool) => tool.name));
|
|
165
207
|
const add = (leaf, description, properties, required, operation) => {
|
|
166
|
-
let name =
|
|
167
|
-
|
|
208
|
+
let name = providerSafeMcpToolName(serverName, leaf);
|
|
209
|
+
for (let suffix = '_'; used.has(name); suffix += '_') name = providerSafeMcpToolName(serverName, leaf + suffix);
|
|
168
210
|
used.add(name);
|
|
169
211
|
tools.push({
|
|
170
212
|
name,
|
|
@@ -238,6 +280,7 @@ export function getMcpServerStatus(scopeId = DEFAULT_MCP_SCOPE_ID) {
|
|
|
238
280
|
),
|
|
239
281
|
}
|
|
240
282
|
: {}),
|
|
283
|
+
warnings: Array.isArray(server.warnings) ? server.warnings : [],
|
|
241
284
|
toolCount: Array.isArray(server.tools) ? server.tools.length : 0,
|
|
242
285
|
tools: (server.tools || []).map((tool) => ({
|
|
243
286
|
name: tool.name,
|
|
@@ -312,7 +355,7 @@ export function getMcpServerInstructionsMap(scopeId = DEFAULT_MCP_SCOPE_ID) {
|
|
|
312
355
|
// shutdown generation captured BEFORE the caller queued/closed anything: a
|
|
313
356
|
// disconnectAll() landing during the close (or the connect) bumps it, and the
|
|
314
357
|
// replacement must then never register (no resurrection after shutdown).
|
|
315
|
-
async function replaceServerConnection(scopeId, name, current, cfg, genAtStart) {
|
|
358
|
+
async function replaceServerConnection(scopeId, name, current, cfg, genAtStart, options = {}) {
|
|
316
359
|
const registryKey = mcpServerRegistryKey(scopeId, name);
|
|
317
360
|
if (current) {
|
|
318
361
|
await _closeServer(current);
|
|
@@ -325,10 +368,12 @@ async function replaceServerConnection(scopeId, name, current, cfg, genAtStart)
|
|
|
325
368
|
try {
|
|
326
369
|
// connectServer re-checks the same generation after loading the SDK and
|
|
327
370
|
// again right before registering, so a shutdown mid-handshake also aborts.
|
|
328
|
-
await connectServer(name, cfg, scopeId, genAtStart);
|
|
371
|
+
await connectServer(name, cfg, scopeId, genAtStart, options);
|
|
329
372
|
} catch (reason) {
|
|
330
373
|
const rawMessage = reason instanceof Error ? reason.message : String(reason);
|
|
331
|
-
|
|
374
|
+
const error = new Error(scrubMcpConnectionMessage(rawMessage, cfg));
|
|
375
|
+
if (reason?.authRequired) error.authRequired = true;
|
|
376
|
+
throw error;
|
|
332
377
|
}
|
|
333
378
|
}
|
|
334
379
|
|
|
@@ -338,6 +383,7 @@ async function replaceServerConnection(scopeId, name, current, cfg, genAtStart)
|
|
|
338
383
|
// connect per server is ever running (no doubly registered transports).
|
|
339
384
|
async function runExclusiveServerOp(registryKey, task) {
|
|
340
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.
|
|
341
387
|
await pending.catch(() => {});
|
|
342
388
|
}
|
|
343
389
|
return reconnects.run(registryKey, task);
|
|
@@ -378,7 +424,7 @@ async function reconnectMcpServer(scopeId, serverName, failedServer) {
|
|
|
378
424
|
* rejects (credential-scrubbed message) when the connect fails, leaving the
|
|
379
425
|
* server unregistered.
|
|
380
426
|
*/
|
|
381
|
-
export async function reconnectSingleMcpServer(scopeId, name, cfg = undefined) {
|
|
427
|
+
export async function reconnectSingleMcpServer(scopeId, name, cfg = undefined, options = {}) {
|
|
382
428
|
const normalized = normalizeMcpScopeId(scopeId);
|
|
383
429
|
_knownMcpScopes.add(normalized);
|
|
384
430
|
const registryKey = mcpServerRegistryKey(normalized, name);
|
|
@@ -397,7 +443,7 @@ export async function reconnectSingleMcpServer(scopeId, name, cfg = undefined) {
|
|
|
397
443
|
closeCallAdmission(normalized, name, `MCP ${name} disabled`);
|
|
398
444
|
return false;
|
|
399
445
|
}
|
|
400
|
-
await replaceServerConnection(normalized, name, current, nextCfg, genAtStart);
|
|
446
|
+
await replaceServerConnection(normalized, name, current, nextCfg, genAtStart, options);
|
|
401
447
|
return true;
|
|
402
448
|
});
|
|
403
449
|
}
|
|
@@ -409,106 +455,112 @@ export async function reconnectSingleMcpServer(scopeId, name, cfg = undefined) {
|
|
|
409
455
|
export async function executeMcpTool(name, args, options = {}) {
|
|
410
456
|
const match = name.match(MCP_TOOL_NAME_RE);
|
|
411
457
|
if (!match) throw new Error(`Not an MCP tool name: ${name}`);
|
|
412
|
-
const [, serverName, toolName] = match;
|
|
413
458
|
const scopeId = normalizeMcpScopeId(options);
|
|
414
|
-
const
|
|
415
|
-
if (!
|
|
416
|
-
const
|
|
459
|
+
const target = resolveMcpToolTarget(scopeId, name);
|
|
460
|
+
if (!target) throw new Error(`MCP server "${match[1]}" not connected`);
|
|
461
|
+
const { server, definition } = target;
|
|
462
|
+
const serverName = server.name;
|
|
463
|
+
const toolName = definition?.mcpToolName || match[2];
|
|
417
464
|
const callSignal = options?.signal || null;
|
|
418
465
|
const dispatch = (target) =>
|
|
419
466
|
definition?.mcpOperation
|
|
420
467
|
? _callMcpFeatureWithTimeout(target, definition.mcpOperation, args, callSignal)
|
|
421
468
|
: _callToolWithTimeout(target, toolName, args, callSignal);
|
|
469
|
+
let retryServer = null;
|
|
470
|
+
const secrets = () => oauthSecrets(server.transport, retryServer?.transport);
|
|
422
471
|
const gate = callAdmissionFor(server);
|
|
423
|
-
return gate
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
throw mcpAbortError(
|
|
430
|
-
callSignal,
|
|
431
|
-
`MCP tool call aborted before dispatch (server="${serverName}", tool="${toolName}")`
|
|
432
|
-
);
|
|
433
|
-
}
|
|
434
|
-
let result;
|
|
435
|
-
try {
|
|
436
|
-
result = await dispatch(server);
|
|
437
|
-
} catch (firstErr) {
|
|
438
|
-
const firstMsg = firstErr instanceof Error ? firstErr.message : String(firstErr);
|
|
439
|
-
if (isMcpCallAbortError(firstErr) || callSignal?.aborted) {
|
|
440
|
-
// A cancelled call is not a transport failure: reconnecting and
|
|
441
|
-
// replaying it would duplicate the side effect the caller just
|
|
442
|
-
// walked away from.
|
|
443
|
-
mcpLog(`[mcp-client] Tool call aborted by caller for "${serverName}/${toolName}".\n`);
|
|
444
|
-
throw firstErr;
|
|
445
|
-
}
|
|
446
|
-
if (isMcpToolCallTimeoutError(firstErr)) {
|
|
447
|
-
mcpLog(`[mcp-client] Tool call timed out; skipping reconnect retry for "${serverName}/${toolName}".\n`);
|
|
448
|
-
throw firstErr;
|
|
449
|
-
}
|
|
450
|
-
const serverDead = server.liveness?.alive === false;
|
|
451
|
-
if (!serverDead && isMcpServerResponseError(firstErr)) {
|
|
452
|
-
// The server answered (JSON-RPC error): it is alive — nothing to
|
|
453
|
-
// reconnect, and the call already ran its course.
|
|
454
|
-
throw firstErr;
|
|
455
|
-
}
|
|
456
|
-
// Only a failure that proves the request never left this process
|
|
457
|
-
// may be replayed; anything else may already have run server-side.
|
|
458
|
-
const undelivered = isMcpUndeliveredError(firstErr);
|
|
459
|
-
const deadTail = serverDead ? server.stderrTail?.() : '';
|
|
460
|
-
mcpLog(`[mcp-client] Tool call failed, attempting shared reconnect...\n`);
|
|
461
|
-
let retryServer;
|
|
462
|
-
try {
|
|
463
|
-
// The reconnect is SHARED (singleflight): the race only stops
|
|
464
|
-
// THIS caller from waiting on it — the reconnect keeps running
|
|
465
|
-
// for its other waiters. Without this, an abort mid-reconnect
|
|
466
|
-
// could not settle the call and the admission slot stayed held
|
|
467
|
-
// until the reconnect finished, or forever if it hung.
|
|
468
|
-
retryServer = await raceMcpAbort(
|
|
469
|
-
reconnectMcpServer(scopeId, serverName, server),
|
|
470
|
-
callSignal,
|
|
471
|
-
`MCP tool call aborted during reconnect (server="${serverName}", tool="${toolName}")`
|
|
472
|
-
);
|
|
473
|
-
} catch (reconnectErr) {
|
|
474
|
-
if (isMcpCallAbortError(reconnectErr) || callSignal?.aborted) throw reconnectErr;
|
|
475
|
-
const reconnectMsg = reconnectErr instanceof Error ? reconnectErr.message : String(reconnectErr);
|
|
476
|
-
throw new Error(
|
|
477
|
-
appendMcpStderrTail(`Tool call failed: ${firstMsg}; reconnect also failed: ${reconnectMsg}`, deadTail)
|
|
478
|
-
);
|
|
479
|
-
}
|
|
480
|
-
if (!undelivered) {
|
|
481
|
-
throw new Error(
|
|
482
|
-
appendMcpStderrTail(
|
|
483
|
-
`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`,
|
|
484
|
-
deadTail
|
|
485
|
-
)
|
|
486
|
-
);
|
|
487
|
-
}
|
|
488
|
-
// A reconnect that outlived the caller's abort must not dispatch the
|
|
489
|
-
// 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.
|
|
490
478
|
if (callSignal?.aborted) {
|
|
491
479
|
throw mcpAbortError(
|
|
492
480
|
callSignal,
|
|
493
|
-
`MCP tool call aborted
|
|
481
|
+
`MCP tool call aborted before dispatch (server="${serverName}", tool="${toolName}")`
|
|
494
482
|
);
|
|
495
483
|
}
|
|
484
|
+
let result;
|
|
496
485
|
try {
|
|
497
|
-
result = await dispatch(
|
|
498
|
-
} catch (
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
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
|
+
}
|
|
502
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,
|
|
503
559
|
}
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
}
|
|
508
|
-
{
|
|
509
|
-
signal: callSignal,
|
|
510
|
-
}
|
|
511
|
-
);
|
|
560
|
+
)
|
|
561
|
+
.catch((error) => {
|
|
562
|
+
throw scrubMcpOAuthError(error, server.cfg, secrets());
|
|
563
|
+
});
|
|
512
564
|
}
|
|
513
565
|
|
|
514
566
|
const MCP_FEATURE_REQUESTS = {
|
|
@@ -884,6 +936,7 @@ export function raceMcpAbort(promise, signal, message) {
|
|
|
884
936
|
if (!signal) return promise;
|
|
885
937
|
allowConcurrentAbortListeners(signal);
|
|
886
938
|
if (signal.aborted) {
|
|
939
|
+
// The abort wins; the abandoned promise's late rejection must not go unhandled.
|
|
887
940
|
void Promise.resolve(promise).catch(() => {});
|
|
888
941
|
return Promise.reject(mcpAbortError(signal, message));
|
|
889
942
|
}
|
|
@@ -1022,12 +1075,7 @@ export function isMcpTool(name) {
|
|
|
1022
1075
|
}
|
|
1023
1076
|
/** True when the prefixed name exists on a connected MCP server. */
|
|
1024
1077
|
export function isRegisteredMcpTool(name, scopeId = DEFAULT_MCP_SCOPE_ID) {
|
|
1025
|
-
|
|
1026
|
-
if (!match) return false;
|
|
1027
|
-
const [, serverName] = match;
|
|
1028
|
-
const server = scopedServer(scopeId, serverName);
|
|
1029
|
-
if (!server || !Array.isArray(server.tools)) return false;
|
|
1030
|
-
return server.tools.some((t) => t?.name === name);
|
|
1078
|
+
return Boolean(resolveMcpToolTarget(scopeId, name)?.definition);
|
|
1031
1079
|
}
|
|
1032
1080
|
/**
|
|
1033
1081
|
* Check whether the inputSchema for an MCP tool declares the given top-level
|
|
@@ -1040,9 +1088,7 @@ export function mcpToolHasField(name, field, scopeId = DEFAULT_MCP_SCOPE_ID) {
|
|
|
1040
1088
|
const memoKey = `${normalizedScopeId}|${name}|${field}`;
|
|
1041
1089
|
const memoized = _mcpToolFieldMemo.get(memoKey);
|
|
1042
1090
|
if (memoized !== undefined) return memoized;
|
|
1043
|
-
const
|
|
1044
|
-
const server = match ? scopedServer(normalizedScopeId, match[1]) : null;
|
|
1045
|
-
const tool = server ? server.tools.find((t) => t.name === name) : null;
|
|
1091
|
+
const tool = resolveMcpToolTarget(normalizedScopeId, name)?.definition;
|
|
1046
1092
|
const props = tool?.inputSchema?.properties;
|
|
1047
1093
|
const result = Boolean(props && Object.hasOwn(props, field));
|
|
1048
1094
|
_mcpToolFieldMemo.set(memoKey, result);
|
|
@@ -1078,6 +1124,7 @@ export async function disconnectAll(options = undefined) {
|
|
|
1078
1124
|
/* ignore */
|
|
1079
1125
|
}
|
|
1080
1126
|
try {
|
|
1127
|
+
entry.transport.oauth?.close();
|
|
1081
1128
|
void entry.client.close().catch(() => {
|
|
1082
1129
|
/* ignore */
|
|
1083
1130
|
});
|
|
@@ -1106,6 +1153,9 @@ export async function disconnectAll(options = undefined) {
|
|
|
1106
1153
|
export async function disconnectMcpServer(name, options = {}) {
|
|
1107
1154
|
const scopeId = normalizeMcpScopeId(options);
|
|
1108
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);
|
|
1109
1159
|
return runExclusiveServerOp(registryKey, async () => {
|
|
1110
1160
|
const server = servers.get(registryKey);
|
|
1111
1161
|
if (!server) return false;
|
|
@@ -1120,6 +1170,73 @@ export async function disconnectMcpServer(name, options = {}) {
|
|
|
1120
1170
|
return true;
|
|
1121
1171
|
});
|
|
1122
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
|
+
}
|
|
1123
1240
|
/**
|
|
1124
1241
|
* Close a single server: for stdio transports first shut down the full child
|
|
1125
1242
|
* process tree (close stdin -> grace -> tree kill) so wrapper chains such as
|
|
@@ -1131,6 +1248,7 @@ async function _closeServer(server) {
|
|
|
1131
1248
|
// A deliberate close is not a crash: suppress the unexpected-close report.
|
|
1132
1249
|
if (server?.liveness) server.liveness.closing = true;
|
|
1133
1250
|
const transport = server?.transport;
|
|
1251
|
+
transport?.oauth?.close();
|
|
1134
1252
|
// Live stdio transports expose the spawned ChildProcess on _process.
|
|
1135
1253
|
if (transport?._process) {
|
|
1136
1254
|
try {
|
|
@@ -1153,7 +1271,8 @@ async function connectServer(
|
|
|
1153
1271
|
name,
|
|
1154
1272
|
cfg,
|
|
1155
1273
|
scopeId = DEFAULT_MCP_SCOPE_ID,
|
|
1156
|
-
genAtStart = currentConnectAbortGeneration(scopeId)
|
|
1274
|
+
genAtStart = currentConnectAbortGeneration(scopeId),
|
|
1275
|
+
options = {}
|
|
1157
1276
|
) {
|
|
1158
1277
|
scopeId = normalizeMcpScopeId(scopeId);
|
|
1159
1278
|
_knownMcpScopes.add(scopeId);
|
|
@@ -1163,9 +1282,21 @@ async function connectServer(
|
|
|
1163
1282
|
// abort before creating a transport/child at all.
|
|
1164
1283
|
throw new Error(`MCP server "${name}" connect aborted by shutdown`);
|
|
1165
1284
|
}
|
|
1166
|
-
const client = createMcpClient(sdk, name, cfg);
|
|
1167
|
-
const { transport, autoDetectAdvert, stderrTail } = createMcpTransport({
|
|
1168
|
-
|
|
1285
|
+
const client = createMcpClient(sdk, name, cfg, scopeId);
|
|
1286
|
+
const { transport, autoDetectAdvert, stderrTail, warnings } = createMcpTransport({
|
|
1287
|
+
name,
|
|
1288
|
+
cfg,
|
|
1289
|
+
sdk,
|
|
1290
|
+
log: mcpLog,
|
|
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
|
+
},
|
|
1298
|
+
});
|
|
1299
|
+
const liveness = trackMcpLiveness(client, name, cfg, stderrTail, transport);
|
|
1169
1300
|
const pending = { scopeId, name, client, transport };
|
|
1170
1301
|
_pendingConnects.add(pending);
|
|
1171
1302
|
try {
|
|
@@ -1195,7 +1326,7 @@ async function connectServer(
|
|
|
1195
1326
|
}
|
|
1196
1327
|
throw new Error(`MCP server "${name}" connect aborted by shutdown`);
|
|
1197
1328
|
}
|
|
1198
|
-
const tools = mcpServerTools(name, capabilities, toolsResult.tools);
|
|
1329
|
+
const tools = mcpServerTools(name, capabilities, toolsResult.tools, cfg);
|
|
1199
1330
|
const registryKey = mcpServerRegistryKey(scopeId, name);
|
|
1200
1331
|
servers.set(registryKey, {
|
|
1201
1332
|
scopeId,
|
|
@@ -1209,6 +1340,7 @@ async function connectServer(
|
|
|
1209
1340
|
capabilities,
|
|
1210
1341
|
liveness,
|
|
1211
1342
|
stderrTail,
|
|
1343
|
+
warnings: warnings || [],
|
|
1212
1344
|
generation: genAtStart,
|
|
1213
1345
|
});
|
|
1214
1346
|
if (capabilities?.tools?.listChanged && sdk.ToolListChangedNotificationSchema) {
|
|
@@ -1218,6 +1350,22 @@ async function connectServer(
|
|
|
1218
1350
|
}
|
|
1219
1351
|
_invalidateMcpToolFieldMemo();
|
|
1220
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;
|
|
1221
1369
|
} finally {
|
|
1222
1370
|
_pendingConnects.delete(pending);
|
|
1223
1371
|
}
|
|
@@ -1230,7 +1378,7 @@ async function connectServer(
|
|
|
1230
1378
|
* records the last error (parse/send hiccups do not mean the server died).
|
|
1231
1379
|
* Handlers already installed on the client are chained, not replaced.
|
|
1232
1380
|
*/
|
|
1233
|
-
function trackMcpLiveness(client, name, cfg, stderrTail) {
|
|
1381
|
+
function trackMcpLiveness(client, name, cfg, stderrTail, transport) {
|
|
1234
1382
|
const liveness = { alive: true, closing: false, closedAt: 0, lastError: '' };
|
|
1235
1383
|
const priorClose = client.onclose;
|
|
1236
1384
|
const priorError = client.onerror;
|
|
@@ -1244,22 +1392,35 @@ function trackMcpLiveness(client, name, cfg, stderrTail) {
|
|
|
1244
1392
|
if (typeof priorClose === 'function') priorClose.apply(client, args);
|
|
1245
1393
|
};
|
|
1246
1394
|
client.onerror = (error, ...rest) => {
|
|
1247
|
-
liveness.lastError = scrubMcpConnectionMessage(
|
|
1395
|
+
liveness.lastError = scrubMcpConnectionMessage(
|
|
1396
|
+
error instanceof Error ? error.message : String(error),
|
|
1397
|
+
cfg,
|
|
1398
|
+
oauthSecrets(transport)
|
|
1399
|
+
);
|
|
1248
1400
|
if (typeof priorError === 'function') priorError.call(client, error, ...rest);
|
|
1249
1401
|
};
|
|
1250
1402
|
return liveness;
|
|
1251
1403
|
}
|
|
1252
1404
|
|
|
1253
|
-
/** The SDK client for one server, advertising the project root when known.
|
|
1254
|
-
|
|
1255
|
-
|
|
1405
|
+
/** The SDK client for one server, advertising the project root when known.
|
|
1406
|
+
* A config marked `_mixdogRootsFollowScope` advertises its scope's current
|
|
1407
|
+
* root (setMcpScopeRoot) and is told when it changes. */
|
|
1408
|
+
function createMcpClient(sdk, name, cfg, scopeId = DEFAULT_MCP_SCOPE_ID) {
|
|
1409
|
+
const followScope = cfg?._mixdogRootsFollowScope === true;
|
|
1410
|
+
const fixedRoot = String(cfg?._mixdogProjectRoot || cfg?.cwd || '').trim();
|
|
1411
|
+
const currentRoot = () => (followScope ? _scopeRoots.get(normalizeMcpScopeId(scopeId)) || fixedRoot : fixedRoot);
|
|
1256
1412
|
const client = new sdk.Client(
|
|
1257
1413
|
{ name: `mixdog-agent/${name}`, version: '1.0.0' },
|
|
1258
|
-
{
|
|
1414
|
+
{
|
|
1415
|
+
capabilities: followScope || fixedRoot ? { roots: { listChanged: followScope } } : {},
|
|
1416
|
+
jsonSchemaValidator: createLazyOutputValidator(sdk.AjvJsonSchemaValidator),
|
|
1417
|
+
}
|
|
1259
1418
|
);
|
|
1260
|
-
if (
|
|
1419
|
+
if ((followScope || fixedRoot) && sdk.ListRootsRequestSchema) {
|
|
1261
1420
|
client.setRequestHandler(sdk.ListRootsRequestSchema, async () => {
|
|
1262
|
-
const
|
|
1421
|
+
const root = currentRoot();
|
|
1422
|
+
if (!root) return { roots: [] };
|
|
1423
|
+
const rootPath = resolve(root);
|
|
1263
1424
|
return {
|
|
1264
1425
|
roots: [
|
|
1265
1426
|
{
|
|
@@ -1273,15 +1434,63 @@ function createMcpClient(sdk, name, cfg) {
|
|
|
1273
1434
|
return client;
|
|
1274
1435
|
}
|
|
1275
1436
|
|
|
1276
|
-
/**
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1437
|
+
/**
|
|
1438
|
+
* Set the root advertised by `scopeId`'s scope-following servers (plugin
|
|
1439
|
+
* servers, whose roots track the session cwd) and notify the connected ones
|
|
1440
|
+
* when it changed. No reconnect: the connection and its tools stay as they are.
|
|
1441
|
+
*/
|
|
1442
|
+
export function setMcpScopeRoot(scopeId, root) {
|
|
1443
|
+
const normalized = normalizeMcpScopeId(scopeId);
|
|
1444
|
+
const next = String(root || '').trim();
|
|
1445
|
+
if ((_scopeRoots.get(normalized) || '') === next) return;
|
|
1446
|
+
if (next) _scopeRoots.set(normalized, next);
|
|
1447
|
+
else _scopeRoots.delete(normalized);
|
|
1448
|
+
for (const [, server] of scopedServerEntries(normalized)) {
|
|
1449
|
+
if (server.cfg?._mixdogRootsFollowScope !== true) continue;
|
|
1450
|
+
Promise.resolve()
|
|
1451
|
+
.then(() => server.client?.sendRootsListChanged?.())
|
|
1452
|
+
// A server that rejects the roots notification keeps its previous roots.
|
|
1453
|
+
.catch(() => {});
|
|
1454
|
+
}
|
|
1455
|
+
}
|
|
1456
|
+
|
|
1457
|
+
/** The connected server and tool definition a prefixed tool name refers to.
|
|
1458
|
+
* The name's server part finds it directly; a name shortened to fit provider
|
|
1459
|
+
* limits is found by scanning this scope's servers. A known server without
|
|
1460
|
+
* the tool still resolves (definition null) so the call reaches the server. */
|
|
1461
|
+
function resolveMcpToolTarget(scopeId, name) {
|
|
1462
|
+
const match = MCP_TOOL_NAME_RE.exec(String(name || ''));
|
|
1463
|
+
if (!match) return null;
|
|
1464
|
+
const direct = scopedServer(scopeId, match[1]);
|
|
1465
|
+
const directTool = direct?.tools?.find((tool) => tool.name === name);
|
|
1466
|
+
if (directTool) return { server: direct, definition: directTool };
|
|
1467
|
+
for (const [, server] of scopedServerEntries(scopeId)) {
|
|
1468
|
+
const definition = server.tools?.find((tool) => tool.name === name);
|
|
1469
|
+
if (definition) return { server, definition };
|
|
1470
|
+
}
|
|
1471
|
+
return direct ? { server: direct, definition: null } : null;
|
|
1472
|
+
}
|
|
1473
|
+
|
|
1474
|
+
/** Protocol tools under the `mcp__<server>__` prefix plus the feature tools.
|
|
1475
|
+
* Names that would exceed provider limits are shortened deterministically
|
|
1476
|
+
* (`providerSafeMcpToolName`); a tool whose name was shortened carries the
|
|
1477
|
+
* server's own tool name as `mcpToolName` and its server as `mcpServerName`.
|
|
1478
|
+
* A plugin-declared server's tools carry its owner as `mcpPluginId`, so
|
|
1479
|
+
* scope filtering never has to infer ownership from the server name. */
|
|
1480
|
+
function mcpServerTools(name, capabilities, rawTools, cfg) {
|
|
1481
|
+
const protocolTools = (rawTools || []).map((t) => {
|
|
1482
|
+
const safeName = providerSafeMcpToolName(name, t.name);
|
|
1483
|
+
return {
|
|
1484
|
+
name: safeName,
|
|
1485
|
+
...(safeName === `mcp__${name}__${t.name}` ? {} : { mcpToolName: t.name, mcpServerName: name }),
|
|
1486
|
+
description: t.description || '',
|
|
1487
|
+
inputSchema: t.inputSchema || { type: 'object', properties: {} },
|
|
1488
|
+
...(t.annotations && typeof t.annotations === 'object' ? { annotations: t.annotations } : {}),
|
|
1489
|
+
};
|
|
1490
|
+
});
|
|
1491
|
+
const tools = [...protocolTools, ...mcpFeatureTools(name, capabilities, protocolTools)];
|
|
1492
|
+
const pluginId = String(cfg?._mixdogPlugin?.id || '').trim();
|
|
1493
|
+
return pluginId ? tools.map((tool) => ({ ...tool, mcpPluginId: pluginId })) : tools;
|
|
1285
1494
|
}
|
|
1286
1495
|
|
|
1287
1496
|
// tools/list_changed refreshes run one at a time per server connection, so an
|
|
@@ -1304,11 +1513,11 @@ async function runToolRefresh(registryKey, live, name, cfg, capabilities) {
|
|
|
1304
1513
|
throw new Error('ListTools returned invalid shape: missing or non-array tools field');
|
|
1305
1514
|
}
|
|
1306
1515
|
if (servers.get(registryKey) !== live) return;
|
|
1307
|
-
live.tools = mcpServerTools(name, capabilities, refreshed.tools);
|
|
1516
|
+
live.tools = mcpServerTools(name, capabilities, refreshed.tools, cfg);
|
|
1308
1517
|
_invalidateMcpToolFieldMemo();
|
|
1309
1518
|
} catch (error) {
|
|
1310
1519
|
mcpLog(
|
|
1311
|
-
`[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`
|
|
1312
1521
|
);
|
|
1313
1522
|
}
|
|
1314
1523
|
}
|
|
@@ -1317,25 +1526,34 @@ async function runToolRefresh(registryKey, live, name, cfg, capabilities) {
|
|
|
1317
1526
|
export function _registerMcpServerForTest(scopeId, name, rawTools = [], options = {}) {
|
|
1318
1527
|
const normalizedScopeId = normalizeMcpScopeId(scopeId);
|
|
1319
1528
|
const registryKey = mcpServerRegistryKey(normalizedScopeId, name);
|
|
1320
|
-
const tools = rawTools.map((tool) =>
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1529
|
+
const tools = rawTools.map((tool) => {
|
|
1530
|
+
const prefixed = String(tool?.name || '').startsWith('mcp__');
|
|
1531
|
+
const leaf = String(tool?.name || '');
|
|
1532
|
+
const safeName = prefixed ? leaf : providerSafeMcpToolName(name, leaf);
|
|
1533
|
+
const shortened = !prefixed && safeName !== `mcp__${name}__${leaf}`;
|
|
1534
|
+
return {
|
|
1535
|
+
...tool,
|
|
1536
|
+
name: safeName,
|
|
1537
|
+
...(shortened ? { mcpToolName: leaf, mcpServerName: name } : {}),
|
|
1538
|
+
inputSchema: tool?.inputSchema || { type: 'object', properties: {} },
|
|
1539
|
+
};
|
|
1540
|
+
});
|
|
1327
1541
|
const capabilities = options.capabilities || {};
|
|
1542
|
+
const pluginId = String(options.cfg?._mixdogPlugin?.id || '').trim();
|
|
1328
1543
|
const server = {
|
|
1329
1544
|
scopeId: normalizedScopeId,
|
|
1330
1545
|
registryKey,
|
|
1331
1546
|
name,
|
|
1332
|
-
tools: [...tools, ...mcpFeatureTools(name, capabilities, tools)]
|
|
1547
|
+
tools: [...tools, ...mcpFeatureTools(name, capabilities, tools)].map((tool) =>
|
|
1548
|
+
pluginId ? { ...tool, mcpPluginId: pluginId } : tool
|
|
1549
|
+
),
|
|
1550
|
+
warnings: options.warnings || [],
|
|
1333
1551
|
cfg: options.cfg || {},
|
|
1334
1552
|
instructions: options.instructions || '',
|
|
1335
1553
|
capabilities,
|
|
1336
1554
|
liveness: { alive: true, closing: false, closedAt: 0, lastError: '' },
|
|
1337
1555
|
stderrTail: typeof options.stderrTail === 'function' ? options.stderrTail : () => '',
|
|
1338
|
-
transport: null,
|
|
1556
|
+
transport: options.transport || null,
|
|
1339
1557
|
client: {
|
|
1340
1558
|
callTool:
|
|
1341
1559
|
typeof options.callTool === 'function'
|