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
|
@@ -1,15 +1,35 @@
|
|
|
1
|
-
// Effective MCP server set and its status projection:
|
|
2
|
-
//
|
|
1
|
+
// Effective MCP server set and its status projection: the user's configured
|
|
2
|
+
// servers plus the servers registered plugins declare (resolved live, with
|
|
3
|
+
// explicit owner metadata), per-server config reads, and the live/failed rollup.
|
|
3
4
|
import { clean } from '../../runtime/agent/orchestrator/runtime-core/session-text.mjs';
|
|
4
5
|
import { envFlag } from '../../runtime/shared/env.mjs';
|
|
6
|
+
import { resolveMcpServerCatalog } from './plugin-servers.mjs';
|
|
5
7
|
|
|
6
|
-
function mcpServerStatus(cfg, live, fail) {
|
|
8
|
+
function mcpServerStatus(cfg, live, fail, loginPending) {
|
|
7
9
|
if (cfg?.enabled === false) return 'disabled';
|
|
8
10
|
if (live) return 'connected';
|
|
11
|
+
// Until an interactive sign-in completes the server still needs it.
|
|
12
|
+
if (loginPending || fail?.authRequired) return 'sign-in required';
|
|
9
13
|
return fail ? 'failed' : 'disconnected';
|
|
10
14
|
}
|
|
11
15
|
|
|
12
|
-
|
|
16
|
+
const EMPTY_CATALOG = { servers: {}, sources: {}, entries: {}, plugins: {}, warnings: [], adoption: null };
|
|
17
|
+
|
|
18
|
+
// Internal markers on a resolved plugin config; never part of an editable config.
|
|
19
|
+
function withoutMarkers(cfg) {
|
|
20
|
+
const { _mixdogStrictEnv, _mixdogRootsFollowScope, _mixdogPlugin, ...rest } = cfg || {};
|
|
21
|
+
return rest;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function createMcpServerCatalog({
|
|
25
|
+
mcpClient,
|
|
26
|
+
getConfig,
|
|
27
|
+
getCurrentCwd = () => '',
|
|
28
|
+
getMcpScopeId,
|
|
29
|
+
getPluginDataDir = () => '',
|
|
30
|
+
listPlugins = () => [],
|
|
31
|
+
state,
|
|
32
|
+
}) {
|
|
13
33
|
function mcpTransportLabel(cfg = {}) {
|
|
14
34
|
if (cfg.autoDetect) return `autoDetect:${cfg.autoDetect}`;
|
|
15
35
|
try {
|
|
@@ -19,33 +39,65 @@ export function createMcpServerCatalog({ mcpClient, getConfig, getMcpScopeId, st
|
|
|
19
39
|
}
|
|
20
40
|
}
|
|
21
41
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
42
|
+
/** Everything this runtime would connect, resolved now. MCP is
|
|
43
|
+
* machine-global: project `.mcp.json` files and per-project overrides stay
|
|
44
|
+
* outside the chain; only plugin servers without a declared cwd follow this
|
|
45
|
+
* runtime's session cwd. */
|
|
46
|
+
function resolveCatalog() {
|
|
47
|
+
if (envFlag('MIXDOG_DISABLE_MCP')) return EMPTY_CATALOG;
|
|
48
|
+
return resolveConfiguredCatalog();
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Every configured server (user config plus plugin declarations, disabled
|
|
52
|
+
* ones included) whether or not this runtime connects MCP at all. */
|
|
53
|
+
function resolveConfiguredCatalog() {
|
|
54
|
+
let plugins = [];
|
|
55
|
+
let registryWarning = null;
|
|
56
|
+
try {
|
|
57
|
+
plugins = listPlugins() || [];
|
|
58
|
+
} catch (error) {
|
|
59
|
+
registryWarning = { message: `plugin MCP servers unavailable: ${error?.message || String(error)}` };
|
|
39
60
|
}
|
|
61
|
+
const catalog = resolveMcpServerCatalog({
|
|
62
|
+
config: getConfig() || {},
|
|
63
|
+
plugins,
|
|
64
|
+
cwd: clean(getCurrentCwd?.()),
|
|
65
|
+
dataDir: clean(getPluginDataDir?.()),
|
|
66
|
+
});
|
|
67
|
+
if (registryWarning) catalog.warnings.unshift(registryWarning);
|
|
68
|
+
return catalog;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function resolveEffectiveMcpServers() {
|
|
72
|
+
const { servers, sources } = resolveCatalog();
|
|
40
73
|
return { servers, sources };
|
|
41
74
|
}
|
|
42
75
|
|
|
76
|
+
/** Plugin owner of an effective server name, or null for a configured one. */
|
|
77
|
+
function pluginServer(name) {
|
|
78
|
+
const catalog = resolveCatalog();
|
|
79
|
+
const entry = catalog.entries[clean(name)];
|
|
80
|
+
return entry?.owner ? { ...entry, plugin: catalog.plugins[entry.owner.id] } : null;
|
|
81
|
+
}
|
|
82
|
+
|
|
43
83
|
function getMcpServerConfig(name) {
|
|
44
84
|
const serverName = clean(name);
|
|
45
85
|
if (!serverName) throw new Error('MCP server name is required');
|
|
46
|
-
const { servers, sources } =
|
|
86
|
+
const { servers, sources, entries } = resolveCatalog();
|
|
47
87
|
const effective = servers[serverName];
|
|
48
88
|
if (!effective) throw new Error(`MCP server not configured: ${serverName}`);
|
|
89
|
+
// An unadopted plugin copy has an owner but stays the user's editable config.
|
|
90
|
+
const owner = sources[serverName] === 'plugin' ? entries[serverName]?.owner || null : null;
|
|
91
|
+
if (owner) {
|
|
92
|
+
return {
|
|
93
|
+
name: serverName,
|
|
94
|
+
source: 'plugin',
|
|
95
|
+
enabled: effective.enabled !== false,
|
|
96
|
+
owner,
|
|
97
|
+
readOnly: true,
|
|
98
|
+
config: withoutMarkers({ ...effective, enabled: undefined }),
|
|
99
|
+
};
|
|
100
|
+
}
|
|
49
101
|
const raw = getConfig()?.mcpServers?.[serverName];
|
|
50
102
|
if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {
|
|
51
103
|
throw new Error(`MCP server config is unavailable: ${serverName}`);
|
|
@@ -58,11 +110,8 @@ export function createMcpServerCatalog({ mcpClient, getConfig, getMcpScopeId, st
|
|
|
58
110
|
};
|
|
59
111
|
}
|
|
60
112
|
|
|
61
|
-
function
|
|
62
|
-
|
|
63
|
-
return { servers: [], configuredCount: 0, connectedCount: 0, failedCount: 0 };
|
|
64
|
-
}
|
|
65
|
-
const { servers: configured, sources } = resolveEffectiveMcpServers();
|
|
113
|
+
function statusRows(catalog) {
|
|
114
|
+
const { servers: configured, sources, entries } = catalog;
|
|
66
115
|
const connected = new Map((mcpClient.getMcpServerStatus?.(getMcpScopeId()) || []).map((row) => [row.name, row]));
|
|
67
116
|
const failures = new Map((state.mcpFailures || []).map((row) => [row.name, row]));
|
|
68
117
|
const servers = [];
|
|
@@ -73,32 +122,85 @@ export function createMcpServerCatalog({ mcpClient, getConfig, getMcpScopeId, st
|
|
|
73
122
|
const dead = live && (live.alive === false || live.connected === false);
|
|
74
123
|
const fail =
|
|
75
124
|
failures.get(name) || (dead ? { msg: live.error || live.lastError || 'MCP server is not alive' } : undefined);
|
|
76
|
-
|
|
125
|
+
const entry = entries[name] || {};
|
|
126
|
+
const loginPending = !(live && !dead) && mcpClient.isMcpLoginPending?.(name, getMcpScopeId()) === true;
|
|
127
|
+
const status = mcpServerStatus(cfg, live && !dead, fail, loginPending);
|
|
128
|
+
// Needing an OAuth sign-in is a state, not an error.
|
|
129
|
+
const authRequired = status === 'sign-in required';
|
|
130
|
+
const row = {
|
|
77
131
|
name,
|
|
78
132
|
configured: true,
|
|
79
133
|
enabled: cfg?.enabled !== false,
|
|
80
134
|
connected: Boolean(live) && !dead,
|
|
81
|
-
status
|
|
135
|
+
status,
|
|
82
136
|
transport: mcpTransportLabel(cfg),
|
|
83
137
|
toolCount: live?.toolCount || 0,
|
|
84
138
|
tools: live?.tools || [],
|
|
85
139
|
capabilities: live?.capabilities || { tools: false, prompts: false, resources: false },
|
|
86
|
-
error: fail?.msg || null,
|
|
140
|
+
error: authRequired ? null : fail?.msg || null,
|
|
141
|
+
warnings: [...(entry.warnings || []), ...(live?.warnings || [])],
|
|
87
142
|
source: sources[name] || 'config',
|
|
88
|
-
}
|
|
143
|
+
};
|
|
144
|
+
if (authRequired) row.authRequired = true;
|
|
145
|
+
const oauth = mcpClient.getMcpServerOAuthState?.(cfg);
|
|
146
|
+
// Why the last sign-in did not complete (already credential-scrubbed).
|
|
147
|
+
if (oauth) row.oauth = authRequired && fail?.msg ? { ...oauth, lastError: fail.msg } : oauth;
|
|
148
|
+
if (entry.owner) {
|
|
149
|
+
row.owner = entry.owner;
|
|
150
|
+
row.pluginEnabled = entry.pluginEnabled;
|
|
151
|
+
row.serverEnabled = entry.serverEnabled;
|
|
152
|
+
if (!entry.pluginEnabled) row.disabledReason = 'plugin disabled';
|
|
153
|
+
else if (!entry.serverEnabled) row.disabledReason = 'server disabled';
|
|
154
|
+
}
|
|
155
|
+
servers.push(row);
|
|
89
156
|
connected.delete(name);
|
|
90
157
|
}
|
|
91
158
|
for (const live of connected.values()) {
|
|
92
159
|
servers.push({ ...live, configured: false, status: 'connected' });
|
|
93
160
|
}
|
|
94
161
|
servers.sort((a, b) => String(a.name).localeCompare(String(b.name)));
|
|
162
|
+
return servers;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function mcpStatus() {
|
|
166
|
+
if (envFlag('MIXDOG_DISABLE_MCP')) {
|
|
167
|
+
return { servers: [], configuredCount: 0, connectedCount: 0, failedCount: 0 };
|
|
168
|
+
}
|
|
169
|
+
const catalog = resolveCatalog();
|
|
170
|
+
const servers = statusRows(catalog);
|
|
171
|
+
const declarationErrors = Object.values(catalog.plugins).flatMap((plugin) =>
|
|
172
|
+
plugin.errors.map((error) => ({ plugin: plugin.id, ...error }))
|
|
173
|
+
);
|
|
95
174
|
return {
|
|
96
175
|
servers,
|
|
97
|
-
configuredCount: Object.keys(
|
|
176
|
+
configuredCount: Object.keys(catalog.servers).length,
|
|
98
177
|
connectedCount: servers.filter((row) => row.connected).length,
|
|
99
178
|
failedCount: servers.filter((row) => row.status === 'failed').length,
|
|
179
|
+
warnings: catalog.warnings,
|
|
180
|
+
declarationErrors,
|
|
100
181
|
};
|
|
101
182
|
}
|
|
102
183
|
|
|
103
|
-
|
|
184
|
+
/** Per plugin id: declaration format/errors and its server status rows. */
|
|
185
|
+
function pluginMcpSummary() {
|
|
186
|
+
const catalog = resolveCatalog();
|
|
187
|
+
const rows = new Map(statusRows(catalog).map((row) => [row.name, row]));
|
|
188
|
+
return new Map(
|
|
189
|
+
Object.values(catalog.plugins).map((plugin) => [
|
|
190
|
+
plugin.id,
|
|
191
|
+
{ format: plugin.format, errors: plugin.errors, servers: plugin.servers.map((name) => rows.get(name)) },
|
|
192
|
+
])
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
return {
|
|
197
|
+
mcpTransportLabel,
|
|
198
|
+
resolveCatalog,
|
|
199
|
+
resolveConfiguredCatalog,
|
|
200
|
+
resolveEffectiveMcpServers,
|
|
201
|
+
pluginServer,
|
|
202
|
+
getMcpServerConfig,
|
|
203
|
+
mcpStatus,
|
|
204
|
+
pluginMcpSummary,
|
|
205
|
+
};
|
|
104
206
|
}
|
|
@@ -6,11 +6,28 @@ import { createMcpServerCatalog } from './mcp-glue/server-catalog.mjs';
|
|
|
6
6
|
import { createMcpConnect } from './mcp-glue/connect.mjs';
|
|
7
7
|
import { createMcpServerInput } from './mcp-glue/server-input.mjs';
|
|
8
8
|
|
|
9
|
-
export function createMcpGlue({
|
|
10
|
-
|
|
9
|
+
export function createMcpGlue({
|
|
10
|
+
mcpClient,
|
|
11
|
+
getConfig,
|
|
12
|
+
getCurrentCwd,
|
|
13
|
+
getMcpScopeId = () => null,
|
|
14
|
+
getPluginDataDir,
|
|
15
|
+
listPlugins,
|
|
16
|
+
state,
|
|
17
|
+
}) {
|
|
18
|
+
const catalog = createMcpServerCatalog({
|
|
19
|
+
mcpClient,
|
|
20
|
+
getConfig,
|
|
21
|
+
getCurrentCwd,
|
|
22
|
+
getMcpScopeId,
|
|
23
|
+
getPluginDataDir,
|
|
24
|
+
listPlugins,
|
|
25
|
+
state,
|
|
26
|
+
});
|
|
11
27
|
const connect = createMcpConnect({
|
|
12
28
|
mcpClient,
|
|
13
29
|
getMcpScopeId,
|
|
30
|
+
getCurrentCwd,
|
|
14
31
|
state,
|
|
15
32
|
resolveEffectiveMcpServers: catalog.resolveEffectiveMcpServers,
|
|
16
33
|
mcpStatus: catalog.mcpStatus,
|
|
@@ -19,9 +36,15 @@ export function createMcpGlue({ mcpClient, getConfig, getCurrentCwd, getMcpScope
|
|
|
19
36
|
|
|
20
37
|
return {
|
|
21
38
|
mcpStatus: catalog.mcpStatus,
|
|
39
|
+
resolveMcpCatalog: catalog.resolveCatalog,
|
|
40
|
+
resolveConfiguredMcpCatalog: catalog.resolveConfiguredCatalog,
|
|
41
|
+
pluginMcpServer: catalog.pluginServer,
|
|
42
|
+
pluginMcpSummary: catalog.pluginMcpSummary,
|
|
22
43
|
getMcpServerConfig: catalog.getMcpServerConfig,
|
|
23
44
|
connectConfiguredMcp: connect.connectConfiguredMcp,
|
|
24
45
|
awaitInitialMcpConnect: connect.awaitInitialMcpConnect,
|
|
46
|
+
cancelMcpLogin: connect.cancelMcpLogin,
|
|
47
|
+
completeMcpLogin: connect.completeMcpLogin,
|
|
25
48
|
normalizeMcpServerInput,
|
|
26
49
|
};
|
|
27
50
|
}
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
// Explicit MCP OAuth sign-in flows: the only interactive MCP connect. A flow
|
|
2
|
+
// reconnects one configured server with browser login allowed, hands the
|
|
3
|
+
// authorization URL back to the caller (opening the host browser only when
|
|
4
|
+
// asked), and is then polled until the reconnect settles.
|
|
5
|
+
import { randomUUID } from 'node:crypto';
|
|
6
|
+
import { openInBrowser } from '../runtime/shared/open-url.mjs';
|
|
7
|
+
|
|
8
|
+
// Settled flows stay readable long enough for a poller to observe the result.
|
|
9
|
+
const SETTLED_FLOW_TTL_MS = 10 * 60_000;
|
|
10
|
+
const NOT_COMPLETED = 'MCP OAuth sign-in was not completed';
|
|
11
|
+
|
|
12
|
+
const NOT_PENDING = 'MCP OAuth sign-in is no longer pending';
|
|
13
|
+
|
|
14
|
+
// flowId -> flow, shared by every session runtime of this process. A caller
|
|
15
|
+
// is not bound to the runtime that began a flow (the desktop's control
|
|
16
|
+
// session is replaced after a failed call, and an idle one is evicted), so
|
|
17
|
+
// status / complete / cancel reach the flow from any runtime; each flow keeps
|
|
18
|
+
// the handlers of the runtime that began it (its MCP scope and connect).
|
|
19
|
+
const flows = new Map();
|
|
20
|
+
|
|
21
|
+
// A pending flow keeps its authorization URL readable: a client polling the
|
|
22
|
+
// status (remote sign-in) still shows the page to open.
|
|
23
|
+
const view = (flow) => ({
|
|
24
|
+
flowId: flow.flowId,
|
|
25
|
+
state: flow.state,
|
|
26
|
+
...(flow.state === 'pending' && flow.url ? { url: flow.url } : {}),
|
|
27
|
+
...(flow.error ? { error: flow.error } : {}),
|
|
28
|
+
});
|
|
29
|
+
function settle(flow, state, error = null) {
|
|
30
|
+
if (flow.state !== 'pending') return;
|
|
31
|
+
flow.state = state;
|
|
32
|
+
if (state === 'failed') flow.error = error || NOT_COMPLETED;
|
|
33
|
+
setTimeout(() => flows.delete(flow.flowId), SETTLED_FLOW_TTL_MS).unref?.();
|
|
34
|
+
}
|
|
35
|
+
function flowOf(flowId) {
|
|
36
|
+
const flow = flows.get(String(flowId || ''));
|
|
37
|
+
if (!flow) throw new Error('MCP OAuth login not found');
|
|
38
|
+
return flow;
|
|
39
|
+
}
|
|
40
|
+
async function cancelFlow(flow) {
|
|
41
|
+
if (flow.state === 'pending') {
|
|
42
|
+
flow.cancelled = true;
|
|
43
|
+
flow.owner.cancel(flow.name);
|
|
44
|
+
await flow.done;
|
|
45
|
+
}
|
|
46
|
+
return view(flow);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* `connect(name, openBrowser)` reconnects the server interactively, calling
|
|
51
|
+
* `openBrowser(url)` when a login starts, and resolves `{ connected, error }`.
|
|
52
|
+
* `cancel(name)` aborts that server's pending login. `completeCallback(name,
|
|
53
|
+
* callbackUrl)` answers it with a pasted redirect URL (throws a fixed message
|
|
54
|
+
* for an invalid one). `onComplete(name)` runs after a successful sign-in,
|
|
55
|
+
* before the flow reports complete.
|
|
56
|
+
*/
|
|
57
|
+
export function createMcpOAuthLogins({
|
|
58
|
+
connect,
|
|
59
|
+
cancel,
|
|
60
|
+
completeCallback = () => {
|
|
61
|
+
throw new Error(NOT_PENDING);
|
|
62
|
+
},
|
|
63
|
+
onComplete = async () => {},
|
|
64
|
+
openBrowser = openInBrowser,
|
|
65
|
+
}) {
|
|
66
|
+
// name -> tail of that server's serialized begin/cancel/sign-out steps, so
|
|
67
|
+
// replacing a pending flow is atomic and exactly one can be pending.
|
|
68
|
+
const lanes = new Map();
|
|
69
|
+
function exclusive(name, step) {
|
|
70
|
+
const run = (lanes.get(name) || Promise.resolve()).then(step);
|
|
71
|
+
const tail = run.then(
|
|
72
|
+
() => {},
|
|
73
|
+
() => {}
|
|
74
|
+
);
|
|
75
|
+
lanes.set(name, tail);
|
|
76
|
+
tail.then(() => {
|
|
77
|
+
if (lanes.get(name) === tail) lanes.delete(name);
|
|
78
|
+
});
|
|
79
|
+
return run;
|
|
80
|
+
}
|
|
81
|
+
// This runtime's handlers, kept on each flow it begins.
|
|
82
|
+
const owner = {
|
|
83
|
+
exclusive,
|
|
84
|
+
cancel,
|
|
85
|
+
completeCallback,
|
|
86
|
+
};
|
|
87
|
+
// One sign-in per server of this runtime (its MCP scope).
|
|
88
|
+
const cancelServerFlows = (name) =>
|
|
89
|
+
Promise.all([...flows.values()].filter((flow) => flow.owner === owner && flow.name === name).map(cancelFlow));
|
|
90
|
+
|
|
91
|
+
async function begin(name, options = {}) {
|
|
92
|
+
const { openBrowser: hostBrowser } = options || {};
|
|
93
|
+
if (hostBrowser !== undefined && typeof hostBrowser !== 'boolean') {
|
|
94
|
+
throw new TypeError('openBrowser must be a boolean');
|
|
95
|
+
}
|
|
96
|
+
let announceUrl;
|
|
97
|
+
const urlReady = new Promise((resolve) => {
|
|
98
|
+
announceUrl = resolve;
|
|
99
|
+
});
|
|
100
|
+
// One sign-in per server: a new one replaces the pending one, and the
|
|
101
|
+
// replacement is registered before the next queued step of this server.
|
|
102
|
+
const flow = await exclusive(name, async () => {
|
|
103
|
+
await cancelServerFlows(name);
|
|
104
|
+
const flow = {
|
|
105
|
+
flowId: `mcp_oauth_${randomUUID()}`,
|
|
106
|
+
name,
|
|
107
|
+
owner,
|
|
108
|
+
state: 'pending',
|
|
109
|
+
error: null,
|
|
110
|
+
cancelled: false,
|
|
111
|
+
url: null,
|
|
112
|
+
};
|
|
113
|
+
flows.set(flow.flowId, flow);
|
|
114
|
+
const launch = async (url) => {
|
|
115
|
+
// A login reached after its flow was cancelled (queued behind another
|
|
116
|
+
// connect of this server) never opens anything.
|
|
117
|
+
if (flow.cancelled) throw new Error('MCP OAuth login cancelled');
|
|
118
|
+
flow.awaitingCallback = true;
|
|
119
|
+
flow.url = url;
|
|
120
|
+
announceUrl(url);
|
|
121
|
+
if (hostBrowser !== false) openBrowser(url);
|
|
122
|
+
};
|
|
123
|
+
flow.done = (async () => {
|
|
124
|
+
try {
|
|
125
|
+
const outcome = await connect(name, launch);
|
|
126
|
+
if (flow.cancelled) settle(flow, 'cancelled');
|
|
127
|
+
else if (outcome?.connected) {
|
|
128
|
+
// The sign-in itself succeeded; a failed follow-up never undoes it.
|
|
129
|
+
await Promise.resolve()
|
|
130
|
+
.then(() => onComplete(name))
|
|
131
|
+
.catch(() => {});
|
|
132
|
+
settle(flow, 'complete');
|
|
133
|
+
} else settle(flow, 'failed', outcome?.error);
|
|
134
|
+
} catch (error) {
|
|
135
|
+
settle(flow, flow.cancelled ? 'cancelled' : 'failed', error?.message || String(error));
|
|
136
|
+
}
|
|
137
|
+
})();
|
|
138
|
+
return flow;
|
|
139
|
+
});
|
|
140
|
+
const url = await Promise.race([urlReady, flow.done.then(() => null)]);
|
|
141
|
+
if (url) return { flowId: flow.flowId, state: 'pending', url };
|
|
142
|
+
// Settled without a login: stored credentials (or no challenge) sufficed.
|
|
143
|
+
if (flow.state === 'complete') return view(flow);
|
|
144
|
+
// A superseded or failed begin stays readable (cancelled / failed).
|
|
145
|
+
throw new Error(flow.error || NOT_COMPLETED);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return {
|
|
149
|
+
begin,
|
|
150
|
+
status: (flowId) => view(flowOf(flowId)),
|
|
151
|
+
cancel(flowId) {
|
|
152
|
+
const flow = flowOf(flowId);
|
|
153
|
+
return flow.owner.exclusive(flow.name, () => cancelFlow(flow));
|
|
154
|
+
},
|
|
155
|
+
/** Answer the pending flow with the redirect URL pasted from the browser
|
|
156
|
+
* and resolve once it settles: `{ flowId, state: 'complete' | 'failed',
|
|
157
|
+
* error? }`. An invalid paste throws and leaves the flow pending. */
|
|
158
|
+
async complete(flowId, callbackUrl) {
|
|
159
|
+
const flow = flowOf(flowId);
|
|
160
|
+
if (flow.state !== 'pending' || flow.cancelled || !flow.awaitingCallback) throw new Error(NOT_PENDING);
|
|
161
|
+
flow.owner.completeCallback(flow.name, callbackUrl);
|
|
162
|
+
await flow.done;
|
|
163
|
+
return view(flow);
|
|
164
|
+
},
|
|
165
|
+
/** Cancel every pending sign-in of one server, then run `after` (sign-out)
|
|
166
|
+
* before any later begin of that server starts; resolves its result. */
|
|
167
|
+
cancelServer(name, after = async () => {}) {
|
|
168
|
+
return exclusive(name, async () => {
|
|
169
|
+
await cancelServerFlows(name);
|
|
170
|
+
return after();
|
|
171
|
+
});
|
|
172
|
+
},
|
|
173
|
+
};
|
|
174
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* model-switch-target.mjs — what a user's model selection names, and whether
|
|
3
|
+
* it switches an established conversation to another model. Shared by every
|
|
4
|
+
* surface that offers the choice (session API routes, plain REPL /model).
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/** The provider/model a selection names; a bare preset id or name names the
|
|
8
|
+
* preset's model. */
|
|
9
|
+
export function selectedModel(runtime, request, sessionProvider, sessionModel) {
|
|
10
|
+
const model = String(request?.model ?? '').trim();
|
|
11
|
+
if (!request?.provider && model) {
|
|
12
|
+
const wanted = model.toLowerCase();
|
|
13
|
+
const presets = runtime?.listPresets?.();
|
|
14
|
+
const preset = (Array.isArray(presets) ? presets : []).find((entry) =>
|
|
15
|
+
[entry?.id, entry?.name].some(
|
|
16
|
+
(key) =>
|
|
17
|
+
String(key ?? '')
|
|
18
|
+
.trim()
|
|
19
|
+
.toLowerCase() === wanted
|
|
20
|
+
)
|
|
21
|
+
);
|
|
22
|
+
if (preset?.provider && preset?.model) return { provider: String(preset.provider), model: String(preset.model) };
|
|
23
|
+
}
|
|
24
|
+
return { provider: String(request?.provider ?? sessionProvider), model: model || sessionModel };
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** A selection of another model than the established conversation's own. */
|
|
28
|
+
export function switchesSessionModel(runtime, request) {
|
|
29
|
+
const sessionProvider = String(runtime?.sessionProvider ?? '');
|
|
30
|
+
const sessionModel = String(runtime?.sessionModel ?? '');
|
|
31
|
+
if (!sessionProvider || !sessionModel) return false;
|
|
32
|
+
const { provider, model } = selectedModel(runtime, request, sessionProvider, sessionModel);
|
|
33
|
+
return provider !== sessionProvider || model !== sessionModel;
|
|
34
|
+
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* set-route.mjs — select the main route: resolve and validate the request,
|
|
3
3
|
* adopt it into config, then decide how the current session follows it
|
|
4
|
-
* (untouched, tuned, recreated,
|
|
4
|
+
* (untouched, tuned, recreated, updated in place when empty, or switched in
|
|
5
|
+
* place when the selection is addressed to an established conversation).
|
|
5
6
|
*/
|
|
6
7
|
import { clean, hasOwn } from '../../runtime/agent/orchestrator/runtime-core/session-text.mjs';
|
|
7
8
|
import {
|
|
@@ -117,6 +118,51 @@ function updateSessionInPlace(deps, session, updateOptions) {
|
|
|
117
118
|
writeStatuslineRoute(statusRoutes, getSession(), route);
|
|
118
119
|
}
|
|
119
120
|
|
|
121
|
+
/** Index of the message a model switch boundary is recorded on: the last one
|
|
122
|
+
* before a trailing unanswered prompt, which a retry may rewind. */
|
|
123
|
+
function modelSwitchAnchorIndex(messages) {
|
|
124
|
+
let index = messages.length - 1;
|
|
125
|
+
while (index >= 0 && (messages[index]?.role === 'user' || messages[index]?.role === 'system')) index -= 1;
|
|
126
|
+
if (index >= 0) return index;
|
|
127
|
+
return messages.findLastIndex((message) => message?.role !== 'system');
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/** Display-only boundary of an in-place model switch, kept in message meta
|
|
131
|
+
* (like an inheritance boundary): the transcript shows it live and after a
|
|
132
|
+
* restore, and the model conversation gains no synthetic message. */
|
|
133
|
+
export function recordModelSwitchBoundary(session, boundary) {
|
|
134
|
+
const messages = Array.isArray(session?.messages) ? session.messages : [];
|
|
135
|
+
const index = modelSwitchAnchorIndex(messages);
|
|
136
|
+
if (index < 0) return false;
|
|
137
|
+
const anchor = messages[index];
|
|
138
|
+
const switches = Array.isArray(anchor.meta?.modelSwitches) ? anchor.meta.modelSwitches : [];
|
|
139
|
+
if (switches.some((entry) => entry?.at === boundary.at && entry?.modelId === boundary.modelId)) return true;
|
|
140
|
+
// Replace the message object: the session save worker detects changed
|
|
141
|
+
// messages by reference identity, so an in-place meta edit is never saved.
|
|
142
|
+
messages[index] = { ...anchor, meta: { ...anchor.meta, modelSwitches: [...switches, boundary] } };
|
|
143
|
+
return true;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/** Move the addressed, established session onto the adopted route in place.
|
|
147
|
+
* Its provider replay state and context readings are dropped, so the next
|
|
148
|
+
* turn re-measures and auto-compacts against the new model's window. Returns
|
|
149
|
+
* the recorded boundary, or null when the session has nothing to mark. */
|
|
150
|
+
function switchSessionInPlace(deps, session) {
|
|
151
|
+
const { getRoute, getSession } = deps;
|
|
152
|
+
const route = getRoute();
|
|
153
|
+
if (route.provider === session.provider && route.model === session.model) {
|
|
154
|
+
updateSessionInPlace(deps, session);
|
|
155
|
+
return null;
|
|
156
|
+
}
|
|
157
|
+
const boundary = { provider: route.provider, modelId: route.model, at: Date.now() };
|
|
158
|
+
const recorded = recordModelSwitchBoundary(session, boundary);
|
|
159
|
+
updateSessionInPlace(deps, session, { crossProvider: true });
|
|
160
|
+
// A manager that answered with another copy of the session carries the
|
|
161
|
+
// boundary too; the next save persists it.
|
|
162
|
+
if (recorded && getSession() && getSession() !== session) recordModelSwitchBoundary(getSession(), boundary);
|
|
163
|
+
return recorded ? boundary : null;
|
|
164
|
+
}
|
|
165
|
+
|
|
120
166
|
/** Validate, persist and adopt the requested route as the runtime's route. */
|
|
121
167
|
async function adoptRoute(deps, persist, next, options) {
|
|
122
168
|
const {
|
|
@@ -144,7 +190,9 @@ async function adoptRoute(deps, persist, next, options) {
|
|
|
144
190
|
* Quota-exhaustion escape hatch: apply `next` to the addressed, ESTABLISHED
|
|
145
191
|
* session in place. The caller (session API) has already proven the latest
|
|
146
192
|
* settled turn failed on quota and that nothing is running. Starts no turn;
|
|
147
|
-
* the next turn's auto-compaction fits history to the new window.
|
|
193
|
+
* the next turn's auto-compaction fits history to the new window. Answers
|
|
194
|
+
* `{ modelSwitch }` with the recorded boundary (as setRoute does) so the
|
|
195
|
+
* session surface shows the same live divider, or true when nothing switched.
|
|
148
196
|
*/
|
|
149
197
|
export function createContinueSessionInModel(deps, persist) {
|
|
150
198
|
const { getSession, invalidateContextStatusCache } = deps;
|
|
@@ -154,33 +202,53 @@ export function createContinueSessionInModel(deps, persist) {
|
|
|
154
202
|
throw new Error(`session ${sessionId || ''} is not the active session`);
|
|
155
203
|
}
|
|
156
204
|
await adoptRoute(deps, persist, next, {});
|
|
157
|
-
|
|
205
|
+
const modelSwitch = switchSessionInPlace(deps, session);
|
|
158
206
|
invalidateContextStatusCache();
|
|
159
|
-
return true;
|
|
207
|
+
return modelSwitch ? { modelSwitch } : true;
|
|
160
208
|
};
|
|
161
209
|
}
|
|
162
210
|
|
|
211
|
+
/** Rejects an unacceptable selection without applying it. */
|
|
212
|
+
async function validateSelectedRoute(deps, next) {
|
|
213
|
+
await resolveSelectedRoute(deps, next);
|
|
214
|
+
return deps.getRoute();
|
|
215
|
+
}
|
|
216
|
+
|
|
163
217
|
export function createSetRoute(deps, persist) {
|
|
164
218
|
const { getRoute, getSession, invalidateContextStatusCache, createCurrentSession } = deps;
|
|
165
219
|
|
|
166
220
|
return async function setRoute(next, options = {}) {
|
|
167
|
-
//
|
|
168
|
-
//
|
|
169
|
-
//
|
|
221
|
+
// Without an explicit durable address a selection is for a new/inherited
|
|
222
|
+
// session and never rewrites an existing conversation's model, unless it
|
|
223
|
+
// is the user's model choice for this conversation (`switchEstablished`,
|
|
224
|
+
// the session surface): that one switches an established conversation in
|
|
225
|
+
// place while an empty one follows the unaddressed rules below.
|
|
170
226
|
const applyToCurrentSession = options?.applyToCurrentSession === true;
|
|
227
|
+
const switchEstablished = applyToCurrentSession || options?.switchEstablished === true;
|
|
228
|
+
// Lets a caller prove the selection is acceptable before it does
|
|
229
|
+
// irreversible work (compaction) ahead of the switch; changes nothing.
|
|
230
|
+
if (options?.validateOnly === true) return validateSelectedRoute(deps, next);
|
|
171
231
|
await adoptRoute(deps, persist, next, options);
|
|
172
232
|
const session = getSession();
|
|
173
|
-
//
|
|
174
|
-
//
|
|
175
|
-
//
|
|
176
|
-
//
|
|
177
|
-
// cache to protect, so /model takes effect live: route + statusline
|
|
233
|
+
// A session the user has already talked in or compacted keeps its route
|
|
234
|
+
// unless the selection was addressed to it. A current session with no
|
|
235
|
+
// model reply, no compaction summary and no in-flight first turn — empty,
|
|
236
|
+
// or holding only refused prompts — takes effect live: route + statusline
|
|
178
237
|
// update immediately. Only a truly empty session is recreated below.
|
|
179
238
|
const currentSessionEmpty = !!session && !sessionRouteCommitted(session);
|
|
180
239
|
if (!currentSessionEmpty) {
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
240
|
+
if (!switchEstablished) return getRoute();
|
|
241
|
+
const route = getRoute();
|
|
242
|
+
// An addressed selection of another model switches the conversation
|
|
243
|
+
// itself in place; the caller (session surface) refuses it while a
|
|
244
|
+
// turn runs. A same-model update (effort/Fast) only tunes the session
|
|
245
|
+
// from its next turn.
|
|
246
|
+
if (session && (route.provider !== session.provider || route.model !== session.model)) {
|
|
247
|
+
const modelSwitch = switchSessionInPlace(deps, session);
|
|
248
|
+
invalidateContextStatusCache();
|
|
249
|
+
return modelSwitch ? { ...getRoute(), modelSwitch } : getRoute();
|
|
250
|
+
}
|
|
251
|
+
persist.applySessionTuning();
|
|
184
252
|
return getRoute();
|
|
185
253
|
}
|
|
186
254
|
// An addressed runtime keeps its durable id: the empty session is updated
|
|
@@ -62,7 +62,7 @@ export function createWebSearchRouteApi(deps) {
|
|
|
62
62
|
await awaitKeychainPrewarm();
|
|
63
63
|
ensureFullConfig();
|
|
64
64
|
await ensureProvidersReady(ensureProviderEnabled(getConfig(), selectedRoute.provider));
|
|
65
|
-
const modelMeta = await lookupModelMeta(selectedRoute.provider, selectedRoute.model);
|
|
65
|
+
const modelMeta = await lookupModelMeta(selectedRoute.provider, selectedRoute.model, { allowFetch: true });
|
|
66
66
|
if (!webSearchCapableFor(selectedRoute.provider, modelMeta)) {
|
|
67
67
|
throw new Error(`model "${selectedRoute.model}" is not marked as web-search capable`);
|
|
68
68
|
}
|