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
|
@@ -3,43 +3,97 @@
|
|
|
3
3
|
// connect. All mutable progress lives on the caller-owned `state`.
|
|
4
4
|
import { clean } from '../../runtime/agent/orchestrator/runtime-core/session-text.mjs';
|
|
5
5
|
import { envFlag } from '../../runtime/shared/env.mjs';
|
|
6
|
+
import { ensurePluginServerData } from './plugin-servers.mjs';
|
|
6
7
|
|
|
7
|
-
export function createMcpConnect({
|
|
8
|
+
export function createMcpConnect({
|
|
9
|
+
mcpClient,
|
|
10
|
+
getMcpScopeId,
|
|
11
|
+
getCurrentCwd = () => '',
|
|
12
|
+
state,
|
|
13
|
+
resolveEffectiveMcpServers,
|
|
14
|
+
mcpStatus,
|
|
15
|
+
}) {
|
|
8
16
|
const scopeOptions = () => ({ scopeId: getMcpScopeId() });
|
|
17
|
+
// name -> the resolved config this runtime last (re)connected the server
|
|
18
|
+
// with. Reconcile compares against it so only new or changed servers
|
|
19
|
+
// reconnect (a cwd switch changes only cwd-following plugin servers).
|
|
20
|
+
const applied = () => {
|
|
21
|
+
if (!(state.mcpApplied instanceof Map)) state.mcpApplied = new Map();
|
|
22
|
+
return state.mcpApplied;
|
|
23
|
+
};
|
|
24
|
+
const fingerprint = (cfg) => JSON.stringify(cfg);
|
|
25
|
+
// Roots advertised by cwd-following servers track this runtime's cwd.
|
|
26
|
+
const syncScopeRoot = () => mcpClient.setMcpScopeRoot?.(getMcpScopeId(), clean(getCurrentCwd?.()));
|
|
27
|
+
// Abort the pending interactive OAuth login of one server's connect.
|
|
28
|
+
const cancelMcpLogin = (name) => mcpClient.cancelMcpLogin?.(clean(name), scopeOptions());
|
|
29
|
+
// Answer it with the redirect URL pasted from the browser.
|
|
30
|
+
const completeMcpLogin = (name, callbackUrl) => mcpClient.completeMcpLogin(clean(name), callbackUrl, scopeOptions());
|
|
9
31
|
|
|
10
32
|
// Connect/disconnect exactly one server in the live registry, leaving all
|
|
11
33
|
// others untouched. Used by the enable/disable toggle so a single-server
|
|
12
|
-
// change never triggers a full disconnectAll()/reconnect freeze.
|
|
13
|
-
|
|
34
|
+
// change never triggers a full disconnectAll()/reconnect freeze. Only an
|
|
35
|
+
// explicit sign-in passes `login` ({ openBrowser }): every other connect is
|
|
36
|
+
// non-interactive and reports a server that needs one as sign-in required.
|
|
37
|
+
async function applyMcpServerConnection(name, enabled, servers = resolveEffectiveMcpServers().servers, login = null) {
|
|
14
38
|
const target = clean(name);
|
|
15
39
|
if (!target) return;
|
|
16
|
-
const { servers } = resolveEffectiveMcpServers();
|
|
17
40
|
// Changing this server's state clears any stale failure record for it.
|
|
18
41
|
if (Array.isArray(state.mcpFailures)) {
|
|
19
42
|
state.mcpFailures = state.mcpFailures.filter((row) => row.name !== target);
|
|
20
43
|
}
|
|
21
|
-
|
|
44
|
+
const cfg = servers[target];
|
|
45
|
+
if (enabled === false || !cfg || cfg.enabled === false) {
|
|
46
|
+
applied().delete(target);
|
|
22
47
|
await mcpClient.disconnectMcpServer?.(target, scopeOptions());
|
|
23
48
|
return;
|
|
24
49
|
}
|
|
25
|
-
|
|
26
|
-
if (!cfg) return;
|
|
50
|
+
applied().set(target, fingerprint(cfg));
|
|
27
51
|
// Close-then-connect as one generation-guarded op: a shutdown landing
|
|
28
52
|
// between the close and the connect never resurrects the server.
|
|
29
53
|
try {
|
|
30
|
-
|
|
54
|
+
ensurePluginServerData(cfg);
|
|
55
|
+
await mcpClient.reconnectSingleMcpServer(
|
|
56
|
+
getMcpScopeId(),
|
|
57
|
+
target,
|
|
58
|
+
cfg,
|
|
59
|
+
login ? { interactive: true, openBrowser: login.openBrowser } : {}
|
|
60
|
+
);
|
|
31
61
|
} catch (error) {
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
|
|
62
|
+
const failure = { name: target, msg: error?.message || String(error) };
|
|
63
|
+
if (error?.authRequired) failure.authRequired = true;
|
|
64
|
+
const failures = Array.isArray(error?.failures) ? error.failures : [failure];
|
|
35
65
|
state.mcpFailures = [...(state.mcpFailures || []), ...failures];
|
|
36
66
|
}
|
|
37
67
|
}
|
|
38
68
|
|
|
39
|
-
|
|
69
|
+
// Bring the registry in line with the effective set: drop servers that are
|
|
70
|
+
// gone or disabled, (re)connect servers that are new or whose resolved
|
|
71
|
+
// config changed; untouched servers keep their connection.
|
|
72
|
+
async function reconcileServers() {
|
|
73
|
+
syncScopeRoot();
|
|
74
|
+
const { servers } = resolveEffectiveMcpServers();
|
|
75
|
+
const known = applied();
|
|
76
|
+
const drops = [...known.keys()].filter((name) => !servers[name] || servers[name].enabled === false);
|
|
77
|
+
const connects = Object.entries(servers)
|
|
78
|
+
.filter(([name, cfg]) => cfg?.enabled !== false && known.get(name) !== fingerprint(cfg))
|
|
79
|
+
.map(([name]) => name);
|
|
80
|
+
await Promise.allSettled([
|
|
81
|
+
...drops.map((name) => applyMcpServerConnection(name, false, servers)),
|
|
82
|
+
...connects.map((name) => applyMcpServerConnection(name, true, servers)),
|
|
83
|
+
]);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
async function connectConfiguredMcp({
|
|
87
|
+
reset = false,
|
|
88
|
+
only = null,
|
|
89
|
+
enabled = true,
|
|
90
|
+
reconcile = false,
|
|
91
|
+
login = null,
|
|
92
|
+
} = {}) {
|
|
40
93
|
if (envFlag('MIXDOG_DISABLE_MCP')) {
|
|
41
94
|
++state.mcpConnectGeneration;
|
|
42
95
|
state.mcpFailures = [];
|
|
96
|
+
applied().clear();
|
|
43
97
|
if (only) await mcpClient.disconnectMcpServer?.(only, scopeOptions());
|
|
44
98
|
else await mcpClient.disconnectAll?.(scopeOptions());
|
|
45
99
|
return mcpStatus();
|
|
@@ -49,7 +103,10 @@ export function createMcpConnect({ mcpClient, getMcpScopeId, state, resolveEffec
|
|
|
49
103
|
// just wait for any in-flight run, then bail if a newer full reset has
|
|
50
104
|
// been requested in the meantime. Registering as in-flight makes a later
|
|
51
105
|
// reset serialize behind us instead of interleaving disconnect/connect.
|
|
52
|
-
if (only) {
|
|
106
|
+
if (only || reconcile) {
|
|
107
|
+
// A disable/remove must not wait out an interactive login of the
|
|
108
|
+
// connect it queues behind: cancel that login first.
|
|
109
|
+
if (only && enabled === false) cancelMcpLogin(only);
|
|
53
110
|
// Atomically capture the current generation AND the prior in-flight
|
|
54
111
|
// promise in the same synchronous step, then chain our op onto it. No
|
|
55
112
|
// await sits between the capture and the `state.mcpConnectInFlight = run`
|
|
@@ -68,7 +125,8 @@ export function createMcpConnect({ mcpClient, getMcpScopeId, state, resolveEffec
|
|
|
68
125
|
}
|
|
69
126
|
}
|
|
70
127
|
if (gen !== state.mcpConnectGeneration) return;
|
|
71
|
-
await
|
|
128
|
+
if (reconcile) await reconcileServers();
|
|
129
|
+
else await applyMcpServerConnection(only, enabled, undefined, login);
|
|
72
130
|
})();
|
|
73
131
|
state.mcpConnectInFlight = run;
|
|
74
132
|
try {
|
|
@@ -95,11 +153,20 @@ export function createMcpConnect({ mcpClient, getMcpScopeId, state, resolveEffec
|
|
|
95
153
|
}
|
|
96
154
|
if (gen !== state.mcpConnectGeneration) return mcpStatus();
|
|
97
155
|
const run = (async () => {
|
|
98
|
-
if (reset)
|
|
156
|
+
if (reset) {
|
|
157
|
+
await mcpClient.disconnectAll?.(scopeOptions());
|
|
158
|
+
applied().clear();
|
|
159
|
+
}
|
|
99
160
|
state.mcpFailures = [];
|
|
161
|
+
syncScopeRoot();
|
|
100
162
|
const { servers } = resolveEffectiveMcpServers();
|
|
163
|
+
for (const [name, cfg] of Object.entries(servers)) {
|
|
164
|
+
if (cfg?.enabled === false) applied().delete(name);
|
|
165
|
+
else applied().set(name, fingerprint(cfg));
|
|
166
|
+
}
|
|
101
167
|
if (Object.keys(servers).length === 0) return;
|
|
102
168
|
try {
|
|
169
|
+
for (const cfg of Object.values(servers)) if (cfg?.enabled !== false) ensurePluginServerData(cfg);
|
|
103
170
|
await mcpClient.connectMcpServers(servers, scopeOptions());
|
|
104
171
|
} catch (error) {
|
|
105
172
|
state.mcpFailures = Array.isArray(error?.failures)
|
|
@@ -160,5 +227,5 @@ export function createMcpConnect({ mcpClient, getMcpScopeId, state, resolveEffec
|
|
|
160
227
|
}
|
|
161
228
|
}
|
|
162
229
|
|
|
163
|
-
return { connectConfiguredMcp, awaitInitialMcpConnect };
|
|
230
|
+
return { connectConfiguredMcp, awaitInitialMcpConnect, cancelMcpLogin, completeMcpLogin };
|
|
164
231
|
}
|
|
@@ -0,0 +1,499 @@
|
|
|
1
|
+
// MCP servers derived live from enabled plugins. Nothing here is written into
|
|
2
|
+
// `config.mcpServers`: every resolve reads the registered plugins' declarations
|
|
3
|
+
// and folds in the user's per-server intent (`config.pluginMcp`). Also owns the
|
|
4
|
+
// one-time adoption of server copies an older enable action wrote into
|
|
5
|
+
// `config.mcpServers`, as a pure plan the caller may persist.
|
|
6
|
+
import { createHash } from 'node:crypto';
|
|
7
|
+
import { mkdirSync } from 'node:fs';
|
|
8
|
+
import { isAbsolute, join, resolve } from 'node:path';
|
|
9
|
+
import { isDeepStrictEqual } from 'node:util';
|
|
10
|
+
import {
|
|
11
|
+
PLUGIN_MCP_OVERRIDE_MAP_FIELDS,
|
|
12
|
+
PLUGIN_MCP_OVERRIDE_VALUE_FIELDS,
|
|
13
|
+
declaredMcpTransport,
|
|
14
|
+
loadPluginMcpDeclarations,
|
|
15
|
+
normalizePluginMcpIntents,
|
|
16
|
+
} from '../../runtime/agent/orchestrator/runtime-core/plugin-mcp.mjs';
|
|
17
|
+
import {
|
|
18
|
+
pluginServerEnv,
|
|
19
|
+
resolvePluginPlaceholders,
|
|
20
|
+
} from '../../runtime/agent/orchestrator/mcp/plugin-placeholders.mjs';
|
|
21
|
+
import { clean } from '../../runtime/shared/clean.mjs';
|
|
22
|
+
import { isPlainObject } from '../../runtime/shared/object.mjs';
|
|
23
|
+
|
|
24
|
+
// Tool names are `mcp__<server>__<tool>` and providers cap a tool name at 64
|
|
25
|
+
// characters of [A-Za-z0-9_-]; a short hyphenated server name leaves the tool
|
|
26
|
+
// its room and can never contain the `__` delimiter.
|
|
27
|
+
export const PLUGIN_MCP_NAME_MAX = 32;
|
|
28
|
+
const LEGACY_ENV_KEYS = new Set(['MIXDOG_PLUGIN_ROOT', 'MIXDOG_PLUGIN_DATA']);
|
|
29
|
+
const CASE_INSENSITIVE_PATHS = process.platform === 'win32';
|
|
30
|
+
|
|
31
|
+
function slug(value) {
|
|
32
|
+
return String(value || '')
|
|
33
|
+
.toLowerCase()
|
|
34
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
35
|
+
.replace(/^-+|-+$/g, '');
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function shortHash(text, length) {
|
|
39
|
+
return createHash('sha1').update(text).digest('hex').slice(0, length);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function withSuffix(name, suffix) {
|
|
43
|
+
return `${name.slice(0, PLUGIN_MCP_NAME_MAX - suffix.length - 1).replace(/-+$/, '')}-${suffix}`;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Name of one plugin server: the registry id (without its source hash) and
|
|
47
|
+
* the declared key, the key dropped when it repeats the plugin. Never depends
|
|
48
|
+
* on how many servers the plugin declares. */
|
|
49
|
+
export function pluginMcpServerBaseName(pluginId, key) {
|
|
50
|
+
const stem = slug(String(pluginId || '').replace(/-[0-9a-f]{8}$/i, '')) || 'plugin';
|
|
51
|
+
const leaf = slug(key) || 'mcp';
|
|
52
|
+
const name = leaf === stem ? `plugin-${stem}` : `plugin-${stem}-${leaf}`;
|
|
53
|
+
return name.length <= PLUGIN_MCP_NAME_MAX ? name : withSuffix(name, shortHash(`${pluginId}\0${key}`, 8));
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function disambiguatedName(base, pluginId, key, taken) {
|
|
57
|
+
const suffix = shortHash(`${pluginId}\0${key}`, 6);
|
|
58
|
+
let name = withSuffix(base, suffix);
|
|
59
|
+
for (let n = 2; taken.has(name); n += 1) name = withSuffix(base, `${suffix}${n}`);
|
|
60
|
+
return name;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// The name an earlier enable action gave a plugin's server copy.
|
|
64
|
+
function legacyServerName(plugin) {
|
|
65
|
+
const base = clean(plugin.name || plugin.title || 'plugin')
|
|
66
|
+
.toLowerCase()
|
|
67
|
+
.replace(/[^a-z0-9_.-]+/g, '-')
|
|
68
|
+
.replace(/^-+|-+$/g, '');
|
|
69
|
+
return base ? `plugin-${base}` : 'plugin-mcp';
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function comparablePath(path) {
|
|
73
|
+
const text = clean(path);
|
|
74
|
+
if (!text) return '';
|
|
75
|
+
const unified = resolve(text)
|
|
76
|
+
.replace(/[\\/]+/g, '/')
|
|
77
|
+
.replace(/\/$/, '');
|
|
78
|
+
return CASE_INSENSITIVE_PATHS ? unified.toLowerCase() : unified;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// How an earlier enable action rewrote a declaration before copying it.
|
|
82
|
+
function legacyCopyForm(value, root) {
|
|
83
|
+
if (typeof value === 'string') {
|
|
84
|
+
return value.replace(/\$\{CLAUDE_PLUGIN_ROOT\}/g, root).replace(/\$\{CODEX_PLUGIN_ROOT\}/g, root);
|
|
85
|
+
}
|
|
86
|
+
if (Array.isArray(value)) return value.map((v) => legacyCopyForm(v, root));
|
|
87
|
+
if (isPlainObject(value))
|
|
88
|
+
return Object.fromEntries(Object.entries(value).map(([k, v]) => [k, legacyCopyForm(v, root)]));
|
|
89
|
+
return value;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function sameTransport(copy, declared, root) {
|
|
93
|
+
const form = legacyCopyForm(declared, root);
|
|
94
|
+
return ['command', 'args', 'url'].every((field) => isDeepStrictEqual(copy[field], form[field]));
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const comparableTransport = (config) => {
|
|
98
|
+
const type = declaredMcpTransport(config);
|
|
99
|
+
return type === 'streamable-http' ? 'http' : type;
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
/** What the user changed in a copy relative to the declaration it was made
|
|
103
|
+
* from: `{ overrides }` when every change is something a plugin server
|
|
104
|
+
* override applies, else `{ reason }` (the copy is then left as configured,
|
|
105
|
+
* so nothing is ever dropped). Values the earlier enable action generated
|
|
106
|
+
* itself (plugin env markers, the forced cwd, enable flags) are not changes. */
|
|
107
|
+
function copyOverrides(copy, declared, root) {
|
|
108
|
+
const form = legacyCopyForm(declared, root);
|
|
109
|
+
const out = {};
|
|
110
|
+
const kept = [];
|
|
111
|
+
for (const field of new Set([...Object.keys(copy), ...Object.keys(form)])) {
|
|
112
|
+
if (field === 'enabled' || field === '_mixdogPluginDisabled') continue;
|
|
113
|
+
if (field === 'type' || field === 'transport') {
|
|
114
|
+
if (comparableTransport(copy) !== comparableTransport(form)) kept.push(field);
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
if (field === 'cwd') {
|
|
118
|
+
const declaredCwd = typeof form.cwd === 'string' && form.cwd ? form.cwd : '';
|
|
119
|
+
let generated = root;
|
|
120
|
+
if (declaredCwd) generated = isAbsolute(declaredCwd) ? declaredCwd : join(root, declaredCwd);
|
|
121
|
+
if (copy.cwd !== undefined && comparablePath(copy.cwd) !== comparablePath(generated)) kept.push(field);
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
if (PLUGIN_MCP_OVERRIDE_MAP_FIELDS.includes(field)) {
|
|
125
|
+
const copyMap = isPlainObject(copy[field]) ? copy[field] : {};
|
|
126
|
+
const declaredMap = isPlainObject(form[field]) ? form[field] : {};
|
|
127
|
+
if (copy[field] !== undefined && !isPlainObject(copy[field])) kept.push(field);
|
|
128
|
+
// A removed declared entry cannot be expressed as an added override.
|
|
129
|
+
else if (Object.keys(declaredMap).some((name) => !Object.hasOwn(copyMap, name))) kept.push(field);
|
|
130
|
+
const added = {};
|
|
131
|
+
for (const [name, value] of Object.entries(copyMap)) {
|
|
132
|
+
if (field === 'env' && LEGACY_ENV_KEYS.has(name)) continue;
|
|
133
|
+
if (!isDeepStrictEqual(value, declaredMap[name])) added[name] = value;
|
|
134
|
+
}
|
|
135
|
+
if (Object.keys(added).length) out[field] = added;
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
if (isDeepStrictEqual(copy[field], form[field])) continue;
|
|
139
|
+
if (PLUGIN_MCP_OVERRIDE_VALUE_FIELDS.includes(field) && Object.hasOwn(copy, field)) out[field] = copy[field];
|
|
140
|
+
else kept.push(field);
|
|
141
|
+
}
|
|
142
|
+
if (!kept.length) return { overrides: out };
|
|
143
|
+
const fields = [...new Set(kept)].map((field) => `"${field}"`).join(', ');
|
|
144
|
+
return { reason: `its ${fields} differ from the plugin declaration in a way a plugin server override cannot keep` };
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Declared key a copy stands for, or a reason it cannot be told apart.
|
|
148
|
+
function copyKey(name, copy, plugin, declared) {
|
|
149
|
+
const base = legacyServerName(plugin);
|
|
150
|
+
const keys = declared.servers.map((server) => server.key);
|
|
151
|
+
if (!keys.length) return { reason: 'the plugin declares no MCP servers' };
|
|
152
|
+
if (name === base) {
|
|
153
|
+
if (keys.length === 1) return { key: keys[0] };
|
|
154
|
+
const matches = declared.servers.filter((server) => sameTransport(copy, server.config, plugin.root));
|
|
155
|
+
return matches.length === 1 ? { key: matches[0].key } : { reason: 'it matches none or several declared servers' };
|
|
156
|
+
}
|
|
157
|
+
if (name.startsWith(`${base}--`)) {
|
|
158
|
+
const key = name.slice(base.length + 2);
|
|
159
|
+
return keys.includes(key) ? { key } : { reason: `the plugin no longer declares "${key}"` };
|
|
160
|
+
}
|
|
161
|
+
return { reason: `its name does not follow the plugin's naming (${base})` };
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Adoption of server copies made by the earlier enable action. A copy is
|
|
166
|
+
* adopted only when its `env.MIXDOG_PLUGIN_ROOT` is a registered plugin's root
|
|
167
|
+
* AND its name follows that plugin's earlier naming; it then leaves
|
|
168
|
+
* `mcpServers` and its name, disabled state and user-added connection values
|
|
169
|
+
* become that server's intent. Everything else stays untouched with a warning.
|
|
170
|
+
* Pure and idempotent: an already-adopted config yields an empty plan.
|
|
171
|
+
*/
|
|
172
|
+
function planCopyAdoption(userServers, intents, declared) {
|
|
173
|
+
// A present declaration wins over a missing one sharing its root, in any order.
|
|
174
|
+
const byRoot = new Map();
|
|
175
|
+
for (const d of declared) {
|
|
176
|
+
const root = comparablePath(d.plugin.root);
|
|
177
|
+
if (!d.missing || !byRoot.has(root)) byRoot.set(root, d);
|
|
178
|
+
}
|
|
179
|
+
const next = structuredClone(intents);
|
|
180
|
+
const removed = [];
|
|
181
|
+
const warnings = [];
|
|
182
|
+
// Copies of a registered plugin that cannot be adopted: left as configured
|
|
183
|
+
// (their own transport is honored) but still bound to the plugin's lifecycle.
|
|
184
|
+
const owned = new Map();
|
|
185
|
+
for (const [name, copy] of Object.entries(userServers)) {
|
|
186
|
+
const marker = isPlainObject(copy) && isPlainObject(copy.env) ? clean(copy.env.MIXDOG_PLUGIN_ROOT) : '';
|
|
187
|
+
if (!marker) continue;
|
|
188
|
+
const owner = byRoot.get(comparablePath(marker));
|
|
189
|
+
const leave = (reason) =>
|
|
190
|
+
warnings.push({ name, message: `"${name}" looks like a plugin MCP copy but was left as configured: ${reason}` });
|
|
191
|
+
if (!owner) {
|
|
192
|
+
leave(`no registered plugin has root ${marker}`);
|
|
193
|
+
continue;
|
|
194
|
+
}
|
|
195
|
+
// A registered plugin whose directory is gone still owns its copies, so
|
|
196
|
+
// its disablement and project scopes keep applying; nothing is adopted.
|
|
197
|
+
if (owner.missing) {
|
|
198
|
+
owned.set(name, { d: owner, key: name });
|
|
199
|
+
leave(`plugin root ${marker} is missing`);
|
|
200
|
+
continue;
|
|
201
|
+
}
|
|
202
|
+
const { key, reason } = copyKey(name, copy, owner.plugin, owner);
|
|
203
|
+
owned.set(name, { d: owner, key: key || name });
|
|
204
|
+
if (!key) {
|
|
205
|
+
leave(reason);
|
|
206
|
+
continue;
|
|
207
|
+
}
|
|
208
|
+
const id = owner.plugin.id;
|
|
209
|
+
const stored = next[id]?.[key];
|
|
210
|
+
if (stored?.alias && stored.alias !== name) {
|
|
211
|
+
leave(`plugin server "${key}" already keeps the name "${stored.alias}"`);
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
214
|
+
const declaration = owner.servers.find((server) => server.key === key).config;
|
|
215
|
+
const changes = copyOverrides(copy, declaration, owner.plugin.root);
|
|
216
|
+
if (!changes.overrides) {
|
|
217
|
+
leave(changes.reason);
|
|
218
|
+
continue;
|
|
219
|
+
}
|
|
220
|
+
const disabled = copy.enabled === false || (copy._mixdogPluginDisabled === true && owner.plugin.enabled !== false);
|
|
221
|
+
const declaredOn = declaration.enabled !== false;
|
|
222
|
+
next[id] = {
|
|
223
|
+
...(next[id] || {}),
|
|
224
|
+
[key]: {
|
|
225
|
+
alias: name,
|
|
226
|
+
...(disabled === declaredOn ? { enabled: !disabled } : {}),
|
|
227
|
+
...changes.overrides,
|
|
228
|
+
...(stored || {}),
|
|
229
|
+
},
|
|
230
|
+
};
|
|
231
|
+
removed.push(name);
|
|
232
|
+
owned.delete(name);
|
|
233
|
+
}
|
|
234
|
+
return { removed, intents: next, warnings, owned };
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
function declarationsOf(plugins) {
|
|
238
|
+
return plugins.map((plugin) => {
|
|
239
|
+
const root = clean(plugin.root);
|
|
240
|
+
if (!root || plugin.exists === false) {
|
|
241
|
+
return {
|
|
242
|
+
plugin,
|
|
243
|
+
missing: true,
|
|
244
|
+
format: 'none',
|
|
245
|
+
servers: [],
|
|
246
|
+
errors: [{ message: `plugin root not found: ${root}` }],
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
try {
|
|
250
|
+
return { plugin, ...loadPluginMcpDeclarations(root) };
|
|
251
|
+
} catch (error) {
|
|
252
|
+
return {
|
|
253
|
+
plugin,
|
|
254
|
+
format: 'none',
|
|
255
|
+
servers: [],
|
|
256
|
+
errors: [{ source: root, message: `cannot read MCP declarations${error?.code ? ` (${error.code})` : ''}` }],
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/** Names for every declared server: configured servers keep theirs, then kept
|
|
263
|
+
* aliases, then generated names in registry order; a later claimant of a taken
|
|
264
|
+
* name gets a deterministic suffix and a warning. */
|
|
265
|
+
function assignNames(userNames, declared, intents) {
|
|
266
|
+
const claimedBy = new Map(userNames.map((name) => [name, null]));
|
|
267
|
+
const names = new Map();
|
|
268
|
+
const warnings = new Map();
|
|
269
|
+
// Slots running under a generated collision name that is not yet kept.
|
|
270
|
+
const unpinned = new Set();
|
|
271
|
+
const slots = declared.flatMap((d) => d.servers.map((server) => ({ d, server, id: d.plugin.id })));
|
|
272
|
+
const claim = (slot, name) => {
|
|
273
|
+
claimedBy.set(name, slot.id);
|
|
274
|
+
names.set(slot, name);
|
|
275
|
+
};
|
|
276
|
+
const conflict = (wanted) => {
|
|
277
|
+
const holder = claimedBy.get(wanted);
|
|
278
|
+
return holder === null ? 'a configured MCP server' : `plugin "${holder}"`;
|
|
279
|
+
};
|
|
280
|
+
for (const slot of slots) {
|
|
281
|
+
const alias = intents[slot.id]?.[slot.server.key]?.alias;
|
|
282
|
+
if (!alias) continue;
|
|
283
|
+
if (claimedBy.has(alias)) warnings.set(slot, [`kept name "${alias}" is taken by ${conflict(alias)}`]);
|
|
284
|
+
else claim(slot, alias);
|
|
285
|
+
}
|
|
286
|
+
for (const slot of slots) {
|
|
287
|
+
if (names.has(slot)) continue;
|
|
288
|
+
const base = pluginMcpServerBaseName(slot.id, slot.server.key);
|
|
289
|
+
if (!claimedBy.has(base)) {
|
|
290
|
+
claim(slot, base);
|
|
291
|
+
continue;
|
|
292
|
+
}
|
|
293
|
+
const name = disambiguatedName(base, slot.id, slot.server.key, claimedBy);
|
|
294
|
+
warnings.set(slot, [
|
|
295
|
+
...(warnings.get(slot) || []),
|
|
296
|
+
`name "${base}" is taken by ${conflict(base)}; using "${name}"`,
|
|
297
|
+
]);
|
|
298
|
+
claim(slot, name);
|
|
299
|
+
if (!intents[slot.id]?.[slot.server.key]?.alias) unpinned.add(slot);
|
|
300
|
+
}
|
|
301
|
+
// A kept name standing in for a generated one that someone else holds still
|
|
302
|
+
// reports the conflict.
|
|
303
|
+
for (const slot of slots) {
|
|
304
|
+
const name = names.get(slot);
|
|
305
|
+
const base = pluginMcpServerBaseName(slot.id, slot.server.key);
|
|
306
|
+
if (warnings.has(slot) || name === base || !claimedBy.has(base) || claimedBy.get(base) === slot.id) continue;
|
|
307
|
+
if (name === intents[slot.id]?.[slot.server.key]?.alias) {
|
|
308
|
+
warnings.set(slot, [`name "${base}" is taken by ${conflict(base)}; keeping "${name}"`]);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
return { slots, names, warnings, unpinned };
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
function withIntentOverrides(config, intent) {
|
|
315
|
+
const out = { ...config };
|
|
316
|
+
for (const field of PLUGIN_MCP_OVERRIDE_MAP_FIELDS) {
|
|
317
|
+
if (intent?.[field]) out[field] = { ...(isPlainObject(out[field]) ? out[field] : {}), ...intent[field] };
|
|
318
|
+
}
|
|
319
|
+
for (const field of PLUGIN_MCP_OVERRIDE_VALUE_FIELDS) {
|
|
320
|
+
if (intent && Object.hasOwn(intent, field)) out[field] = intent[field];
|
|
321
|
+
}
|
|
322
|
+
return out;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/** Launchable config of one plugin server for a runtime whose session cwd is `cwd`. */
|
|
326
|
+
function resolveServerConfig({ plugin, format, server, intent, cwd, dataDir, platform }) {
|
|
327
|
+
const root = clean(plugin.root);
|
|
328
|
+
const data = dataDir ? join(dataDir, 'plugins', 'data', plugin.id) : '';
|
|
329
|
+
const { enabled: _declaredEnabled, ...declared } = server.config;
|
|
330
|
+
const cfg = resolvePluginPlaceholders(withIntentOverrides(declared, intent), { root, data, platform });
|
|
331
|
+
const stdio = declaredMcpTransport(cfg) === 'stdio';
|
|
332
|
+
let cwdDependent = false;
|
|
333
|
+
if (stdio) {
|
|
334
|
+
const declaredCwd = typeof cfg.cwd === 'string' ? cfg.cwd.trim() : '';
|
|
335
|
+
if (declaredCwd) {
|
|
336
|
+
cfg.cwd = isAbsolute(declaredCwd) || declaredCwd.startsWith('${') ? declaredCwd : resolve(root, declaredCwd);
|
|
337
|
+
} else if (format === 'mixdog') {
|
|
338
|
+
cfg.cwd = root;
|
|
339
|
+
} else {
|
|
340
|
+
cwdDependent = true;
|
|
341
|
+
if (cwd) cfg.cwd = cwd;
|
|
342
|
+
else delete cfg.cwd;
|
|
343
|
+
}
|
|
344
|
+
cfg.env = { ...pluginServerEnv({ root, data }), ...(isPlainObject(cfg.env) ? cfg.env : {}) };
|
|
345
|
+
}
|
|
346
|
+
return {
|
|
347
|
+
...cfg,
|
|
348
|
+
// Unset variables without a default fail a plugin server instead of
|
|
349
|
+
// launching it with empty values; MCP roots follow the session cwd.
|
|
350
|
+
_mixdogStrictEnv: true,
|
|
351
|
+
_mixdogRootsFollowScope: true,
|
|
352
|
+
_mixdogPlugin: { id: plugin.id, key: server.key, format, root, source: server.source, data, cwdDependent },
|
|
353
|
+
};
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* The effective MCP server set: the user's configured servers plus the servers
|
|
358
|
+
* every registered plugin declares (disabled ones included, `enabled: false`).
|
|
359
|
+
* `plugins` is the registry list in registration order.
|
|
360
|
+
*/
|
|
361
|
+
export function resolveMcpServerCatalog({ config, plugins = [], cwd = '', dataDir = '', platform = process.platform }) {
|
|
362
|
+
const configured = isPlainObject(config?.mcpServers) ? config.mcpServers : {};
|
|
363
|
+
const declared = declarationsOf(plugins.filter((plugin) => clean(plugin?.id)));
|
|
364
|
+
const adoption = planCopyAdoption(configured, normalizePluginMcpIntents(config?.pluginMcp), declared);
|
|
365
|
+
const removed = new Set(adoption.removed);
|
|
366
|
+
const servers = {};
|
|
367
|
+
const sources = {};
|
|
368
|
+
const entries = {};
|
|
369
|
+
const warnings = [...adoption.warnings];
|
|
370
|
+
for (const [name, cfg] of Object.entries(configured)) {
|
|
371
|
+
if (removed.has(name)) continue;
|
|
372
|
+
const ownedBy = adoption.owned.get(name);
|
|
373
|
+
const entryWarnings = adoption.warnings.filter((w) => w.name === name).map((w) => w.message);
|
|
374
|
+
sources[name] = 'config';
|
|
375
|
+
if (!ownedBy) {
|
|
376
|
+
servers[name] = { ...cfg, ...(cfg?._mixdogPluginDisabled === true ? { enabled: false } : {}) };
|
|
377
|
+
entries[name] = { owner: null, warnings: entryWarnings };
|
|
378
|
+
continue;
|
|
379
|
+
}
|
|
380
|
+
const { plugin, format } = ownedBy.d;
|
|
381
|
+
const pluginEnabled = plugin.enabled !== false;
|
|
382
|
+
const serverEnabled = !(cfg.enabled === false || (cfg._mixdogPluginDisabled === true && pluginEnabled));
|
|
383
|
+
const marker = {
|
|
384
|
+
id: plugin.id,
|
|
385
|
+
key: ownedBy.key,
|
|
386
|
+
format,
|
|
387
|
+
root: clean(plugin.root),
|
|
388
|
+
source: 'config',
|
|
389
|
+
data: '',
|
|
390
|
+
cwdDependent: false,
|
|
391
|
+
};
|
|
392
|
+
servers[name] = { ...cfg, enabled: pluginEnabled && serverEnabled, _mixdogPlugin: marker };
|
|
393
|
+
entries[name] = {
|
|
394
|
+
owner: { kind: 'plugin', ...marker, name: clean(plugin.name) || plugin.id },
|
|
395
|
+
pluginEnabled,
|
|
396
|
+
serverEnabled,
|
|
397
|
+
declaredEnabled: true,
|
|
398
|
+
warnings: entryWarnings,
|
|
399
|
+
};
|
|
400
|
+
}
|
|
401
|
+
const {
|
|
402
|
+
slots,
|
|
403
|
+
names,
|
|
404
|
+
warnings: nameWarnings,
|
|
405
|
+
unpinned,
|
|
406
|
+
} = assignNames(Object.keys(servers), declared, adoption.intents);
|
|
407
|
+
const pluginRows = {};
|
|
408
|
+
for (const d of declared) {
|
|
409
|
+
pluginRows[d.plugin.id] = {
|
|
410
|
+
id: d.plugin.id,
|
|
411
|
+
name: clean(d.plugin.name) || d.plugin.id,
|
|
412
|
+
enabled: d.plugin.enabled !== false,
|
|
413
|
+
format: d.format,
|
|
414
|
+
errors: d.errors,
|
|
415
|
+
servers: [],
|
|
416
|
+
...(d.missing ? { missing: true } : {}),
|
|
417
|
+
};
|
|
418
|
+
}
|
|
419
|
+
for (const slot of slots) {
|
|
420
|
+
const { d, server, id } = slot;
|
|
421
|
+
const name = names.get(slot);
|
|
422
|
+
const intent = adoption.intents[id]?.[server.key];
|
|
423
|
+
const pluginEnabled = d.plugin.enabled !== false;
|
|
424
|
+
// A declaration may ship a server off by default; the user's intent wins.
|
|
425
|
+
const declaredEnabled = server.config.enabled !== false;
|
|
426
|
+
const serverEnabled = typeof intent?.enabled === 'boolean' ? intent.enabled : declaredEnabled;
|
|
427
|
+
const cfg = resolveServerConfig({ plugin: d.plugin, format: d.format, server, intent, cwd, dataDir, platform });
|
|
428
|
+
cfg.enabled = pluginEnabled && serverEnabled;
|
|
429
|
+
servers[name] = cfg;
|
|
430
|
+
sources[name] = 'plugin';
|
|
431
|
+
entries[name] = {
|
|
432
|
+
owner: { kind: 'plugin', ...cfg._mixdogPlugin, name: pluginRows[id].name },
|
|
433
|
+
pluginEnabled,
|
|
434
|
+
serverEnabled,
|
|
435
|
+
declaredEnabled,
|
|
436
|
+
...(unpinned.has(slot) ? { unpinned: true } : {}),
|
|
437
|
+
warnings: nameWarnings.get(slot) || [],
|
|
438
|
+
};
|
|
439
|
+
for (const message of entries[name].warnings) warnings.push({ name, plugin: id, message });
|
|
440
|
+
pluginRows[id].servers.push(name);
|
|
441
|
+
}
|
|
442
|
+
return {
|
|
443
|
+
servers,
|
|
444
|
+
sources,
|
|
445
|
+
entries,
|
|
446
|
+
plugins: pluginRows,
|
|
447
|
+
warnings,
|
|
448
|
+
adoption: adoption.removed.length ? { removed: adoption.removed, intents: adoption.intents } : null,
|
|
449
|
+
};
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
/** `config` with adopted copies removed from `mcpServers` and their intent recorded. */
|
|
453
|
+
export function withAdoptedCopies(config, adoption) {
|
|
454
|
+
if (!adoption) return config;
|
|
455
|
+
const mcpServers = { ...(config.mcpServers || {}) };
|
|
456
|
+
for (const name of adoption.removed) delete mcpServers[name];
|
|
457
|
+
return { ...config, mcpServers, pluginMcp: adoption.intents };
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
/** `config` (already carrying `catalog`'s adoption) with every plugin server
|
|
461
|
+
* that runs under a generated collision name keeping that name, so it does
|
|
462
|
+
* not flip back (changing its tool names and name-keyed scopes) once the
|
|
463
|
+
* conflicting server goes away. Returns `config` itself when nothing changes. */
|
|
464
|
+
export function withPinnedCollisionNames(config, catalog) {
|
|
465
|
+
let next = config;
|
|
466
|
+
for (const [name, entry] of Object.entries(catalog?.entries || {})) {
|
|
467
|
+
if (!entry?.unpinned || !entry.owner) continue;
|
|
468
|
+
next = withPluginServerIntent(next, entry.owner.id, entry.owner.key, (intent) => ({ ...intent, alias: name }));
|
|
469
|
+
}
|
|
470
|
+
return next;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
/** `config` with one plugin server's intent replaced by `update(intent)`. */
|
|
474
|
+
export function withPluginServerIntent(config, pluginId, key, update) {
|
|
475
|
+
const intents = normalizePluginMcpIntents(config.pluginMcp);
|
|
476
|
+
const servers = { ...(intents[pluginId] || {}) };
|
|
477
|
+
const next = normalizePluginMcpIntents({ [pluginId]: { [key]: update({ ...(servers[key] || {}) }) } })[pluginId]?.[
|
|
478
|
+
key
|
|
479
|
+
];
|
|
480
|
+
if (next) servers[key] = next;
|
|
481
|
+
else delete servers[key];
|
|
482
|
+
if (Object.keys(servers).length) intents[pluginId] = servers;
|
|
483
|
+
else delete intents[pluginId];
|
|
484
|
+
return { ...config, pluginMcp: intents };
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
/** `config` without any intent recorded for `pluginId`. */
|
|
488
|
+
export function withoutPluginIntents(config, pluginId) {
|
|
489
|
+
const intents = normalizePluginMcpIntents(config.pluginMcp);
|
|
490
|
+
if (!intents[pluginId]) return config;
|
|
491
|
+
delete intents[pluginId];
|
|
492
|
+
return { ...config, pluginMcp: intents };
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
/** Create a plugin server's data directory; runs just before it connects. */
|
|
496
|
+
export function ensurePluginServerData(cfg) {
|
|
497
|
+
const data = cfg?._mixdogPlugin?.data;
|
|
498
|
+
if (data) mkdirSync(data, { recursive: true });
|
|
499
|
+
}
|