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
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// The MCP SDK Client compiles every tool's outputSchema with Ajv inside
|
|
2
|
+
// listTools(), synchronously: a server listing ~180 tools held the daemon's
|
|
3
|
+
// event loop for 1-2 s on every connect. This validator compiles each schema
|
|
4
|
+
// on the tool's first structured result instead, and creates the SDK's Ajv
|
|
5
|
+
// validator (same options and error text) on first use too.
|
|
6
|
+
// A schema declaring an `$id` anywhere registers it in the client's Ajv, and a
|
|
7
|
+
// schema referencing anything outside itself resolves against what is
|
|
8
|
+
// registered by then (failing the listing when it is not); both still compile
|
|
9
|
+
// when listed, in listing order, exactly as the SDK does.
|
|
10
|
+
const LISTING_ORDER_SCHEMA = /"\$id"|"\$ref":"(?!#)/;
|
|
11
|
+
|
|
12
|
+
export function createLazyOutputValidator(AjvJsonSchemaValidator) {
|
|
13
|
+
let provider = null;
|
|
14
|
+
const shared = () => (provider ??= new AjvJsonSchemaValidator());
|
|
15
|
+
return {
|
|
16
|
+
getValidator(schema) {
|
|
17
|
+
if (LISTING_ORDER_SCHEMA.test(JSON.stringify(schema))) return shared().getValidator(schema);
|
|
18
|
+
let validate = null;
|
|
19
|
+
return (input) => {
|
|
20
|
+
validate ??= shared().getValidator(schema);
|
|
21
|
+
return validate(input);
|
|
22
|
+
};
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { timingSafeEqual } from 'node:crypto';
|
|
2
|
+
import { OAUTH_PAGE_CONTENT_TYPE, oauthErrorHtml, oauthSuccessHtml } from '../providers/lib/oauth-page.mjs';
|
|
3
|
+
|
|
4
|
+
/** Upper bound of a pasted redirect URL (`completeMcpLogin`). */
|
|
5
|
+
export const MCP_OAUTH_CALLBACK_MAX_BYTES = 64 * 1024;
|
|
6
|
+
// Fixed messages: a pasted URL carries the authorization code and is never
|
|
7
|
+
// echoed into an error, a log or a status.
|
|
8
|
+
const INVALID_CALLBACK_PASTE =
|
|
9
|
+
'The pasted sign-in URL is invalid. Paste the full address the browser was redirected to after signing in.';
|
|
10
|
+
export const MCP_OAUTH_CALLBACK_NOT_PENDING = 'MCP OAuth sign-in is not waiting for a callback';
|
|
11
|
+
const sameState = (supplied, expected) => {
|
|
12
|
+
const a = Buffer.from(supplied || '');
|
|
13
|
+
const b = Buffer.from(expected || '');
|
|
14
|
+
return b.length > 0 && a.length === b.length && timingSafeEqual(a, b);
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* The outcome of a redirect URL pasted from the browser for a pending login
|
|
19
|
+
* whose redirect URI is `redirectUrl` and state `state`: `{ code }`, or
|
|
20
|
+
* `{ denied: true }` for an authorization error. Throws a fixed message (never
|
|
21
|
+
* the pasted text) for anything that is not exactly that login's callback.
|
|
22
|
+
*/
|
|
23
|
+
export function parsePastedMcpOAuthCallback(pasted, { redirectUrl, state }) {
|
|
24
|
+
const invalid = () => new Error(INVALID_CALLBACK_PASTE);
|
|
25
|
+
if (typeof pasted !== 'string' || Buffer.byteLength(pasted) > MCP_OAUTH_CALLBACK_MAX_BYTES) throw invalid();
|
|
26
|
+
const raw = pasted.trim();
|
|
27
|
+
let target;
|
|
28
|
+
let expected;
|
|
29
|
+
try {
|
|
30
|
+
target = new URL(raw);
|
|
31
|
+
expected = new URL(redirectUrl);
|
|
32
|
+
} catch {
|
|
33
|
+
throw invalid();
|
|
34
|
+
}
|
|
35
|
+
if (
|
|
36
|
+
raw.includes('#') ||
|
|
37
|
+
target.username ||
|
|
38
|
+
target.password ||
|
|
39
|
+
target.origin !== expected.origin ||
|
|
40
|
+
target.pathname !== expected.pathname
|
|
41
|
+
)
|
|
42
|
+
throw invalid();
|
|
43
|
+
const seen = new Set();
|
|
44
|
+
for (const [key, value] of target.searchParams) {
|
|
45
|
+
if (seen.has(key)) throw invalid();
|
|
46
|
+
seen.add(key);
|
|
47
|
+
// The redirect URI's own parameters come back unchanged; any others an
|
|
48
|
+
// authorization server adds (e.g. Google's scope/authuser/prompt) are
|
|
49
|
+
// ignored, as in Codex's parse_callback_url.
|
|
50
|
+
if (expected.searchParams.has(key) && expected.searchParams.get(key) !== value) throw invalid();
|
|
51
|
+
}
|
|
52
|
+
for (const key of expected.searchParams.keys()) if (!seen.has(key)) throw invalid();
|
|
53
|
+
if (!sameState(target.searchParams.get('state'), state)) throw invalid();
|
|
54
|
+
if (seen.has('code') === seen.has('error')) throw invalid();
|
|
55
|
+
if (seen.has('error')) return { denied: true };
|
|
56
|
+
const code = target.searchParams.get('code');
|
|
57
|
+
if (!code) throw invalid();
|
|
58
|
+
return { code };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Answers a request to the loopback redirect URI: only a GET of the exact
|
|
62
|
+
// callback path and host, carrying exactly the pending state (compared in
|
|
63
|
+
// constant time) and either one code or an error, finishes the login.
|
|
64
|
+
export function answerOAuthCallback(req, res, { redirectUrl, state, finish, serverName }) {
|
|
65
|
+
const page = (status, html) => {
|
|
66
|
+
res.writeHead(status, {
|
|
67
|
+
'Content-Type': OAUTH_PAGE_CONTENT_TYPE,
|
|
68
|
+
'Cache-Control': 'no-store',
|
|
69
|
+
'Referrer-Policy': 'no-referrer',
|
|
70
|
+
});
|
|
71
|
+
res.end(html);
|
|
72
|
+
};
|
|
73
|
+
const invalid = () =>
|
|
74
|
+
page(400, oauthErrorHtml('This sign-in link is invalid or has expired.', 'Start the sign-in again from Mixdog.'));
|
|
75
|
+
let target;
|
|
76
|
+
try {
|
|
77
|
+
target = new URL(req.url || '/', redirectUrl);
|
|
78
|
+
} catch {
|
|
79
|
+
invalid();
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
// `finish` is absent once the login was answered (e.g. by a pasted URL).
|
|
83
|
+
const valid =
|
|
84
|
+
typeof finish === 'function' &&
|
|
85
|
+
req.method === 'GET' &&
|
|
86
|
+
req.headers.host === new URL(redirectUrl).host &&
|
|
87
|
+
target.pathname === '/oauth/callback' &&
|
|
88
|
+
target.searchParams.getAll('state').length === 1 &&
|
|
89
|
+
sameState(target.searchParams.get('state'), state);
|
|
90
|
+
if (!valid || (!target.searchParams.has('error') && target.searchParams.getAll('code').length !== 1)) {
|
|
91
|
+
invalid();
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
const server = serverName || 'the MCP server';
|
|
95
|
+
if (target.searchParams.has('error')) {
|
|
96
|
+
// The authorization server's error text is never reflected.
|
|
97
|
+
page(400, oauthErrorHtml(`Sign-in to ${server} was not completed.`, 'Start the sign-in again from Mixdog.'));
|
|
98
|
+
finish(undefined);
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
page(200, oauthSuccessHtml(server));
|
|
102
|
+
finish(target.searchParams.get('code'));
|
|
103
|
+
}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { randomBytes } from 'node:crypto';
|
|
2
|
+
import { readFile } from 'node:fs/promises';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { withFileLock, writeFileAtomicAsync } from '../../../shared/atomic-file.mjs';
|
|
5
|
+
import { resolvePluginData } from '../../../shared/plugin-paths.mjs';
|
|
6
|
+
import { reauthenticationRequired } from './oauth-shared.mjs';
|
|
7
|
+
|
|
8
|
+
// Same lease as the provider OAuth refresh (openai-oauth-tokens.mjs): a
|
|
9
|
+
// refresh is a network exchange plus keychain persistence. The lock file
|
|
10
|
+
// itself holds no credential (they live in the keychain).
|
|
11
|
+
const CREDENTIAL_LOCK_OPTIONS = Object.freeze({ timeoutMs: 120_000, staleMs: 120_000 });
|
|
12
|
+
|
|
13
|
+
export const credentialFile = (account, extension) => join(resolvePluginData(), 'mcp-oauth', `${account}.${extension}`);
|
|
14
|
+
|
|
15
|
+
// Serializes refreshes, logins and log-outs of one account across processes.
|
|
16
|
+
export function withCredentialLock(account, fn) {
|
|
17
|
+
return withFileLock(credentialFile(account, 'lock'), fn, CREDENTIAL_LOCK_OPTIONS);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// A non-secret marker of the stored entry's revision ('deleted' when there is
|
|
21
|
+
// none) and whether the entry holds tokens (a client registration alone does
|
|
22
|
+
// not sign in), written under the credential lock before every change of the
|
|
23
|
+
// entry. Every process compares it before sending a cached token, so a refresh
|
|
24
|
+
// or log-out elsewhere is observed without reading the keychain per request.
|
|
25
|
+
export const TOKENS_FLAG = '+tokens';
|
|
26
|
+
const markerOf = (revision, hasTokens) => {
|
|
27
|
+
if (revision === null) return 'deleted';
|
|
28
|
+
return hasTokens ? `${revision}${TOKENS_FLAG}` : revision;
|
|
29
|
+
};
|
|
30
|
+
export function writeRevisionMarker(account, revision, hasTokens = false) {
|
|
31
|
+
return writeFileAtomicAsync(credentialFile(account, 'rev'), markerOf(revision, hasTokens));
|
|
32
|
+
}
|
|
33
|
+
// Missing or unreadable (null) never matches: the caller re-reads the keychain.
|
|
34
|
+
function readRevisionMarker(account) {
|
|
35
|
+
return readFile(credentialFile(account, 'rev'), 'utf8').catch(() => null);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// The stored value as another process may have just written it, not this
|
|
39
|
+
// process's cached copy.
|
|
40
|
+
export function readStoredCredentials(credentialStore, account) {
|
|
41
|
+
credentialStore.invalidateSecretCache?.(account);
|
|
42
|
+
return credentialStore.getSecretAsync(account);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Every write stores a fresh random revision; null stands for no entry.
|
|
46
|
+
const revisionOf = (saved) => (saved ? (JSON.parse(saved).revision ?? '') : null);
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* One connection's view of its stored OAuth entry. `creds` holds the cached
|
|
50
|
+
* values: `observed` is the revision of the stored entry this process last
|
|
51
|
+
* read or wrote (null: there was none, undefined: unknown after a log-out
|
|
52
|
+
* here); `loaded` is the pending or completed first read.
|
|
53
|
+
*/
|
|
54
|
+
export function createCredentialCache({ url, credentialStore, account }) {
|
|
55
|
+
const creds = {
|
|
56
|
+
tokens: undefined,
|
|
57
|
+
clientInformation: undefined,
|
|
58
|
+
redirectUrl: undefined,
|
|
59
|
+
discovery: undefined,
|
|
60
|
+
observed: undefined,
|
|
61
|
+
loaded: undefined,
|
|
62
|
+
};
|
|
63
|
+
// Every token this connection held: an error or result echoing the token a
|
|
64
|
+
// request was sent with stays scrubbed after a refresh or log-out.
|
|
65
|
+
const held = new Set();
|
|
66
|
+
const hold = () => {
|
|
67
|
+
for (const token of [creds.tokens?.access_token, creds.tokens?.refresh_token]) if (token) held.add(token);
|
|
68
|
+
};
|
|
69
|
+
const adopt = (saved) => {
|
|
70
|
+
const value = saved ? JSON.parse(saved) : {};
|
|
71
|
+
if (saved && value.url !== url) throw reauthenticationRequired();
|
|
72
|
+
({
|
|
73
|
+
tokens: creds.tokens,
|
|
74
|
+
clientInformation: creds.clientInformation,
|
|
75
|
+
redirectUrl: creds.redirectUrl,
|
|
76
|
+
discovery: creds.discovery,
|
|
77
|
+
} = value);
|
|
78
|
+
creds.observed = revisionOf(saved);
|
|
79
|
+
hold();
|
|
80
|
+
};
|
|
81
|
+
// A failed read (locked keychain) is retried by the next request.
|
|
82
|
+
const ready = () =>
|
|
83
|
+
(creds.loaded ??= credentialStore
|
|
84
|
+
.getSecretAsync(account)
|
|
85
|
+
.then(adopt)
|
|
86
|
+
.catch((error) => {
|
|
87
|
+
creds.loaded = undefined;
|
|
88
|
+
throw error;
|
|
89
|
+
}));
|
|
90
|
+
// The only writer of the stored entry. Under the cross-process lock it
|
|
91
|
+
// changes the entry only while it still has the revision the caller observed
|
|
92
|
+
// (by default the one this process last read or wrote); otherwise it adopts
|
|
93
|
+
// the newer entry, or its deletion by a log-out, and changes nothing.
|
|
94
|
+
function update(change, { expected = creds.observed, remove = false } = {}) {
|
|
95
|
+
return withCredentialLock(account, async () => {
|
|
96
|
+
const saved = await readStoredCredentials(credentialStore, account);
|
|
97
|
+
if (revisionOf(saved) !== expected) {
|
|
98
|
+
adopt(saved);
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
change();
|
|
102
|
+
hold();
|
|
103
|
+
if (remove) {
|
|
104
|
+
await writeRevisionMarker(account, null);
|
|
105
|
+
if (saved) await credentialStore.deleteSecretAsync(account);
|
|
106
|
+
creds.observed = null;
|
|
107
|
+
} else {
|
|
108
|
+
const revision = randomBytes(16).toString('hex');
|
|
109
|
+
await writeRevisionMarker(account, revision, Boolean(creds.tokens));
|
|
110
|
+
const { tokens, clientInformation, redirectUrl, discovery } = creds;
|
|
111
|
+
await credentialStore.setSecretAsync(
|
|
112
|
+
account,
|
|
113
|
+
JSON.stringify({ url, revision, tokens, clientInformation, redirectUrl, discovery })
|
|
114
|
+
);
|
|
115
|
+
creds.observed = revision;
|
|
116
|
+
}
|
|
117
|
+
return true;
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
// Before a cached token is sent: adopt a change another process made (a
|
|
121
|
+
// refresh, a log-out) since this process last read or wrote the entry.
|
|
122
|
+
async function observeStoredRevision() {
|
|
123
|
+
if ((await readRevisionMarker(account)) === markerOf(creds.observed, Boolean(creds.tokens))) return;
|
|
124
|
+
await withCredentialLock(account, async () => {
|
|
125
|
+
adopt(await readStoredCredentials(credentialStore, account));
|
|
126
|
+
// Repair a missing or stale marker (an entry stored before markers or
|
|
127
|
+
// the tokens flag existed, an interrupted change) so the next request
|
|
128
|
+
// reads no keychain.
|
|
129
|
+
if ((await readRevisionMarker(account)) !== markerOf(creds.observed, Boolean(creds.tokens))) {
|
|
130
|
+
await writeRevisionMarker(account, creds.observed, Boolean(creds.tokens));
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
// Forget the in-memory credentials; the next request re-reads the keychain.
|
|
135
|
+
function clear() {
|
|
136
|
+
creds.tokens = creds.clientInformation = creds.redirectUrl = creds.discovery = undefined;
|
|
137
|
+
creds.loaded = creds.observed = undefined;
|
|
138
|
+
}
|
|
139
|
+
return {
|
|
140
|
+
creds,
|
|
141
|
+
held,
|
|
142
|
+
ready,
|
|
143
|
+
adopt,
|
|
144
|
+
update,
|
|
145
|
+
observeStoredRevision,
|
|
146
|
+
clear,
|
|
147
|
+
readStored: () => readStoredCredentials(credentialStore, account),
|
|
148
|
+
withLock: (fn) => withCredentialLock(account, fn),
|
|
149
|
+
};
|
|
150
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { createServer } from 'node:http';
|
|
2
|
+
import { randomBytes } from 'node:crypto';
|
|
3
|
+
import { normalizeMcpTransportUrl } from './client-config.mjs';
|
|
4
|
+
import { answerOAuthCallback } from './oauth-callback.mjs';
|
|
5
|
+
import { anySignal, loginFailed, reauthenticationRequired } from './oauth-shared.mjs';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* The browser login of one connection over its credential `cache` and login
|
|
9
|
+
* `flow` state: the loopback callback listener and the SDK authorization
|
|
10
|
+
* (refresh, registration, redirect and code exchange).
|
|
11
|
+
*/
|
|
12
|
+
export function createLoginFlow({ url, cache, flow, provider, lifetimeSignal, openBrowser, fetchImpl, timeoutMs }) {
|
|
13
|
+
const { creds } = cache;
|
|
14
|
+
function stopListening() {
|
|
15
|
+
flow.listener?.close();
|
|
16
|
+
flow.listener?.closeAllConnections();
|
|
17
|
+
flow.listener = undefined;
|
|
18
|
+
}
|
|
19
|
+
function closeListener() {
|
|
20
|
+
stopListening();
|
|
21
|
+
flow.verifier = undefined;
|
|
22
|
+
flow.state = undefined;
|
|
23
|
+
}
|
|
24
|
+
// The login's callback is answered once, by the loopback or a paste.
|
|
25
|
+
function answerCallback(code) {
|
|
26
|
+
const finish = flow.finishCallback;
|
|
27
|
+
flow.finishCallback = undefined;
|
|
28
|
+
finish?.(code);
|
|
29
|
+
}
|
|
30
|
+
async function listenForCallback(serverName) {
|
|
31
|
+
flow.listener = createServer((req, res) =>
|
|
32
|
+
answerOAuthCallback(req, res, {
|
|
33
|
+
redirectUrl: creds.redirectUrl,
|
|
34
|
+
state: flow.state,
|
|
35
|
+
finish: flow.finishCallback && answerCallback,
|
|
36
|
+
serverName,
|
|
37
|
+
})
|
|
38
|
+
);
|
|
39
|
+
await new Promise((resolve, reject) => {
|
|
40
|
+
flow.listener.once('error', reject);
|
|
41
|
+
// Retain the registered redirect URI if this connection later needs
|
|
42
|
+
// refresh or reauthorization; never register a different callback.
|
|
43
|
+
flow.listener.listen(creds.redirectUrl ? Number(new URL(creds.redirectUrl).port) : 0, '127.0.0.1', resolve);
|
|
44
|
+
});
|
|
45
|
+
creds.redirectUrl = `http://127.0.0.1:${flow.listener.address().port}/oauth/callback`;
|
|
46
|
+
}
|
|
47
|
+
async function login(challenge, requestSignal, interactive, browser, attemptedToken, serverName) {
|
|
48
|
+
flow.allowBrowser = interactive;
|
|
49
|
+
flow.browserOpened = false;
|
|
50
|
+
flow.launchBrowser = browser || openBrowser;
|
|
51
|
+
const signal = AbortSignal.any([lifetimeSignal, requestSignal, AbortSignal.timeout(timeoutMs)]);
|
|
52
|
+
const safeFetch = (input, init = {}) =>
|
|
53
|
+
fetchImpl(normalizeMcpTransportUrl(input), {
|
|
54
|
+
...init,
|
|
55
|
+
redirect: 'error',
|
|
56
|
+
signal: anySignal(signal, init.signal),
|
|
57
|
+
});
|
|
58
|
+
let abort;
|
|
59
|
+
const callback = new Promise((resolve, reject) => {
|
|
60
|
+
const finish = (code) => (code ? resolve(code) : reject(loginFailed()));
|
|
61
|
+
flow.finishCallback = finish;
|
|
62
|
+
abort = () => finish();
|
|
63
|
+
signal.addEventListener('abort', abort, { once: true });
|
|
64
|
+
});
|
|
65
|
+
// A discovery/registration error can precede waiting for the callback.
|
|
66
|
+
callback.catch(() => {});
|
|
67
|
+
try {
|
|
68
|
+
signal.throwIfAborted();
|
|
69
|
+
const { auth } = await import('@modelcontextprotocol/sdk/client/auth.js');
|
|
70
|
+
const options = { serverUrl: url, ...challenge, fetchFn: safeFetch };
|
|
71
|
+
// Another process sharing this keychain entry may already have
|
|
72
|
+
// refreshed (and rotated) the tokens this request was rejected with:
|
|
73
|
+
// decide on the stored credentials, under the cross-process lock.
|
|
74
|
+
const result = await cache.withLock(async () => {
|
|
75
|
+
cache.adopt(await cache.readStored());
|
|
76
|
+
if (creds.tokens && creds.tokens.access_token !== attemptedToken?.access_token) return 'AUTHORIZED';
|
|
77
|
+
if (!interactive && (!creds.tokens?.refresh_token || !creds.clientInformation || !creds.redirectUrl)) {
|
|
78
|
+
throw reauthenticationRequired();
|
|
79
|
+
}
|
|
80
|
+
flow.state = randomBytes(32).toString('base64url');
|
|
81
|
+
if (interactive) await listenForCallback(serverName);
|
|
82
|
+
signal.throwIfAborted();
|
|
83
|
+
return auth(provider, options);
|
|
84
|
+
});
|
|
85
|
+
if (result === 'REDIRECT') {
|
|
86
|
+
const code = await callback;
|
|
87
|
+
signal.throwIfAborted();
|
|
88
|
+
const exchanged = await cache.withLock(async () => {
|
|
89
|
+
// The entry this login registered in is gone only after a log-out
|
|
90
|
+
// since it began, which wins: never recreate it.
|
|
91
|
+
const saved = await cache.readStored();
|
|
92
|
+
if (!saved) throw loginFailed();
|
|
93
|
+
cache.adopt(saved);
|
|
94
|
+
return auth(provider, { ...options, authorizationCode: code });
|
|
95
|
+
});
|
|
96
|
+
if (exchanged !== 'AUTHORIZED') throw loginFailed();
|
|
97
|
+
}
|
|
98
|
+
signal.throwIfAborted();
|
|
99
|
+
} catch (error) {
|
|
100
|
+
// SDK errors may contain authorization server response bodies/secrets.
|
|
101
|
+
if (error?.code === 'reauthenticationRequired' || !interactive) throw reauthenticationRequired();
|
|
102
|
+
throw loginFailed();
|
|
103
|
+
} finally {
|
|
104
|
+
flow.allowBrowser = false;
|
|
105
|
+
signal.removeEventListener('abort', abort);
|
|
106
|
+
closeListener();
|
|
107
|
+
flow.finishCallback = undefined;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return { login, stopListening, closeListener, answerCallback };
|
|
111
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* mcp/oauth-probe.mjs — what authentication an HTTP/SSE server (being added or
|
|
3
|
+
* edited) needs, decided without credentials and without a browser: one
|
|
4
|
+
* anonymous request, then OAuth discovery (RFC 9728 protected-resource
|
|
5
|
+
* metadata, RFC 8414 / OIDC authorization-server metadata) on a 401.
|
|
6
|
+
*/
|
|
7
|
+
import {
|
|
8
|
+
normalizeMcpTransportUrl,
|
|
9
|
+
resolveMcpOAuthUrl,
|
|
10
|
+
resolveMcpTransportKind,
|
|
11
|
+
scrubMcpConnectionMessage,
|
|
12
|
+
} from './client-config.mjs';
|
|
13
|
+
import { hasConfiguredMcpAuth } from './client-transport.mjs';
|
|
14
|
+
|
|
15
|
+
export const MCP_AUTH_PROBE_TIMEOUT_MS = 10_000;
|
|
16
|
+
|
|
17
|
+
const INITIALIZE_PROBE = JSON.stringify({
|
|
18
|
+
jsonrpc: '2.0',
|
|
19
|
+
id: 0,
|
|
20
|
+
method: 'initialize',
|
|
21
|
+
params: {
|
|
22
|
+
protocolVersion: '2025-03-26',
|
|
23
|
+
capabilities: {},
|
|
24
|
+
clientInfo: { name: 'mixdog-auth-probe', version: '1' },
|
|
25
|
+
},
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* `cfg = { type: 'http' | 'sse', url, headers?, bearer_token_env_var?, env_http_headers? }`
|
|
30
|
+
* → `{ auth: 'oauth' | 'none' | 'configured' | 'unreachable', error? }`.
|
|
31
|
+
* Configured authentication is authoritative and never sent anywhere.
|
|
32
|
+
*/
|
|
33
|
+
export async function probeMcpServerAuth(cfg, { fetchImpl = fetch, timeoutMs = MCP_AUTH_PROBE_TIMEOUT_MS } = {}) {
|
|
34
|
+
if (!cfg || typeof cfg !== 'object' || Array.isArray(cfg)) throw new TypeError('MCP server config is required');
|
|
35
|
+
const kind = resolveMcpTransportKind(cfg);
|
|
36
|
+
if (kind !== 'http' && kind !== 'sse') throw new Error('MCP auth probe needs an http or sse server');
|
|
37
|
+
const url = resolveMcpOAuthUrl(cfg);
|
|
38
|
+
if (hasConfiguredMcpAuth(cfg)) return { auth: 'configured' };
|
|
39
|
+
const deadline = AbortSignal.timeout(timeoutMs);
|
|
40
|
+
// Same policy as the OAuth login's own requests: normalized URLs, no
|
|
41
|
+
// redirects, and nothing outlives the probe's budget.
|
|
42
|
+
const probeFetch = (input, init = {}) =>
|
|
43
|
+
fetchImpl(normalizeMcpTransportUrl(String(input)), {
|
|
44
|
+
...init,
|
|
45
|
+
redirect: 'error',
|
|
46
|
+
signal: init.signal ? AbortSignal.any([deadline, init.signal]) : deadline,
|
|
47
|
+
});
|
|
48
|
+
const failure = (error) => ({
|
|
49
|
+
auth: 'unreachable',
|
|
50
|
+
error: scrubMcpConnectionMessage(
|
|
51
|
+
deadline.aborted ? `no answer within ${timeoutMs}ms` : String(error?.cause?.message || error?.message || error),
|
|
52
|
+
cfg
|
|
53
|
+
),
|
|
54
|
+
});
|
|
55
|
+
let response;
|
|
56
|
+
try {
|
|
57
|
+
response =
|
|
58
|
+
kind === 'http'
|
|
59
|
+
? await probeFetch(url, {
|
|
60
|
+
method: 'POST',
|
|
61
|
+
headers: { 'Content-Type': 'application/json', Accept: 'application/json, text/event-stream' },
|
|
62
|
+
body: INITIALIZE_PROBE,
|
|
63
|
+
})
|
|
64
|
+
: await probeFetch(url, { headers: { Accept: 'text/event-stream' } });
|
|
65
|
+
} catch (error) {
|
|
66
|
+
return failure(error);
|
|
67
|
+
}
|
|
68
|
+
// The body is discarded; a failed cancel changes nothing.
|
|
69
|
+
await response.body?.cancel().catch(() => {});
|
|
70
|
+
if (response.status === 404 || response.status >= 500)
|
|
71
|
+
return { auth: 'unreachable', error: `HTTP ${response.status}` };
|
|
72
|
+
if (response.status !== 401) return { auth: 'none' };
|
|
73
|
+
try {
|
|
74
|
+
const { discoverOAuthServerInfo, extractWWWAuthenticateParams } = await import(
|
|
75
|
+
'@modelcontextprotocol/sdk/client/auth.js'
|
|
76
|
+
);
|
|
77
|
+
const { resourceMetadataUrl } = extractWWWAuthenticateParams(response);
|
|
78
|
+
const { authorizationServerMetadata } = await discoverOAuthServerInfo(url, {
|
|
79
|
+
resourceMetadataUrl,
|
|
80
|
+
fetchFn: probeFetch,
|
|
81
|
+
});
|
|
82
|
+
if (authorizationServerMetadata) return { auth: 'oauth' };
|
|
83
|
+
} catch (error) {
|
|
84
|
+
if (deadline.aborted) return failure(error);
|
|
85
|
+
}
|
|
86
|
+
return { auth: 'none', error: 'HTTP 401: the server requires authentication but offers no OAuth sign-in' };
|
|
87
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { normalizeMcpTransportUrl } from './client-config.mjs';
|
|
2
|
+
import { loginFailed, reauthenticationRequired } from './oauth-shared.mjs';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* The SDK's OAuth client provider over one connection's credential `cache`
|
|
6
|
+
* and login `flow` state.
|
|
7
|
+
*/
|
|
8
|
+
export function createOAuthProvider({ cache, flow, lifetimeSignal }) {
|
|
9
|
+
const { creds, ready, update } = cache;
|
|
10
|
+
// The SDK saves only inside a login, which holds the lock and has just
|
|
11
|
+
// adopted the stored entry.
|
|
12
|
+
const save = async (change) => {
|
|
13
|
+
if (!(await update(change))) throw loginFailed();
|
|
14
|
+
};
|
|
15
|
+
return {
|
|
16
|
+
get redirectUrl() {
|
|
17
|
+
return creds.redirectUrl;
|
|
18
|
+
},
|
|
19
|
+
get clientMetadata() {
|
|
20
|
+
return {
|
|
21
|
+
client_name: 'Mixdog',
|
|
22
|
+
redirect_uris: [creds.redirectUrl],
|
|
23
|
+
grant_types: ['authorization_code', 'refresh_token'],
|
|
24
|
+
response_types: ['code'],
|
|
25
|
+
token_endpoint_auth_method: 'none',
|
|
26
|
+
};
|
|
27
|
+
},
|
|
28
|
+
state: () => flow.state,
|
|
29
|
+
clientInformation: async () => {
|
|
30
|
+
await ready();
|
|
31
|
+
// A connect that cannot open a sign-in never registers a client (the
|
|
32
|
+
// SDK registers one after dropping a rejected registration): the next
|
|
33
|
+
// explicit sign-in registers it.
|
|
34
|
+
if (!creds.clientInformation && !flow.allowBrowser) throw reauthenticationRequired();
|
|
35
|
+
return creds.clientInformation;
|
|
36
|
+
},
|
|
37
|
+
saveClientInformation: (value) =>
|
|
38
|
+
save(() => {
|
|
39
|
+
creds.clientInformation = value;
|
|
40
|
+
}),
|
|
41
|
+
tokens: async () => {
|
|
42
|
+
await ready();
|
|
43
|
+
return creds.tokens;
|
|
44
|
+
},
|
|
45
|
+
saveTokens: async (value) => {
|
|
46
|
+
lifetimeSignal.throwIfAborted();
|
|
47
|
+
await save(() => {
|
|
48
|
+
creds.tokens = value;
|
|
49
|
+
});
|
|
50
|
+
},
|
|
51
|
+
saveCodeVerifier: (value) => {
|
|
52
|
+
flow.verifier = value;
|
|
53
|
+
},
|
|
54
|
+
codeVerifier: () => {
|
|
55
|
+
if (!flow.verifier) throw loginFailed();
|
|
56
|
+
return flow.verifier;
|
|
57
|
+
},
|
|
58
|
+
discoveryState: () => creds.discovery,
|
|
59
|
+
saveDiscoveryState: (value) =>
|
|
60
|
+
save(() => {
|
|
61
|
+
creds.discovery = value;
|
|
62
|
+
}),
|
|
63
|
+
// The SDK invalidates 'all' when the token endpoint rejects the client
|
|
64
|
+
// (invalid_client / unauthorized_client) and 'tokens' when it rejects the
|
|
65
|
+
// grant (invalid_grant). The registration goes in both cases: an
|
|
66
|
+
// authorization server that forgot its clients (a restart) may answer the
|
|
67
|
+
// refresh with invalid_grant, and reusing that registration would fail
|
|
68
|
+
// every later sign-in at the authorization endpoint; re-registering is cheap.
|
|
69
|
+
invalidateCredentials: async (scope) => {
|
|
70
|
+
if (scope === 'all' || scope === 'verifier') flow.verifier = undefined;
|
|
71
|
+
if (scope === 'discovery') creds.discovery = undefined;
|
|
72
|
+
if (scope !== 'all' && scope !== 'tokens' && scope !== 'client') return;
|
|
73
|
+
await update(
|
|
74
|
+
() => {
|
|
75
|
+
if (scope !== 'client') creds.tokens = undefined;
|
|
76
|
+
creds.clientInformation = undefined;
|
|
77
|
+
if (scope === 'all') creds.discovery = undefined;
|
|
78
|
+
},
|
|
79
|
+
{ remove: scope === 'all' }
|
|
80
|
+
);
|
|
81
|
+
},
|
|
82
|
+
redirectToAuthorization: async (authorizationUrl) => {
|
|
83
|
+
if (!flow.allowBrowser || flow.browserOpened) throw reauthenticationRequired();
|
|
84
|
+
const target = new URL(normalizeMcpTransportUrl(authorizationUrl));
|
|
85
|
+
if (
|
|
86
|
+
target.searchParams.get('state') !== flow.state ||
|
|
87
|
+
target.searchParams.get('code_challenge_method') !== 'S256' ||
|
|
88
|
+
!flow.verifier
|
|
89
|
+
)
|
|
90
|
+
throw loginFailed();
|
|
91
|
+
flow.browserOpened = true;
|
|
92
|
+
await flow.launchBrowser(target.href);
|
|
93
|
+
},
|
|
94
|
+
};
|
|
95
|
+
}
|