mixdog 1.3.5 → 1.4.0
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/NOTICE.md +2 -2
- package/README.md +2 -2
- package/package.json +2 -11
- package/scripts/daemon-stub.mjs +1 -1
- package/scripts/gate-local.mjs +4 -2
- package/scripts/native-release-status.mjs +2 -2
- package/scripts/recall-history-cases.json +1 -1
- package/scripts/suite-health.mjs +10 -1
- package/scripts/test.mjs +7 -11
- package/src/agents/reviewer/AGENT.md +7 -11
- package/src/agents/reviewer/agent.json +1 -1
- package/src/defaults/shipped-history.json +17 -1
- package/src/defaults/skills/browser-use/SKILL.md +59 -11
- package/src/defaults/skills/code-tidy/SKILL.md +142 -270
- package/src/defaults/skills/code-tidy/references/agent-cleanup.md +4 -4
- package/src/defaults/skills/code-tidy/references/cleanup-report.md +31 -76
- package/src/defaults/skills/code-tidy/references/dead-code.md +6 -12
- package/src/defaults/skills/computer-use/SKILL.md +33 -9
- package/src/defaults/skills/setup/SKILL.md +22 -0
- package/src/defaults/skills/setup/references/actions.md +39 -5
- package/src/defaults/skills/setup/references/surfaces.md +11 -2
- package/src/defaults/skills/visualize/SKILL.md +2 -1
- package/src/lib/rules-builder.cjs +10 -8
- package/src/rules/agent/AGENT.md +6 -6
- package/src/rules/lead/LEAD.md +18 -20
- package/src/rules/shared/05-tool-calls.md +5 -6
- package/src/rules/shared/10-tool-workflow.md +2 -0
- package/src/rules/shared/60-verification.md +7 -5
- package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +2 -3
- package/src/runtime/agent/orchestrator/agent-runtime/title-completion.mjs +3 -1
- package/src/runtime/agent/orchestrator/agent-trace-format.mjs +3 -1
- package/src/runtime/agent/orchestrator/config.mjs +17 -1
- package/src/runtime/agent/orchestrator/context/collect.mjs +3 -0
- package/src/runtime/agent/orchestrator/context/deferred-tools.mjs +72 -67
- package/src/runtime/agent/orchestrator/context/skill-catalog.mjs +54 -1
- package/src/runtime/agent/orchestrator/mcp/agent-connectors.mjs +3 -3
- package/src/runtime/agent/orchestrator/mcp/client-transport.mjs +176 -72
- package/src/runtime/agent/orchestrator/mcp/client.mjs +19 -0
- package/src/runtime/agent/orchestrator/providers/account-pool.mjs +181 -133
- package/src/runtime/agent/orchestrator/providers/admission-scheduler.mjs +42 -6
- package/src/runtime/agent/orchestrator/providers/anthropic-cache-warm.mjs +165 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth-client-version.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth-credentials.mjs +4 -1
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth-request/gzip-post.mjs +14 -14
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +145 -66
- package/src/runtime/agent/orchestrator/providers/anthropic-overload-fallback.mjs +25 -3
- package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +64 -33
- package/src/runtime/agent/orchestrator/providers/anthropic.mjs +135 -87
- package/src/runtime/agent/orchestrator/providers/antigravity-transport.mjs +123 -102
- package/src/runtime/agent/orchestrator/providers/cache-warm-admission.mjs +90 -0
- package/src/runtime/agent/orchestrator/providers/cursor-wire-transport.mjs +25 -9
- package/src/runtime/agent/orchestrator/providers/cursor.mjs +22 -13
- package/src/runtime/agent/orchestrator/providers/lib/oauth-token-utils.mjs +17 -3
- package/src/runtime/agent/orchestrator/providers/media-normalization.mjs +52 -10
- package/src/runtime/agent/orchestrator/providers/model-proxy-channel.d.mts +8 -1
- package/src/runtime/agent/orchestrator/providers/model-proxy-channel.mjs +1 -0
- package/src/runtime/agent/orchestrator/providers/npm-cli-version.mjs +11 -3
- package/src/runtime/agent/orchestrator/providers/oauth-credential-probes.mjs +2 -1
- package/src/runtime/agent/orchestrator/providers/openai-oauth-tokens.mjs +10 -9
- package/src/runtime/agent/orchestrator/providers/openai-ws-send/send-context.mjs +22 -3
- package/src/runtime/agent/orchestrator/providers/openai-ws-send-attempts/attempt-context.mjs +43 -5
- package/src/runtime/agent/orchestrator/providers/openai-ws-send-attempts/handshake-failure.mjs +3 -1
- package/src/runtime/agent/orchestrator/providers/openai-ws-send-attempts/stream-failure.mjs +6 -3
- package/src/runtime/agent/orchestrator/providers/provider-error-kind.mjs +65 -1
- package/src/runtime/agent/orchestrator/providers/quota-reading.mjs +38 -12
- package/src/runtime/agent/orchestrator/providers/registry.mjs +26 -0
- package/src/runtime/agent/orchestrator/providers/retry-executor.mjs +39 -7
- package/src/runtime/agent/orchestrator/providers/statusline-route-meta.mjs +6 -0
- package/src/runtime/agent/orchestrator/runtime-core/ask-user-tool-defs.mjs +12 -7
- package/src/runtime/agent/orchestrator/runtime-core/compaction-config-keys.mjs +62 -0
- package/src/runtime/agent/orchestrator/runtime-core/config-helpers.mjs +37 -16
- package/src/runtime/agent/orchestrator/runtime-core/deferred-tool-delta.mjs +100 -23
- package/src/runtime/agent/orchestrator/runtime-core/session-text.mjs +2 -0
- package/src/runtime/agent/orchestrator/runtime-core/tool-bridge.mjs +19 -4
- package/src/runtime/agent/orchestrator/runtime-core/tool-catalog.mjs +125 -30
- package/src/runtime/agent/orchestrator/session/cache-warm.mjs +792 -0
- package/src/runtime/agent/orchestrator/session/compact/runner.mjs +133 -22
- package/src/runtime/agent/orchestrator/session/compaction-archive-capture.mjs +475 -58
- package/src/runtime/agent/orchestrator/session/compaction-archive.mjs +42 -17
- package/src/runtime/agent/orchestrator/session/eager-dispatch.mjs +4 -1
- package/src/runtime/agent/orchestrator/session/human-gate/gate.mjs +11 -5
- package/src/runtime/agent/orchestrator/session/human-gate/outbound-policy.mjs +0 -2
- package/src/runtime/agent/orchestrator/session/human-gate/requests.mjs +36 -4
- package/src/runtime/agent/orchestrator/session/human-gate/store.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +18 -22
- package/src/runtime/agent/orchestrator/session/loop/fresh-context.mjs +32 -3
- package/src/runtime/agent/orchestrator/session/loop/loop-state.mjs +3 -1
- package/src/runtime/agent/orchestrator/session/loop/no-tool-turn.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/loop/route-fallback.mjs +17 -3
- package/src/runtime/agent/orchestrator/session/loop/send-phase.mjs +7 -2
- package/src/runtime/agent/orchestrator/session/loop/tool-exec/after-hook.mjs +2 -1
- package/src/runtime/agent/orchestrator/session/loop/tool-exec/before-hook.mjs +3 -2
- package/src/runtime/agent/orchestrator/session/loop/tool-exec/dispatch.mjs +16 -2
- package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +16 -1
- package/src/runtime/agent/orchestrator/session/loop/tool-exposure-migration.mjs +10 -0
- package/src/runtime/agent/orchestrator/session/loop/tool-helpers.mjs +3 -10
- package/src/runtime/agent/orchestrator/session/loop/tool-phase.mjs +12 -3
- package/src/runtime/agent/orchestrator/session/manager/ask-queue.mjs +12 -2
- package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +19 -1
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-execute.mjs +133 -90
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-state.mjs +31 -4
- package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +30 -18
- package/src/runtime/agent/orchestrator/session/manager/owner-route-fallbacks.mjs +23 -13
- package/src/runtime/agent/orchestrator/session/manager/pending-message-entry.mjs +11 -2
- package/src/runtime/agent/orchestrator/session/manager/provider-replay.mjs +13 -0
- package/src/runtime/agent/orchestrator/session/manager/route-fallback-chain.mjs +129 -0
- package/src/runtime/agent/orchestrator/session/manager/runtime-liveness.mjs +20 -2
- package/src/runtime/agent/orchestrator/session/manager/session-close.mjs +2 -0
- package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +8 -0
- package/src/runtime/agent/orchestrator/session/manager/session-record.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/manager/tool-resolution.mjs +7 -2
- package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint-journal.mjs +102 -15
- package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint.mjs +18 -5
- package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +19 -8
- package/src/runtime/agent/orchestrator/session/prompt-cache-idle-gaps.mjs +113 -0
- package/src/runtime/agent/orchestrator/session/prompt-cache-touch-store.mjs +127 -0
- package/src/runtime/agent/orchestrator/session/routine-result.mjs +36 -9
- package/src/runtime/agent/orchestrator/session/sensitive-projection.mjs +100 -0
- package/src/runtime/agent/orchestrator/session/sensitive-values.mjs +257 -0
- package/src/runtime/agent/orchestrator/session/side-question-store.mjs +20 -7
- package/src/runtime/agent/orchestrator/session/side-question.mjs +18 -0
- package/src/runtime/agent/orchestrator/session/skill-update-notice.mjs +60 -0
- package/src/runtime/agent/orchestrator/session/store/live-state.mjs +6 -3
- package/src/runtime/agent/orchestrator/session/store/save-delta.mjs +9 -5
- package/src/runtime/agent/orchestrator/session/store/serialize.mjs +9 -8
- package/src/runtime/agent/orchestrator/session/store/session-persistence-events.mjs +29 -0
- package/src/runtime/agent/orchestrator/session/store-agent-worker-rows.mjs +8 -6
- package/src/runtime/agent/orchestrator/session/store-summary-catalog.mjs +12 -2
- package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +44 -14
- package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +84 -3
- package/src/runtime/agent/orchestrator/session/stored-attachment-history.mjs +32 -0
- package/src/runtime/agent/orchestrator/session/thinking-binding.mjs +108 -0
- package/src/runtime/agent/orchestrator/session/tool-batch/pre-dispatch.mjs +2 -2
- package/src/runtime/agent/orchestrator/session/tool-batch/record-outcome.mjs +2 -1
- package/src/runtime/agent/orchestrator/session/transcript-restore/restore.mjs +18 -1
- package/src/runtime/agent/orchestrator/session/transcript-restore/tool-result-status.mjs +9 -7
- package/src/runtime/agent/orchestrator/session/transcript-restore/tool-result-text.mjs +2 -2
- package/src/runtime/agent/orchestrator/session/turn-failure.mjs +5 -1
- package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +5 -2
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/backgrounded-result.mjs +12 -1
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/misuse-guard.mjs +240 -0
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/result-format.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/shell-cwd.mjs +38 -0
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +25 -5
- package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +65 -7
- package/src/runtime/agent/orchestrator/tools/builtin/shell-runtime.mjs +7 -1
- package/src/runtime/agent/orchestrator/tools/shell-command.mjs +36 -13
- package/src/runtime/browser-bridge/action-schema.mjs +13 -2
- package/src/runtime/browser-bridge/client.mjs +73 -8
- package/src/runtime/browser-bridge/input-fields.mjs +8 -9
- package/src/runtime/browser-bridge/mail-links.mjs +107 -0
- package/src/runtime/browser-bridge/tool-defs.mjs +1 -1
- package/src/runtime/channels/lib/config.mjs +6 -61
- package/src/runtime/channels/lib/crash-log.mjs +1 -1
- package/src/runtime/channels/lib/event-pipeline.mjs +4 -19
- package/src/runtime/channels/lib/event-queue.mjs +3 -18
- package/src/runtime/channels/lib/owned-runtime/automation-services.mjs +2 -2
- package/src/runtime/channels/lib/owned-runtime/automation-start.mjs +3 -4
- package/src/runtime/channels/lib/owned-runtime/config-reload.mjs +4 -59
- package/src/runtime/channels/lib/owned-runtime/owned-start.mjs +33 -115
- package/src/runtime/channels/lib/owned-runtime/owned-stop.mjs +5 -22
- package/src/runtime/channels/lib/owned-runtime/state.mjs +2 -8
- package/src/runtime/channels/lib/owned-runtime.mjs +6 -23
- package/src/runtime/channels/lib/parent-bridge.mjs +2 -2
- package/src/runtime/channels/lib/runtime-paths.mjs +4 -43
- package/src/runtime/channels/lib/scheduler.mjs +33 -147
- package/src/runtime/channels/lib/schedules-db.mjs +34 -11
- package/src/runtime/channels/lib/status-snapshot.mjs +1 -1
- package/src/runtime/channels/lib/tool-dispatch.mjs +1 -2
- package/src/runtime/channels/lib/webhooks-db.mjs +5 -9
- package/src/runtime/channels/lib/worker-bootstrap.mjs +0 -1
- package/src/runtime/channels/lib/worker-ipc.mjs +0 -1
- package/src/runtime/channels/lib/worker-main.mjs +10 -34
- package/src/runtime/computer-bridge/action-schema.mjs +42 -1
- package/src/runtime/computer-bridge/actions.mjs +9 -1
- package/src/runtime/computer-bridge/client.mjs +180 -8
- package/src/runtime/computer-bridge/core-actions.mjs +2 -2
- package/src/runtime/computer-bridge/error-recovery.mjs +27 -2
- package/src/runtime/computer-bridge/linux-readiness.mjs +65 -23
- package/src/runtime/computer-bridge/pending-continuation.mjs +1 -1
- package/src/runtime/connectors/connector-accounts-mcp.mjs +230 -0
- package/src/runtime/connectors/gmail-app-password.mjs +172 -39
- package/src/runtime/connectors/google-mcp.mjs +10 -12
- package/src/runtime/memory/lib/pg/adapter.mjs +77 -70
- package/src/runtime/memory/lib/pg/supervisor.mjs +24 -12
- package/src/runtime/memory/lib/runtime-fetcher.mjs +23 -5
- package/src/runtime/session-search/session-search-db.mjs +73 -9
- package/src/runtime/session-search/session-search-ingest.mjs +110 -20
- package/src/runtime/shared/abort-race.mjs +89 -0
- package/src/runtime/shared/agent-avatar.d.mts +0 -1
- package/src/runtime/shared/agent-kind.mjs +4 -3
- package/src/runtime/shared/bounded-download.mjs +8 -3
- package/src/runtime/shared/config.mjs +2 -13
- package/src/runtime/shared/connector-accounts.mjs +20 -10
- package/src/runtime/shared/connector-catalog.d.mts +29 -0
- package/src/runtime/shared/connector-catalog.mjs +150 -0
- package/src/runtime/shared/connector-triggers.mjs +22 -19
- package/src/runtime/shared/defaults-separation.mjs +2 -14
- package/src/runtime/shared/delegated-approval-host.mjs +103 -11
- package/src/runtime/shared/err-text.mjs +24 -15
- package/src/runtime/shared/file-permissions.d.mts +0 -8
- package/src/runtime/shared/file-permissions.mjs +0 -11
- package/src/runtime/shared/git-direct.test-support.mjs +19 -0
- package/src/runtime/shared/heartbeat-interval.d.mts +4 -0
- package/src/runtime/shared/heartbeat-interval.mjs +19 -0
- package/src/runtime/shared/idle-auto-clear-gate.mjs +104 -0
- package/src/runtime/shared/llm/usage-rollup.mjs +1 -1
- package/src/runtime/shared/mixy-presence.mjs +22 -0
- package/src/runtime/shared/orchestration.d.mts +2 -0
- package/src/runtime/shared/pg-schema-db.mjs +27 -16
- package/src/runtime/shared/profile-config.cjs +1 -1
- package/src/runtime/shared/prompt-origin.d.mts +4 -0
- package/src/runtime/shared/prompt-origin.mjs +21 -0
- package/src/runtime/shared/provider-accounts.mjs +7 -1
- package/src/runtime/shared/provider-display-name.mjs +29 -0
- package/src/runtime/shared/provider-timeout-phase.mjs +2 -0
- package/src/runtime/shared/routine-trigger.d.mts +2 -0
- package/src/runtime/shared/routine-trigger.mjs +10 -0
- package/src/runtime/shared/sensitive-path-rules.d.mts +2 -10
- package/src/runtime/shared/sensitive-path-rules.mjs +19 -41
- package/src/runtime/shared/session-activity-signal.mjs +34 -1
- package/src/runtime/shared/session-sidebar-host.d.mts +1 -1
- package/src/runtime/shared/session-sidebar-host.mjs +7 -3
- package/src/runtime/shared/session-turn-prelude.mjs +18 -0
- package/src/runtime/shared/tool-result-summary.mjs +1 -14
- package/src/runtime/shared/worktrees/test-support.mjs +9 -4
- package/src/runtime/terminal-bridge/client.mjs +10 -1
- package/src/runtime/visualize-bridge/client.mjs +27 -8
- package/src/runtime/visualize-bridge/tool-defs.mjs +22 -8
- package/src/runtime/visualize-bridge/tool-meta.d.mts +5 -0
- package/src/runtime/visualize-bridge/tool-meta.mjs +22 -0
- package/src/runtime/web-search/lib/fetch-pipeline.mjs +2 -2
- package/src/session-runtime/agent-home-delivery.mjs +448 -185
- package/src/session-runtime/agent-task-sessions.mjs +329 -180
- package/src/session-runtime/boot/apis.mjs +9 -0
- package/src/session-runtime/boot/infrastructure.mjs +4 -0
- package/src/session-runtime/boot/providers.mjs +3 -0
- package/src/session-runtime/boot/tools.mjs +6 -1
- package/src/session-runtime/config-lifecycle.mjs +6 -0
- package/src/session-runtime/connector-merge-migration.mjs +305 -0
- package/src/session-runtime/internal-tool-executor/ask-user-tool.mjs +132 -28
- package/src/session-runtime/internal-tool-executor/bridge-tools.mjs +2 -0
- package/src/session-runtime/lifecycle/inheritance.mjs +16 -3
- package/src/session-runtime/lifecycle/session-catalog.mjs +3 -1
- package/src/session-runtime/lifecycle/teardown/runtime-stops.mjs +50 -4
- package/src/session-runtime/lifecycle/teardown.mjs +23 -5
- package/src/session-runtime/mcp-glue/server-catalog.mjs +55 -3
- package/src/session-runtime/mcp-oauth-login.mjs +5 -3
- package/src/session-runtime/mixy-heartbeat.mjs +153 -33
- package/src/session-runtime/notification-bus.mjs +31 -117
- package/src/session-runtime/notification-completion-queue.mjs +54 -0
- package/src/session-runtime/notification-listeners.mjs +76 -0
- package/src/session-runtime/owned-automation-run.mjs +2 -1
- package/src/session-runtime/prewarm/channel-start.mjs +3 -7
- package/src/session-runtime/resource-mcp-api.mjs +143 -224
- package/src/session-runtime/resource-mcp-connector-accounts.mjs +163 -0
- package/src/session-runtime/resource-mcp-oauth-api.mjs +217 -0
- package/src/session-runtime/resource-mcp-secrets.mjs +82 -0
- package/src/session-runtime/resource-mcp-servers.mjs +78 -0
- package/src/session-runtime/resource-mcp-store.mjs +49 -0
- package/src/session-runtime/routine-owner.mjs +40 -9
- package/src/session-runtime/services/agent-tool/close-flow.mjs +3 -0
- package/src/session-runtime/services/agent-tool/spawn-preset.mjs +7 -2
- package/src/session-runtime/services/agent-tool/tag-registry.mjs +1 -0
- package/src/session-runtime/services/agent-tool/worker-index/awaiting-user-sync.mjs +51 -0
- package/src/session-runtime/services/agent-tool/worker-index/disposal.mjs +55 -0
- package/src/session-runtime/services/agent-tool/worker-index/released-store.mjs +123 -0
- package/src/session-runtime/services/agent-tool/worker-index/restart-settlement.mjs +112 -0
- package/src/session-runtime/services/agent-tool/worker-index/row-reads.mjs +36 -0
- package/src/session-runtime/services/agent-tool/worker-index/row-shape.mjs +2 -0
- package/src/session-runtime/services/agent-tool/worker-index/row-store.mjs +17 -4
- package/src/session-runtime/services/agent-tool/worker-index/row-writes.mjs +58 -0
- package/src/session-runtime/services/agent-tool/worker-index.mjs +41 -241
- package/src/session-runtime/services/agent-tool/worker-rows.mjs +3 -0
- package/src/session-runtime/services/channel-admin.mjs +7 -13
- package/src/session-runtime/services/channel-session-router.mjs +4 -2
- package/src/session-runtime/services/seeds.mjs +0 -28
- package/src/session-runtime/session-compaction-sync.mjs +27 -0
- package/src/session-runtime/session-create/session-options.mjs +1 -0
- package/src/session-runtime/settings-builtin-tools-api.mjs +11 -0
- package/src/session-runtime/settings-compaction-api.mjs +2 -40
- package/src/session-runtime/settings-system-api.mjs +13 -1
- package/src/session-runtime/setup-tool/agent-settings-actions.mjs +65 -0
- package/src/session-runtime/setup-tool/executor.mjs +145 -22
- package/src/session-runtime/setup-tool/extended-actions.mjs +8 -3
- package/src/session-runtime/setup-tool/session-route-actions.mjs +152 -0
- package/src/session-runtime/setup-tool/settings-contract.mjs +4 -3
- package/src/session-runtime/setup-tool/tool-defs.mjs +64 -5
- package/src/session-runtime/tool-policy/agent-profile-fold.mjs +17 -13
- package/src/session-runtime/tool-policy/policy-stage.mjs +16 -2
- package/src/session-runtime/turn/turn-run/ask-callbacks.mjs +1 -0
- package/src/session-runtime/workflow-agents-api/style-and-mode.mjs +4 -0
- package/src/standalone/channel-transport/client-registry/client-records.mjs +65 -0
- package/src/standalone/channel-transport/client-registry/client-registration.mjs +118 -0
- package/src/standalone/channel-transport/client-registry.mjs +24 -163
- package/src/standalone/channel-transport.mjs +1 -1
- package/src/standalone/daemon-channels-loader.mjs +10 -8
- package/src/standalone/daemon-session-control/list-page.mjs +72 -0
- package/src/standalone/daemon-session-control/request.mjs +51 -0
- package/src/standalone/daemon-session-control/room-host.mjs +57 -0
- package/src/standalone/daemon-session-control/route-control.mjs +48 -0
- package/src/standalone/daemon-session-control/send-turn.mjs +60 -0
- package/src/standalone/daemon-session-control/worktree-control.mjs +40 -0
- package/src/standalone/daemon-session-control.mjs +124 -188
- package/src/standalone/daemon.mjs +26 -3
- package/src/standalone/session-client.mjs +7 -3
- package/src/standalone/session-protocol.mjs +5 -0
- package/src/standalone/session-service/agent-tree/agent-child.mjs +1 -0
- package/src/standalone/session-service/quota-resume.mjs +3 -0
- package/src/standalone/session-service/retention/busy.mjs +65 -1
- package/src/standalone/session-service/session-calls/session-action.mjs +23 -2
- package/src/standalone/session-service/session-calls/session-views.mjs +8 -0
- package/src/standalone/session-tool-output.mjs +15 -7
- package/src/tui/app/model-options.mjs +3 -28
- package/src/tui/dist/index.mjs +58 -64
- package/src/tui/session/agent-job-feed/model-notification.mjs +3 -0
- package/src/tui/session/initial-state.mjs +3 -0
- package/src/tui/session/notice-surface.mjs +7 -1
- package/src/tui/session/queue-helpers.mjs +3 -42
- package/src/tui/session/session-api/routes-agent-definitions.mjs +14 -2
- package/src/tui/session/session-api/routes-write.mjs +5 -1
- package/src/tui/session/session-api/routes.mjs +1 -0
- package/src/tui/session/session-api/settings.mjs +17 -1
- package/src/tui/session/session-flow/auto-clear.mjs +4 -33
- package/src/tui/session/session-flow/drain-closed-queue.mjs +38 -0
- package/src/tui/session/session-flow/drain-enqueue.mjs +86 -0
- package/src/tui/session/session-flow/drain-retry.mjs +35 -0
- package/src/tui/session/session-flow/drain.mjs +118 -149
- package/src/tui/session/session-flow/queue/entry-shape.mjs +5 -0
- package/src/tui/session/session-flow/queue/take-entries.mjs +5 -2
- package/src/tui/session/tool-approval-delegation.mjs +64 -0
- package/src/tui/session/tool-approval-request.mjs +39 -0
- package/src/tui/session/tool-approval-settle.mjs +62 -0
- package/src/tui/session/tool-approval.mjs +69 -115
- package/src/tui/session/tui-steering-persist.mjs +8 -1
- package/src/tui/session/turn-stream.mjs +19 -2
- package/src/tui/session/turn.mjs +1 -0
- package/src/vendor/statusline/bin/statusline-route.mjs +1 -219
- package/src/vendor/statusline/src/gateway/route-meta.mjs +35 -13
- package/scripts/code-graph-description-contract.mjs +0 -188
- package/src/runtime/channels/lib/owned-runtime/ownership-refresh.mjs +0 -36
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
// MCP OAuth sign-in and sign-out of a configured server or of one account of a
|
|
2
|
+
// multi-account connector server: the interactive login flows
|
|
3
|
+
// (mcp-oauth-login.mjs), the transactional account switch, and the
|
|
4
|
+
// credential-free auth probe.
|
|
5
|
+
import { clean } from '../runtime/agent/orchestrator/runtime-core/session-text.mjs';
|
|
6
|
+
import { createMcpOAuthLogins } from './mcp-oauth-login.mjs';
|
|
7
|
+
import { connectorAccountServers, connectorAccountsOf } from '../runtime/shared/connector-catalog.mjs';
|
|
8
|
+
import { forgetConnectorAccountHealth } from '../runtime/connectors/connector-accounts-mcp.mjs';
|
|
9
|
+
import { accountTarget } from './resource-mcp-servers.mjs';
|
|
10
|
+
|
|
11
|
+
export function createMcpOAuthApi({
|
|
12
|
+
sync,
|
|
13
|
+
mcpStatus,
|
|
14
|
+
connectConfiguredMcp,
|
|
15
|
+
clearMcpServerOAuth,
|
|
16
|
+
reloadFullConfig,
|
|
17
|
+
awaitKeychainPrewarm,
|
|
18
|
+
resolveMcpCatalog,
|
|
19
|
+
cancelMcpLogin,
|
|
20
|
+
completeMcpLogin,
|
|
21
|
+
getMcpServerOAuthState,
|
|
22
|
+
probeMcpServerAuth,
|
|
23
|
+
startMcpServerOAuthSwitch,
|
|
24
|
+
commitMcpServerOAuthSwitch,
|
|
25
|
+
}) {
|
|
26
|
+
const reconnectOne = async (name) => {
|
|
27
|
+
const status = await connectConfiguredMcp({ only: name, enabled: true });
|
|
28
|
+
const row = status?.servers?.find((server) => server.name === name);
|
|
29
|
+
return { connected: row?.connected === true, error: row?.error || null };
|
|
30
|
+
};
|
|
31
|
+
// Explicit OAuth sign-in: the one interactive MCP connect. On success the
|
|
32
|
+
// other runtimes reconnect the server non-interactively with the new tokens.
|
|
33
|
+
const oauthLogins = createMcpOAuthLogins({
|
|
34
|
+
async connect(name, openBrowser) {
|
|
35
|
+
const status = await connectConfiguredMcp({ only: name, enabled: true, login: { openBrowser } });
|
|
36
|
+
const row = status?.servers?.find((server) => server.name === name);
|
|
37
|
+
return { connected: row?.connected === true, error: row?.error || null };
|
|
38
|
+
},
|
|
39
|
+
cancel: (name) => cancelMcpLogin(name),
|
|
40
|
+
completeCallback: completeMcpLogin,
|
|
41
|
+
// 계정 바꾸기, transactionally: the working connection stays until the new
|
|
42
|
+
// sign-in succeeded and connects; a cancel or failure changes nothing, and
|
|
43
|
+
// a new sign-in that cannot connect is rolled back.
|
|
44
|
+
// An account of a multi-account connector signs in the same way, any
|
|
45
|
+
// mode: beside its stored sign-in (if any), then its server reconnects.
|
|
46
|
+
async switchAccount(name, launch, attempt) {
|
|
47
|
+
const servers = resolveMcpCatalog().servers;
|
|
48
|
+
const target = accountTarget(servers, name);
|
|
49
|
+
const cfg = target ? target.accountCfg : servers[name];
|
|
50
|
+
const serverName = target ? target.serverName : name;
|
|
51
|
+
// 연결 / 다시 인증 of a signed-in account: its stored sign-in (refreshed
|
|
52
|
+
// as needed) is tried first; the browser opens only when that fails.
|
|
53
|
+
if (target && launch.accountMode !== 'switch' && getMcpServerOAuthState(cfg)?.signedIn === true) {
|
|
54
|
+
const repaired = await reconnectOne(serverName);
|
|
55
|
+
if (repaired.connected && !connectorAccountError(name)) return repaired;
|
|
56
|
+
}
|
|
57
|
+
const switching = startMcpServerOAuthSwitch(cfg, launch, name);
|
|
58
|
+
attempt.abort = () => switching.cancel();
|
|
59
|
+
attempt.answer = (callbackUrl) => switching.answer(callbackUrl);
|
|
60
|
+
const signIn = await switching.done;
|
|
61
|
+
// The same account twice would leave `account` ambiguous.
|
|
62
|
+
const address = String(signIn?.identity?.account || '').toLowerCase();
|
|
63
|
+
if (target && address) {
|
|
64
|
+
const twin = connectorAccountServers(target.cfg).find(
|
|
65
|
+
(other) =>
|
|
66
|
+
other.name !== name && String(getMcpServerOAuthState(other)?.account || '').toLowerCase() === address
|
|
67
|
+
);
|
|
68
|
+
if (twin) return { connected: false, error: `${signIn.identity.account} is already connected` };
|
|
69
|
+
}
|
|
70
|
+
const rollback = await commitMcpServerOAuthSwitch(cfg, signIn);
|
|
71
|
+
const outcome = await reconnectOne(serverName);
|
|
72
|
+
const accountError = target ? connectorAccountError(name) : '';
|
|
73
|
+
if (outcome.connected && !accountError) return outcome;
|
|
74
|
+
await rollback();
|
|
75
|
+
await reconnectOne(serverName);
|
|
76
|
+
return { connected: false, error: accountError || outcome.error || 'The new sign-in could not connect' };
|
|
77
|
+
},
|
|
78
|
+
onComplete: (name) =>
|
|
79
|
+
sync.announce('mcp', {
|
|
80
|
+
detail: {
|
|
81
|
+
mcpChanges: [{ name: accountTarget(resolveMcpCatalog().servers, name)?.serverName || name, enabled: true }],
|
|
82
|
+
},
|
|
83
|
+
}),
|
|
84
|
+
});
|
|
85
|
+
/** The effective config of a server that signs in with MCP OAuth (HTTP/SSE
|
|
86
|
+
* without configured authentication). */
|
|
87
|
+
const oauthServerConfig = async (name) => {
|
|
88
|
+
const serverName = clean(name);
|
|
89
|
+
if (!serverName) throw new Error('MCP server name is required');
|
|
90
|
+
await awaitKeychainPrewarm();
|
|
91
|
+
reloadFullConfig();
|
|
92
|
+
const cfg = resolveMcpCatalog().servers[serverName];
|
|
93
|
+
if (!cfg) throw new Error(`MCP server not configured: ${serverName}`);
|
|
94
|
+
if (!getMcpServerOAuthState(cfg)) throw new Error(`MCP server "${serverName}" does not use OAuth sign-in`);
|
|
95
|
+
return { serverName, cfg };
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
/** The sign-in account `name` addresses on a multi-account connector
|
|
99
|
+
* server — an account's name, or the server's name when it has exactly one
|
|
100
|
+
* sign-in account (`every`: all of them, for a sign-out) — as
|
|
101
|
+
* `{ serverName, cfg, account, accountCfg, signOut: [accountCfg…] }`;
|
|
102
|
+
* null for any other server. */
|
|
103
|
+
const signInAccount = async (name, { every = false } = {}) => {
|
|
104
|
+
const wanted = clean(name);
|
|
105
|
+
if (!wanted) throw new Error('MCP server name is required');
|
|
106
|
+
await awaitKeychainPrewarm();
|
|
107
|
+
reloadFullConfig();
|
|
108
|
+
const servers = resolveMcpCatalog().servers;
|
|
109
|
+
const target = accountTarget(servers, wanted);
|
|
110
|
+
if (target) {
|
|
111
|
+
if (!getMcpServerOAuthState(target.accountCfg)) {
|
|
112
|
+
throw new Error(`MCP server "${wanted}" does not use OAuth sign-in`);
|
|
113
|
+
}
|
|
114
|
+
return { ...target, signOut: [target.accountCfg] };
|
|
115
|
+
}
|
|
116
|
+
const cfg = servers[wanted];
|
|
117
|
+
if (!connectorAccountsOf(cfg).length) return null;
|
|
118
|
+
const signIns = connectorAccountServers(cfg).filter((accountCfg) => getMcpServerOAuthState(accountCfg));
|
|
119
|
+
if (every && signIns.length) return { serverName: wanted, cfg, account: null, accountCfg: null, signOut: signIns };
|
|
120
|
+
if (signIns.length === 1) {
|
|
121
|
+
const account = connectorAccountsOf(cfg).find((entry) => entry.name === signIns[0].name);
|
|
122
|
+
return { serverName: wanted, cfg, account, accountCfg: signIns[0], signOut: signIns };
|
|
123
|
+
}
|
|
124
|
+
throw new Error(
|
|
125
|
+
signIns.length
|
|
126
|
+
? `MCP server "${wanted}" has several accounts; name the account to sign in`
|
|
127
|
+
: `MCP server "${wanted}" does not use OAuth sign-in`
|
|
128
|
+
);
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
/** What account `name` of a multi-account connector currently fails with ('' when fine). */
|
|
132
|
+
function connectorAccountError(name) {
|
|
133
|
+
for (const row of mcpStatus()?.servers || []) {
|
|
134
|
+
const account = (Array.isArray(row.accounts) ? row.accounts : []).find((entry) => entry.name === name);
|
|
135
|
+
if (account) return account.error || (account.authRequired ? 'Sign-in required' : '');
|
|
136
|
+
}
|
|
137
|
+
return '';
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return {
|
|
141
|
+
/** `{ flowId, state: 'pending', url }` once the browser login starts
|
|
142
|
+
* (`options.openBrowser === false` leaves opening `url` to the caller), or
|
|
143
|
+
* `{ flowId, state: 'complete' }` when stored credentials sufficed. */
|
|
144
|
+
async beginMcpOAuthLogin(name, options = {}) {
|
|
145
|
+
const account = await signInAccount(name);
|
|
146
|
+
if (account) {
|
|
147
|
+
if (account.cfg.enabled === false) throw new Error(`MCP server is disabled: ${account.serverName}`);
|
|
148
|
+
// Beside the stored sign-in, whatever the mode: the account's server keeps running meanwhile.
|
|
149
|
+
return oauthLogins.begin(account.account.name, { ...(options || {}), beside: true });
|
|
150
|
+
}
|
|
151
|
+
const { beside: _beside, ...ownOptions } = options || {};
|
|
152
|
+
options = ownOptions;
|
|
153
|
+
const { serverName, cfg } = await oauthServerConfig(name);
|
|
154
|
+
if (cfg.enabled === false) throw new Error(`MCP server is disabled: ${serverName}`);
|
|
155
|
+
// `mode`: `connect` (a normal authorization), `reauth` (repair the same
|
|
156
|
+
// connection; a refresh is tried first) or `switch` (계정 바꾸기).
|
|
157
|
+
if (options?.mode === 'switch' && !getMcpServerOAuthState(cfg)?.signedIn) {
|
|
158
|
+
throw new Error(`MCP server "${serverName}" is not signed in`);
|
|
159
|
+
}
|
|
160
|
+
return oauthLogins.begin(serverName, options);
|
|
161
|
+
},
|
|
162
|
+
/** `{ flowId, state: 'pending' | 'complete' | 'failed' | 'cancelled',
|
|
163
|
+
* step?, error?, reason? }`; with `{ since, waitMs }` it resolves on the
|
|
164
|
+
* flow's next change (mcp-oauth-login.mjs). */
|
|
165
|
+
getMcpOAuthLoginStatus(flowId, options) {
|
|
166
|
+
return oauthLogins.status(flowId, options);
|
|
167
|
+
},
|
|
168
|
+
cancelMcpOAuthLogin(flowId) {
|
|
169
|
+
return oauthLogins.cancel(flowId);
|
|
170
|
+
},
|
|
171
|
+
/** Remote / headless sign-in: complete the pending flow with the redirect
|
|
172
|
+
* URL pasted from the browser. `{ flowId, state: 'complete' | 'failed',
|
|
173
|
+
* error? }`; an invalid paste throws a fixed message and stays pending. */
|
|
174
|
+
completeMcpOAuthLogin(flowId, callbackUrl) {
|
|
175
|
+
return oauthLogins.complete(flowId, callbackUrl);
|
|
176
|
+
},
|
|
177
|
+
/** Forget the server's stored OAuth credentials (shared by every server
|
|
178
|
+
* with the same exact URL) and reconnect without a login, here and on
|
|
179
|
+
* peers, so it reports sign-in required. */
|
|
180
|
+
async signOutMcpServer(name) {
|
|
181
|
+
const account = await signInAccount(name, { every: true });
|
|
182
|
+
if (account) {
|
|
183
|
+
const accountName = account.account?.name || clean(name);
|
|
184
|
+
return oauthLogins.cancelServer(accountName, async () => {
|
|
185
|
+
let revoked = true;
|
|
186
|
+
for (const accountCfg of account.signOut) {
|
|
187
|
+
forgetConnectorAccountHealth(accountCfg.name);
|
|
188
|
+
revoked = (await clearMcpServerOAuth(accountCfg, { revoke: true }))?.revoked === true && revoked;
|
|
189
|
+
}
|
|
190
|
+
const status = await sync.reconnectMcpAndAnnounce({
|
|
191
|
+
only: account.serverName,
|
|
192
|
+
enabled: account.cfg.enabled !== false,
|
|
193
|
+
});
|
|
194
|
+
return { ...status, revoked };
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
const { serverName, cfg } = await oauthServerConfig(name);
|
|
198
|
+
// A begin issued meanwhile waits until the credentials are gone.
|
|
199
|
+
return oauthLogins.cancelServer(serverName, async () => {
|
|
200
|
+
const { revoked = false } = (await clearMcpServerOAuth(cfg, { revoke: true })) || {};
|
|
201
|
+
const status = await sync.reconnectMcpAndAnnounce({ only: serverName, enabled: cfg.enabled !== false });
|
|
202
|
+
return { ...status, revoked };
|
|
203
|
+
});
|
|
204
|
+
},
|
|
205
|
+
/** `{ auth: 'oauth' | 'none' | 'configured' | 'unreachable', error? }` for an
|
|
206
|
+
* HTTP/SSE config, without credentials or a browser. */
|
|
207
|
+
probeMcpServerAuth(cfg) {
|
|
208
|
+
if (!cfg || typeof cfg !== 'object' || Array.isArray(cfg)) throw new TypeError('MCP server config is required');
|
|
209
|
+
const probe = {};
|
|
210
|
+
for (const key of ['type', 'url', 'headers', 'bearer_token_env_var', 'env_http_headers']) {
|
|
211
|
+
if (cfg[key] !== undefined) probe[key] = cfg[key];
|
|
212
|
+
}
|
|
213
|
+
if (probe.type !== 'http' && probe.type !== 'sse') throw new TypeError("type must be 'http' or 'sse'");
|
|
214
|
+
return probeMcpServerAuth(probe);
|
|
215
|
+
},
|
|
216
|
+
};
|
|
217
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
// The keychain side of MCP server edits: storing a server's OAuth client
|
|
2
|
+
// secret or Gmail app password, and releasing a removed server's or account's
|
|
3
|
+
// sign-in once no other configured server shares it.
|
|
4
|
+
import {
|
|
5
|
+
gmailAppPasswordAccount,
|
|
6
|
+
gmailAppPasswordValue,
|
|
7
|
+
connectorAccountServers,
|
|
8
|
+
} from '../runtime/shared/connector-catalog.mjs';
|
|
9
|
+
import { forgetConnectorAccountHealth } from '../runtime/connectors/connector-accounts-mcp.mjs';
|
|
10
|
+
import { oauthUrlOf } from './resource-mcp-servers.mjs';
|
|
11
|
+
|
|
12
|
+
/** Every config `servers` connects with, a multi-account server's accounts
|
|
13
|
+
* as the one-account configs they are. */
|
|
14
|
+
const expandedConfigs = (servers) =>
|
|
15
|
+
Object.values(servers || {}).flatMap((cfg) => {
|
|
16
|
+
const accounts = connectorAccountServers(cfg);
|
|
17
|
+
return accounts.length ? accounts : [cfg];
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
export function createMcpServerSecrets({
|
|
21
|
+
gmailAppPassword,
|
|
22
|
+
saveOAuthClientSecret,
|
|
23
|
+
clearMcpServerOAuth,
|
|
24
|
+
resolveConfiguredMcpCatalog,
|
|
25
|
+
}) {
|
|
26
|
+
// The user's OAuth client secret (custom connector → 고급 설정 → 비밀값)
|
|
27
|
+
// goes to the keychain only; clearing the client id forgets it.
|
|
28
|
+
const storeOAuthClientSecret = async (input, cfg, previous = {}) => {
|
|
29
|
+
if (!cfg?.url) return;
|
|
30
|
+
const secret = typeof input.oauthClientSecret === 'string' ? input.oauthClientSecret.trim() : '';
|
|
31
|
+
if (cfg.oauthClientId && secret) await saveOAuthClientSecret(cfg.url, secret);
|
|
32
|
+
else if (!cfg.oauthClientId && previous.oauthClientId) await saveOAuthClientSecret(previous.url || cfg.url, '');
|
|
33
|
+
};
|
|
34
|
+
// Gmail with an app password (connector-catalog.mjs GMAIL_APP_PASSWORD): the
|
|
35
|
+
// password is checked with Gmail before anything is saved and goes to the
|
|
36
|
+
// keychain only. A new connection needs one; an edit without one keeps it.
|
|
37
|
+
const storeGmailAppPassword = async (input, cfg, required) => {
|
|
38
|
+
const account = gmailAppPasswordAccount(cfg);
|
|
39
|
+
if (!account) return;
|
|
40
|
+
const password = gmailAppPasswordValue(input.appPassword);
|
|
41
|
+
if (password) await gmailAppPassword.save(account, password);
|
|
42
|
+
else if (required || input.appPassword !== undefined) {
|
|
43
|
+
throw new Error('GMAIL_APP_PASSWORD_FORMAT: An app password has 16 characters.');
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
// A server's OAuth tokens and client registration go with it, unless another
|
|
47
|
+
// configured server (plugin or disabled ones included, even with MCP
|
|
48
|
+
// disabled for this runtime) still uses them through the same exact URL.
|
|
49
|
+
// `signIn` tells the caller what happened to the sign-in: `removed` (local
|
|
50
|
+
// secrets deleted, `revoked` when the provider confirmed the revocation
|
|
51
|
+
// too), `shared` (another server still uses it), or none (null).
|
|
52
|
+
const releaseOAuth = async (cfg) => {
|
|
53
|
+
const url = oauthUrlOf(cfg);
|
|
54
|
+
if (!url) return null;
|
|
55
|
+
if (expandedConfigs(resolveConfiguredMcpCatalog().servers).some((other) => oauthUrlOf(other) === url)) {
|
|
56
|
+
return { signIn: 'shared', revoked: false };
|
|
57
|
+
}
|
|
58
|
+
const { revoked = false } = (await clearMcpServerOAuth(cfg, { revoke: true })) || {};
|
|
59
|
+
return { signIn: 'removed', revoked };
|
|
60
|
+
};
|
|
61
|
+
// An app password is deleted once no configured server uses its account.
|
|
62
|
+
const forgetUnusedGmailAppPassword = async (account) => {
|
|
63
|
+
if (!account) return false;
|
|
64
|
+
const servers = expandedConfigs(resolveConfiguredMcpCatalog().servers);
|
|
65
|
+
if (servers.some((cfg) => gmailAppPasswordAccount(cfg) === account)) return false;
|
|
66
|
+
await gmailAppPassword.forget(account);
|
|
67
|
+
return true;
|
|
68
|
+
};
|
|
69
|
+
// A removed account keeps no sign-in another configured server or account
|
|
70
|
+
// does not share: `{ signIn, revoked }` as removeMcpServer reports it.
|
|
71
|
+
const releaseAccount = async (accountCfg) => {
|
|
72
|
+
forgetConnectorAccountHealth(accountCfg.name);
|
|
73
|
+
const address = gmailAppPasswordAccount(accountCfg);
|
|
74
|
+
if (address) {
|
|
75
|
+
const forgotten = await forgetUnusedGmailAppPassword(address);
|
|
76
|
+
return { signIn: forgotten ? 'removed' : 'shared', revoked: false };
|
|
77
|
+
}
|
|
78
|
+
return releaseOAuth(accountCfg);
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
return { storeOAuthClientSecret, storeGmailAppPassword, releaseOAuth, forgetUnusedGmailAppPassword, releaseAccount };
|
|
82
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
// Pure reads of an `mcpServers` map shared by the MCP server API units
|
|
2
|
+
// (resource-mcp-api.mjs): OAuth URLs, multi-account connector account lookup,
|
|
3
|
+
// name refusals and the merge of a partial server edit.
|
|
4
|
+
import { clean } from '../runtime/agent/orchestrator/runtime-core/session-text.mjs';
|
|
5
|
+
import { MCP_TIMEOUT_FIELD_GROUPS } from '../runtime/agent/orchestrator/runtime-core/plugin-mcp.mjs';
|
|
6
|
+
import { resolveMcpOAuthUrl } from '../runtime/agent/orchestrator/mcp/client-config.mjs';
|
|
7
|
+
import {
|
|
8
|
+
connectorAccountServer,
|
|
9
|
+
connectorAccountsOf,
|
|
10
|
+
connectorById,
|
|
11
|
+
connectorOfServer,
|
|
12
|
+
} from '../runtime/shared/connector-catalog.mjs';
|
|
13
|
+
|
|
14
|
+
/** The URL a server's OAuth credentials are stored under, or null. */
|
|
15
|
+
export function oauthUrlOf(cfg) {
|
|
16
|
+
try {
|
|
17
|
+
return resolveMcpOAuthUrl(cfg);
|
|
18
|
+
} catch {
|
|
19
|
+
// No valid remote URL: such a server never logged in.
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** The `mcpServers` map of a config as a fresh object (never the live one). */
|
|
25
|
+
export function mcpServersOf(config) {
|
|
26
|
+
return config.mcpServers && typeof config.mcpServers === 'object' ? { ...config.mcpServers } : {};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** The multi-account connector account `name` names in `servers`:
|
|
30
|
+
* `{ serverName, cfg, account, accountCfg }`, or null. */
|
|
31
|
+
export function accountTarget(servers, name) {
|
|
32
|
+
for (const [serverName, cfg] of Object.entries(servers || {})) {
|
|
33
|
+
const account = connectorAccountsOf(cfg).find((entry) => entry.name === name);
|
|
34
|
+
if (account) return { serverName, cfg, account, accountCfg: connectorAccountServer(cfg, account) };
|
|
35
|
+
}
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** The refusal of a new server name that is taken. A name a connector account
|
|
40
|
+
* holds is an account to the user (never a server), so it says so. */
|
|
41
|
+
export function nameTakenError(servers, name) {
|
|
42
|
+
const holder = accountTarget(servers, name);
|
|
43
|
+
const connector = holder && connectorById(connectorOfServer(holder.cfg));
|
|
44
|
+
return new Error(
|
|
45
|
+
connector
|
|
46
|
+
? `The name "${name}" is used by a ${connector.name} account. Choose another name.`
|
|
47
|
+
: `MCP server already exists: ${name}`
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const transportType = (type) => (type === 'streamable-http' ? 'http' : type);
|
|
52
|
+
|
|
53
|
+
/** UI editors send complete transports; setup may send a partial patch. Merge
|
|
54
|
+
* the patch over the existing entry, keeping omitted fields (including
|
|
55
|
+
* credentials) unless the transport itself changed. */
|
|
56
|
+
export function mergedServerInput(input, existing, originalName) {
|
|
57
|
+
const transportChanged =
|
|
58
|
+
(input.type && transportType(input.type) !== transportType(existing.type || (existing.url ? 'http' : 'stdio'))) ||
|
|
59
|
+
(input.command && existing.url) ||
|
|
60
|
+
(input.url && !existing.url);
|
|
61
|
+
const maps = {};
|
|
62
|
+
if (!transportChanged) {
|
|
63
|
+
for (const key of ['env', 'headers', 'env_http_headers']) {
|
|
64
|
+
if (Object.hasOwn(input, key)) maps[key] = { ...(existing[key] || {}), ...input[key] };
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
// Existing timeouts are kept by mergeMcpServerConfig; the normalizer must only
|
|
68
|
+
// see (and validate) newly supplied ones, so a stored 0/'off' never blocks
|
|
69
|
+
// an unrelated save.
|
|
70
|
+
const { ...base } = transportChanged ? {} : existing;
|
|
71
|
+
for (const field of MCP_TIMEOUT_FIELD_GROUPS.flat()) delete base[field];
|
|
72
|
+
return {
|
|
73
|
+
...base,
|
|
74
|
+
...input,
|
|
75
|
+
...maps,
|
|
76
|
+
name: clean(input.name) || originalName,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
// The user's `mcpServers` as the MCP server API reads and writes it: every
|
|
2
|
+
// read already merged (one server per multi-account connector) and with
|
|
3
|
+
// adoptable plugin copies adopted; every write persisted with collision names
|
|
4
|
+
// pinned. Also the free-name and plugin-ownership checks over that registry.
|
|
5
|
+
import { withAdoptedCopies, withPinnedCollisionNames } from './mcp-glue/plugin-servers.mjs';
|
|
6
|
+
import { connectorAccountsOf, mergeConnectorAccountServers } from '../runtime/shared/connector-catalog.mjs';
|
|
7
|
+
import { mcpServersOf } from './resource-mcp-servers.mjs';
|
|
8
|
+
|
|
9
|
+
function pluginOwnedError(name, owner) {
|
|
10
|
+
return new Error(
|
|
11
|
+
`MCP server "${name}" is provided by plugin "${owner.id}" (server "${owner.key}"); ` +
|
|
12
|
+
'enable or disable it with set_mcp_enabled, or manage the plugin instead'
|
|
13
|
+
);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function createMcpServerStore({ getConfig, saveConfigAndAdopt, resolveMcpCatalog, pluginMcpServer }) {
|
|
17
|
+
/** The config with any adoptable plugin server copies already adopted, so
|
|
18
|
+
* every write also persists the adoption and never re-adds a copy. */
|
|
19
|
+
const adoptedConfig = () => withAdoptedCopies(getConfig(), resolveMcpCatalog().adoption);
|
|
20
|
+
|
|
21
|
+
/** Persist a new `mcpServers` map; project overrides never survive a global
|
|
22
|
+
* edit. A plugin server pushed off its name by the edit keeps the collision
|
|
23
|
+
* name it gets, so it does not flip again later. */
|
|
24
|
+
const saveServers = (servers) => {
|
|
25
|
+
const nextConfig = { ...adoptedConfig() };
|
|
26
|
+
delete nextConfig.mcpProjectOverrides;
|
|
27
|
+
saveConfigAndAdopt({ ...nextConfig, mcpServers: servers });
|
|
28
|
+
const saved = getConfig();
|
|
29
|
+
const pinned = withPinnedCollisionNames(saved, resolveMcpCatalog());
|
|
30
|
+
if (pinned !== saved) saveConfigAndAdopt(pinned);
|
|
31
|
+
};
|
|
32
|
+
// Every write starts from the merged form: one server per multi-account connector.
|
|
33
|
+
const userServers = () => mergeConnectorAccountServers(mcpServersOf(adoptedConfig())).servers;
|
|
34
|
+
/** A name no server, plugin server or account has yet: `base`, else `base-2`… */
|
|
35
|
+
const freeName = (servers, base) => {
|
|
36
|
+
const taken = new Set(
|
|
37
|
+
Object.entries(servers).flatMap(([name, cfg]) => [name, ...connectorAccountsOf(cfg).map((a) => a.name)])
|
|
38
|
+
);
|
|
39
|
+
const free = (name) => !taken.has(name) && !pluginMcpServer(name);
|
|
40
|
+
if (free(base)) return base;
|
|
41
|
+
for (let n = 2; ; n += 1) if (free(`${base}-${n}`)) return `${base}-${n}`;
|
|
42
|
+
};
|
|
43
|
+
const refusePluginServer = (name) => {
|
|
44
|
+
const owned = name ? pluginMcpServer(name) : null;
|
|
45
|
+
if (owned) throw pluginOwnedError(name, owned.owner);
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
return { adoptedConfig, saveServers, userServers, freeName, refusePluginServer };
|
|
49
|
+
}
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
* and a new routine cannot name it.
|
|
7
7
|
*/
|
|
8
8
|
import { MIXY_AGENT_ID } from '../runtime/agent/orchestrator/session/agent-home.mjs';
|
|
9
|
+
import { mixyExists } from '../runtime/shared/mixy-presence.mjs';
|
|
9
10
|
import { scheduleOwnerAgentId } from './agent-home-delivery.mjs';
|
|
10
11
|
|
|
11
12
|
/** `ownerProblem` codes and the error each one raises. */
|
|
@@ -15,7 +16,7 @@ export const ROUTINE_OWNER_PROBLEMS = Object.freeze({
|
|
|
15
16
|
});
|
|
16
17
|
|
|
17
18
|
let editor = null;
|
|
18
|
-
async function
|
|
19
|
+
async function editorApi() {
|
|
19
20
|
if (!editor) {
|
|
20
21
|
const [{ getPluginData }, { createAgentEditorApi }] = await Promise.all([
|
|
21
22
|
import('../runtime/agent/orchestrator/config.mjs'),
|
|
@@ -23,21 +24,51 @@ async function defaultProbe() {
|
|
|
23
24
|
]);
|
|
24
25
|
editor = createAgentEditorApi({ cfgMod: { getPluginData } });
|
|
25
26
|
}
|
|
27
|
+
return editor;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Whether a stored schedule or webhook belongs to Mixy (ownerless rows do).
|
|
31
|
+
* Without a PostgreSQL cluster none exists, and none is started for this. */
|
|
32
|
+
export async function mixyOwnsRoutines() {
|
|
33
|
+
const [{ pgClusterExists }, { resolvePluginData }] = await Promise.all([
|
|
34
|
+
import('../runtime/memory/lib/pg/adapter.mjs'),
|
|
35
|
+
import('../runtime/shared/plugin-paths.mjs'),
|
|
36
|
+
]);
|
|
37
|
+
if (!pgClusterExists(resolvePluginData())) return false;
|
|
38
|
+
const [{ listSchedules }, { listEndpoints }] = await Promise.all([
|
|
39
|
+
import('../runtime/channels/lib/schedules-db.mjs'),
|
|
40
|
+
import('../runtime/channels/lib/webhooks-db.mjs'),
|
|
41
|
+
]);
|
|
42
|
+
const [schedules, webhooks] = await Promise.all([listSchedules(), listEndpoints()]);
|
|
43
|
+
return [...schedules, ...webhooks].some((row) => scheduleOwnerAgentId(row) === MIXY_AGENT_ID);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Whether Mixy exists (mixy-presence.mjs, the one rule), from `status`
|
|
47
|
+
* (getLeadStatus) or a fresh read of it. `ownsRoutine`: the caller holds a
|
|
48
|
+
* stored routine of hers, so her routines need no read. Rejects when they
|
|
49
|
+
* cannot be read and nothing else decides. */
|
|
50
|
+
export async function readMixyExists(status = null, { ownsRoutine = false } = {}) {
|
|
51
|
+
return mixyExists(status ?? (await editorApi()).getLeadStatus(), ownsRoutine ? async () => true : mixyOwnsRoutines);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async function defaultProbe() {
|
|
55
|
+
const api = await editorApi();
|
|
26
56
|
return {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
mixyExists: () => editor.getLeadStatus().created !== false,
|
|
30
|
-
presence: (agentId) => editor.agentDefinitionPresence([agentId])[agentId],
|
|
57
|
+
mixyExists: (ownsRoutine) => readMixyExists(null, { ownsRoutine }),
|
|
58
|
+
presence: (agentId) => api.agentDefinitionPresence([agentId])[agentId],
|
|
31
59
|
};
|
|
32
60
|
}
|
|
33
61
|
|
|
34
62
|
/** Why `ownerAgentId` (none = Mixy) cannot own or run a routine: a
|
|
35
|
-
* ROUTINE_OWNER_PROBLEMS code, '' when it can.
|
|
36
|
-
*
|
|
37
|
-
|
|
63
|
+
* ROUTINE_OWNER_PROBLEMS code, '' when it can. `stored`: asked for a stored
|
|
64
|
+
* routine of that owner (a run, a list row). A routine Mixy owns is itself
|
|
65
|
+
* evidence of her, so only `created: false` pauses one; a new one needs her
|
|
66
|
+
* to exist. Only a proven deletion counts for a bot; a read fault never
|
|
67
|
+
* pauses anything (callers take a rejection as no problem). */
|
|
68
|
+
export async function routineOwnerProblem(ownerAgentId, probe = null, { stored = false } = {}) {
|
|
38
69
|
const owner = scheduleOwnerAgentId({ ownerAgentId });
|
|
39
70
|
const check = probe || (await defaultProbe());
|
|
40
|
-
if (owner === MIXY_AGENT_ID) return check.mixyExists() ? '' : 'no-mixy';
|
|
71
|
+
if (owner === MIXY_AGENT_ID) return (await check.mixyExists(stored)) ? '' : 'no-mixy';
|
|
41
72
|
return check.presence(owner) === 'deleted' ? 'owner-deleted' : '';
|
|
42
73
|
}
|
|
43
74
|
|
|
@@ -92,6 +92,9 @@ export function createCloseFlow({ mgr, registry, views }) {
|
|
|
92
92
|
for (const descendantSessionId of descendantSessionIds) {
|
|
93
93
|
registry.removeWorkerRow({ sessionId: descendantSessionId });
|
|
94
94
|
}
|
|
95
|
+
// The sessions stay open (a send reuses them), so a later scan, even
|
|
96
|
+
// after a restart, would list them again: remember the owner closed them.
|
|
97
|
+
registry.markReleased([sessionId, ...descendantSessionIds]);
|
|
95
98
|
}
|
|
96
99
|
return { closed: ok, tag, sessionId, task_id: task?.taskId || null };
|
|
97
100
|
}
|
|
@@ -31,6 +31,9 @@ export function resolveAgentSpawnPreset(config, args = {}) {
|
|
|
31
31
|
const candidates = !clean(args.preset) ? configuredAgentRouteCandidates(config, agentName) : [];
|
|
32
32
|
const agentRoute = candidates.map((candidate) => normalizeAgentRoute(candidate)).find(Boolean) || null;
|
|
33
33
|
const storedFallbacks = candidates.find((candidate) => normalizeAgentRoute(candidate))?.fallbacks;
|
|
34
|
+
// Where the spawned session reads its fallback chain at each hop
|
|
35
|
+
// (route-fallback-chain.mjs): this agent's route, else Main's.
|
|
36
|
+
const fallbackSource = { agent: agentName };
|
|
34
37
|
if (agentRoute) {
|
|
35
38
|
return {
|
|
36
39
|
presetName: agentPresetName(agentName),
|
|
@@ -41,6 +44,7 @@ export function resolveAgentSpawnPreset(config, args = {}) {
|
|
|
41
44
|
provider: agentRoute.provider,
|
|
42
45
|
model: agentRoute.model,
|
|
43
46
|
...(storedFallbacks?.length ? { fallbacks: storedFallbacks.map(normalizeAgentRoute) } : {}),
|
|
47
|
+
fallbackSource,
|
|
44
48
|
effort: agentRoute.effort,
|
|
45
49
|
fast: agentRoute.fast === true,
|
|
46
50
|
...(agentRoute.autoEffort === false ? { autoEffort: false } : {}),
|
|
@@ -54,12 +58,13 @@ export function resolveAgentSpawnPreset(config, args = {}) {
|
|
|
54
58
|
|
|
55
59
|
const mainPreset = !clean(args.preset) ? findPreset(config, config?.default) : null;
|
|
56
60
|
if (normalizeAgentRoute(mainPreset)) {
|
|
57
|
-
return { presetName: mainPreset.id || mainPreset.name, preset: mainPreset };
|
|
61
|
+
return { presetName: mainPreset.id || mainPreset.name, preset: { ...mainPreset, fallbackSource } };
|
|
58
62
|
}
|
|
59
63
|
|
|
60
64
|
const presetName = clean(args.preset);
|
|
61
65
|
if (!presetName) throw new Error(`agent: agent "${agentName}" has no Main model assignment`);
|
|
62
|
-
const
|
|
66
|
+
const stored = findPreset(config, presetName);
|
|
67
|
+
const preset = stored ? { ...stored, fallbackSource: { preset: presetName } } : synthesizePreset(config, presetName);
|
|
63
68
|
if (!preset) throw new Error(`agent: preset "${presetName}" not found`);
|
|
64
69
|
if (!normalizeAgentRoute(preset)) throw new Error(`agent: preset "${presetName}" has no complete route`);
|
|
65
70
|
return { presetName, preset };
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
// worker-index/awaiting-user-sync.mjs
|
|
2
|
+
// A worker row's approval wait (`awaitingUser`), fenced to its session's turn
|
|
3
|
+
// and kept in step with the runtime this process runs for that session. Owns
|
|
4
|
+
// the sessions whose deferred rows still wait in the mutation batch.
|
|
5
|
+
import { clean } from '../helpers.mjs';
|
|
6
|
+
import { applyAwaitingUser, awaitingUserWouldChange } from './approval-wait.mjs';
|
|
7
|
+
|
|
8
|
+
export function createAwaitingUserSync(store, mgr) {
|
|
9
|
+
// Sessions whose deferred rows still wait in the batch; reconciled once the
|
|
10
|
+
// batch lands in the store.
|
|
11
|
+
const unreconciled = new Set();
|
|
12
|
+
|
|
13
|
+
/** Persist (or clear) a worker's approval wait as a field update fenced to
|
|
14
|
+
* the session and its turn (`turnAt`, ms): a row whose turn started later is
|
|
15
|
+
* left alone. Writes only when a row of that session exists and would
|
|
16
|
+
* change, so a Lead's own approvals cost nothing; a row that is not in the
|
|
17
|
+
* store yet takes the wait when it lands (reconcileAwaitingUser). */
|
|
18
|
+
function setAwaitingUser(sessionId, awaitingUser, { turnAt = 0 } = {}) {
|
|
19
|
+
const fence = {
|
|
20
|
+
sessionId: clean(sessionId),
|
|
21
|
+
awaitingUser: awaitingUser === 'approval' ? 'approval' : null,
|
|
22
|
+
turnAt: Number(turnAt) || 0,
|
|
23
|
+
};
|
|
24
|
+
if (!fence.sessionId || !awaitingUserWouldChange(store.readAll(), fence)) return false;
|
|
25
|
+
store.update((byKey) => applyAwaitingUser(byKey, fence));
|
|
26
|
+
return true;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** The runtime of a session this process runs is the authority on its
|
|
30
|
+
* approval wait (the runtime-liveness counter behind `awaitingUser`): every
|
|
31
|
+
* row it creates or refreshes takes that state, so a missed or stale
|
|
32
|
+
* transition heals on the next write. No runtime here: not ours to judge. */
|
|
33
|
+
function reconcileAwaitingUser(sessionId) {
|
|
34
|
+
const runtime = mgr.getSessionRuntime?.(sessionId);
|
|
35
|
+
if (runtime) setAwaitingUser(sessionId, runtime.awaitingUser, { turnAt: runtime.askStartedAt });
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** A deferred row of `sessionId` is queued; reconciled when its batch lands. */
|
|
39
|
+
function deferReconcile(sessionId) {
|
|
40
|
+
unreconciled.add(sessionId);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** The batch landed: reconcile every session whose deferred row it carried. */
|
|
44
|
+
function reconcileDeferred() {
|
|
45
|
+
const ids = [...unreconciled];
|
|
46
|
+
unreconciled.clear();
|
|
47
|
+
for (const id of ids) reconcileAwaitingUser(id);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return { setAwaitingUser, reconcileAwaitingUser, deferReconcile, reconcileDeferred };
|
|
51
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
// worker-index/disposal.mjs
|
|
2
|
+
// Disposing a worker index: queued rows persist first, then `teardown`
|
|
3
|
+
// releases its registrations; a failed drain leaves them for a retry. Owns
|
|
4
|
+
// the disposed / in-flight dispose state.
|
|
5
|
+
export function createIndexDisposal({ batch, store, teardown }) {
|
|
6
|
+
let disposed = false;
|
|
7
|
+
let disposePromise = null;
|
|
8
|
+
|
|
9
|
+
async function drainThenTeardown() {
|
|
10
|
+
batch.flush();
|
|
11
|
+
await store.flush();
|
|
12
|
+
teardown();
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** Persist queued rows, then release the exit registration. Active rows are
|
|
16
|
+
* NOT settled: they are keyed by process pid, which every inline runtime in
|
|
17
|
+
* the daemon shares, so settling here would idle other runtimes' workers.
|
|
18
|
+
* When the sync flush cannot run (this process's async writer holds the
|
|
19
|
+
* lock) the exit hook and store ref stay until the async drain settles. */
|
|
20
|
+
function dispose() {
|
|
21
|
+
if (disposed) return;
|
|
22
|
+
disposed = true;
|
|
23
|
+
let flushed = false;
|
|
24
|
+
try {
|
|
25
|
+
batch.flush();
|
|
26
|
+
flushed = store.flushSync();
|
|
27
|
+
} catch {}
|
|
28
|
+
if (flushed) {
|
|
29
|
+
teardown();
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
disposePromise = drainThenTeardown().catch(reopenAfterFailedDrain);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** A failed drain tears nothing down; allow a later dispose to retry it. */
|
|
36
|
+
function reopenAfterFailedDrain() {
|
|
37
|
+
disposed = false;
|
|
38
|
+
disposePromise = null;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** Awaitable dispose: drain everything, then tear down. A failed drain
|
|
42
|
+
* rejects and leaves the registrations in place. */
|
|
43
|
+
function disposeAsync() {
|
|
44
|
+
if (!disposePromise) {
|
|
45
|
+
disposed = true;
|
|
46
|
+
disposePromise = drainThenTeardown().catch((error) => {
|
|
47
|
+
reopenAfterFailedDrain();
|
|
48
|
+
throw error;
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
return disposePromise;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return { dispose, disposeAsync };
|
|
55
|
+
}
|