mixdog 1.2.1 → 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/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 +1 -0
- package/src/repl/slash-commands.mjs +16 -1
- 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/mcp/client-config.mjs +36 -7
- package/src/runtime/agent/orchestrator/mcp/client-handshake.mjs +49 -10
- package/src/runtime/agent/orchestrator/mcp/client-transport.mjs +40 -3
- package/src/runtime/agent/orchestrator/mcp/client.mjs +230 -96
- 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/providers/account-pool.mjs +188 -29
- 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 +34 -7
- 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/command-code-oauth.mjs +3 -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/effort-configuration.mjs +77 -17
- 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 +56 -23
- 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/mimo-oauth.mjs +3 -0
- package/src/runtime/agent/orchestrator/providers/model-list-sanitize.mjs +2 -1
- package/src/runtime/agent/orchestrator/providers/muse-code-oauth.mjs +4 -9
- package/src/runtime/agent/orchestrator/providers/openai-compat-chat-send.mjs +4 -2
- 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-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 +4 -2
- package/src/runtime/agent/orchestrator/providers/openai-compat-xai.mjs +23 -14
- package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +17 -3
- 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 +31 -20
- package/src/runtime/agent/orchestrator/providers/provider-error-kind.mjs +581 -0
- 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/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 +1 -97
- package/src/runtime/agent/orchestrator/session/catalog-db/schema.mjs +3 -1
- package/src/runtime/agent/orchestrator/session/loop/send-phase.mjs +4 -0
- package/src/runtime/agent/orchestrator/session/loop/step-auto-effort.mjs +102 -18
- package/src/runtime/agent/orchestrator/session/loop/tool-phase.mjs +3 -1
- package/src/runtime/agent/orchestrator/session/manager/ask-queue.mjs +30 -2
- package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +11 -2
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-execute.mjs +41 -21
- package/src/runtime/agent/orchestrator/session/manager/pending-message-entry.mjs +5 -10
- 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/send-with-recovery.mjs +155 -29
- package/src/runtime/agent/orchestrator/session/store/save-worker.mjs +6 -3
- 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 +2 -0
- package/src/runtime/agent/orchestrator/session/store-summary-fields.mjs +5 -0
- package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +3 -0
- package/src/runtime/agent/orchestrator/session/transcript-restore/restore.mjs +20 -1
- 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/builtin-tools.mjs +5 -5
- package/src/runtime/memory/lib/pg/process.mjs +34 -24
- package/src/runtime/memory/lib/pg/supervisor.mjs +16 -13
- package/src/runtime/office/README.md +1 -1
- package/src/runtime/office/shared/values.mjs +0 -5
- 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/llm/anthropic-betas.mjs +4 -3
- package/src/runtime/shared/llm/anthropic-effort.mjs +123 -134
- package/src/runtime/shared/llm/anthropic-thinking-contract.mjs +50 -14
- package/src/runtime/shared/llm/model-catalog.mjs +53 -40
- package/src/runtime/shared/llm/openai-daybreak.mjs +4 -16
- package/src/runtime/shared/llm/provider-catalog-registry.mjs +11 -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/quota-resume-store.mjs +11 -0
- package/src/runtime/shared/session-baseline-store.mjs +960 -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/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 +135 -46
- package/src/runtime/shared/worktrees/contract.mjs +0 -24
- package/src/runtime/shared/worktrees/test-support.mjs +19 -4
- package/src/session-runtime/boot/apis.mjs +6 -0
- package/src/session-runtime/boot/infrastructure.mjs +6 -0
- 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 +29 -9
- package/src/session-runtime/mcp-glue/server-catalog.mjs +20 -3
- package/src/session-runtime/mcp-glue.mjs +3 -0
- 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/provider-models/model-meta.mjs +12 -3
- 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 +4 -0
- package/src/session-runtime/resource-mcp-api.mjs +98 -2
- package/src/session-runtime/runtime-review-api.mjs +13 -0
- 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/settings-compaction-api.mjs +1 -0
- package/src/standalone/daemon-drain-policy.mjs +8 -0
- package/src/standalone/daemon-telemetry.mjs +55 -0
- package/src/standalone/daemon.mjs +3 -7
- package/src/standalone/session-protocol.mjs +7 -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-api.mjs +5 -0
- package/src/standalone/session-service.mjs +1 -0
- package/src/tui/app/app-view.jsx +1 -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.mjs +2 -1
- package/src/tui/app/model-options.mjs +11 -16
- package/src/tui/app/slash-commands.mjs +7 -2
- package/src/tui/app/slash-dispatch/panel-commands.mjs +16 -1
- package/src/tui/components/StatusLine.jsx +2 -0
- 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/dist/index.mjs +816 -43
- 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 +22 -0
- 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 +5 -14
- package/src/tui/session/session-flow/drain.mjs +7 -1
- package/src/tui/session/session-flow/failed-turn-rewind.mjs +10 -4
- package/src/tui/session/turn.mjs +38 -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/memory/lib/memory-session-merge.mjs +0 -32
- package/src/runtime/shared/llm/usage-session-history.mjs +0 -266
|
@@ -169,8 +169,30 @@ export function noteTurnWorktreeChange(paths = null) {
|
|
|
169
169
|
}
|
|
170
170
|
}
|
|
171
171
|
|
|
172
|
-
|
|
173
|
-
|
|
172
|
+
// A lock hold (queue wait plus work) at least this long is logged, so a slow
|
|
173
|
+
// review bar can be told apart as waiting behind other captures or slow git.
|
|
174
|
+
const SLOW_LOCK_LOG_MS = 1_000;
|
|
175
|
+
|
|
176
|
+
function withStateLock(state, task, op = 'task') {
|
|
177
|
+
const queuedAt = Date.now();
|
|
178
|
+
const queuedBehind = state.lockDepth || 0;
|
|
179
|
+
state.lockDepth = queuedBehind + 1;
|
|
180
|
+
let startedAt = 0;
|
|
181
|
+
const timed = async () => {
|
|
182
|
+
startedAt = Date.now();
|
|
183
|
+
return await task();
|
|
184
|
+
};
|
|
185
|
+
const next = state.lock.then(timed, timed).finally(() => {
|
|
186
|
+
state.lockDepth -= 1;
|
|
187
|
+
const finishedAt = Date.now();
|
|
188
|
+
const waitMs = (startedAt || finishedAt) - queuedAt;
|
|
189
|
+
if (finishedAt - queuedAt >= SLOW_LOCK_LOG_MS) {
|
|
190
|
+
console.info(
|
|
191
|
+
`[turn-snapshot-lock] op=${op} waitMs=${waitMs} workMs=${finishedAt - (startedAt || finishedAt)} ` +
|
|
192
|
+
`queuedBehind=${queuedBehind} root=${state.root}`
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
});
|
|
174
196
|
state.lock = next.then(
|
|
175
197
|
() => undefined,
|
|
176
198
|
() => undefined
|
|
@@ -338,6 +360,7 @@ async function ensureState(state) {
|
|
|
338
360
|
}
|
|
339
361
|
|
|
340
362
|
async function resetShadowIndex(state) {
|
|
363
|
+
state.lastCapture = null;
|
|
341
364
|
state.sourceIndexPath ??= await sourceGitPath(state.root, 'index');
|
|
342
365
|
const sourceIndex = state.sourceIndexPath;
|
|
343
366
|
const target = join(state.gitDir, 'index');
|
|
@@ -400,10 +423,33 @@ async function changedWorktreePaths(state) {
|
|
|
400
423
|
};
|
|
401
424
|
}
|
|
402
425
|
|
|
403
|
-
|
|
426
|
+
/** The capture sequence a caller observes before queueing on the lock. */
|
|
427
|
+
function captureSequence(state) {
|
|
428
|
+
return state.captureSeq || 0;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
/** `after` (a captureSequence() read before queueing): a capture of this root
|
|
432
|
+
* that began after that point, with no reported write since and covering
|
|
433
|
+
* every explicit path, already is a capture that began after the call. Many
|
|
434
|
+
* sessions of one repository queue reads together; they share that capture
|
|
435
|
+
* instead of each re-scanning the worktree in turn. */
|
|
436
|
+
async function captureTreeUnlocked(state, toolPaths = [], { after = -1 } = {}) {
|
|
404
437
|
await ensureState(state);
|
|
405
|
-
const { paths, excludedUntracked } = await changedWorktreePaths(state);
|
|
406
438
|
const explicit = new Set(toolPaths);
|
|
439
|
+
const last = state.lastCapture;
|
|
440
|
+
if (
|
|
441
|
+
after >= 0 &&
|
|
442
|
+
last &&
|
|
443
|
+
last.seq > after &&
|
|
444
|
+
last.stamp === state.changeStamp &&
|
|
445
|
+
[...explicit].every((rel) => last.explicit.has(rel))
|
|
446
|
+
) {
|
|
447
|
+
return last.tree;
|
|
448
|
+
}
|
|
449
|
+
state.captureSeq = captureSequence(state) + 1;
|
|
450
|
+
const capture = { seq: state.captureSeq, stamp: state.changeStamp, explicit, tree: '' };
|
|
451
|
+
state.lastCapture = null;
|
|
452
|
+
const { paths, excludedUntracked } = await changedWorktreePaths(state);
|
|
407
453
|
for (const rel of explicit) {
|
|
408
454
|
try {
|
|
409
455
|
const stat = await lstat(resolve(state.root, rel));
|
|
@@ -432,7 +478,9 @@ async function captureTreeUnlocked(state, toolPaths = []) {
|
|
|
432
478
|
});
|
|
433
479
|
}
|
|
434
480
|
const tree = await runGit(shadowArgs(state, ['write-tree']), { cwd: state.root });
|
|
435
|
-
|
|
481
|
+
capture.tree = clean(tree.stdout);
|
|
482
|
+
state.lastCapture = capture;
|
|
483
|
+
return capture.tree;
|
|
436
484
|
}
|
|
437
485
|
|
|
438
486
|
/** Only explicit tool targets bypass ignore/size filters. Their first-before
|
|
@@ -875,39 +923,74 @@ export async function resumeTurnWorktreeSnapshot(
|
|
|
875
923
|
const root = await repositoryRoot(worktree).catch(() => null);
|
|
876
924
|
if (!root) return null;
|
|
877
925
|
const state = stateForRoot(root);
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
926
|
+
const after = captureSequence(state);
|
|
927
|
+
return await withStateLock(
|
|
928
|
+
state,
|
|
929
|
+
async () => {
|
|
930
|
+
await ensureState(state);
|
|
931
|
+
// A collected or never-written baseline restores nothing. Report that as
|
|
932
|
+
// "no snapshot" instead of failing every later call made against it.
|
|
933
|
+
const kind = await runGit(shadowArgs(state, ['cat-file', '-t', tree]), {
|
|
934
|
+
cwd: state.root,
|
|
935
|
+
allowFailure: true,
|
|
936
|
+
});
|
|
937
|
+
if (kind.code !== 0 || clean(kind.stdout) !== 'tree') return null;
|
|
938
|
+
const scopePaths = Array.isArray(paths) && paths.length > 0 ? [...new Set(paths)] : null;
|
|
939
|
+
const applied = await applyBaselineFilesUnlocked(state, tree, baselineFiles || []);
|
|
940
|
+
const snapshot = {
|
|
941
|
+
state,
|
|
942
|
+
root,
|
|
943
|
+
baselineTree: applied.tree,
|
|
944
|
+
currentTree: tree,
|
|
945
|
+
patch: '',
|
|
946
|
+
files: [],
|
|
947
|
+
patchTruncated: false,
|
|
948
|
+
scopePaths,
|
|
949
|
+
toolPaths: new Set(scopePaths || []),
|
|
950
|
+
pendingBaselines: new Map(),
|
|
951
|
+
baselineFiles: new Map(applied.files.map((entry) => [pathKey(entry.path), entry])),
|
|
952
|
+
};
|
|
953
|
+
const currentTree = await captureTreeUnlocked(state, snapshot.toolPaths, { after });
|
|
954
|
+
Object.assign(
|
|
955
|
+
snapshot,
|
|
956
|
+
withOmittedBaselines(
|
|
957
|
+
snapshot,
|
|
958
|
+
await diffTreesUnlocked(state, snapshot.baselineTree, currentTree, scopePaths, { omitPatch })
|
|
959
|
+
)
|
|
960
|
+
);
|
|
961
|
+
return snapshot;
|
|
962
|
+
},
|
|
963
|
+
'resume'
|
|
964
|
+
);
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
// Git's well-known empty tree: what an unborn HEAD (no commit yet) compares as.
|
|
968
|
+
const EMPTY_TREE = '4b825dc642cb6eb9a060e54bf8d69288fbee4904';
|
|
969
|
+
|
|
970
|
+
/** The files among `paths` whose bytes in the snapshot's last capture still
|
|
971
|
+
* differ from the source repository's current HEAD (staged or not), with
|
|
972
|
+
* their counts against HEAD; no patch text. An ignored file that HEAD does
|
|
973
|
+
* not hold is no uncommitted change and is left out. */
|
|
974
|
+
export async function diffTurnWorktreeHead(snapshot, paths) {
|
|
975
|
+
const scope = [...new Set((paths || []).filter(Boolean))];
|
|
976
|
+
if (!snapshot?.state || !snapshot.currentTree || scope.length === 0) return [];
|
|
977
|
+
return await withStateLock(snapshot.state, async () => {
|
|
978
|
+
const { state } = snapshot;
|
|
979
|
+
const head = await runGit(['rev-parse', '--verify', '--quiet', 'HEAD^{tree}'], {
|
|
883
980
|
cwd: state.root,
|
|
884
981
|
allowFailure: true,
|
|
885
982
|
});
|
|
886
|
-
|
|
887
|
-
const
|
|
888
|
-
const
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
root,
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
scopePaths,
|
|
898
|
-
toolPaths: new Set(scopePaths || []),
|
|
899
|
-
pendingBaselines: new Map(),
|
|
900
|
-
baselineFiles: new Map(applied.files.map((entry) => [pathKey(entry.path), entry])),
|
|
901
|
-
};
|
|
902
|
-
const currentTree = await captureTreeUnlocked(state, snapshot.toolPaths);
|
|
903
|
-
Object.assign(
|
|
904
|
-
snapshot,
|
|
905
|
-
withOmittedBaselines(
|
|
906
|
-
snapshot,
|
|
907
|
-
await diffTreesUnlocked(state, snapshot.baselineTree, currentTree, scopePaths, { omitPatch })
|
|
908
|
-
)
|
|
909
|
-
);
|
|
910
|
-
return snapshot;
|
|
983
|
+
const headTree = head.code === 0 ? clean(head.stdout) : EMPTY_TREE;
|
|
984
|
+
const { files } = await diffTreesUnlocked(state, headTree, snapshot.currentTree, scope, { omitPatch: true });
|
|
985
|
+
const added = files.filter((file) => file.status === 'A').map((file) => file.path);
|
|
986
|
+
if (added.length === 0) return files;
|
|
987
|
+
const ignored = await runGit(['check-ignore', '-z', '--stdin'], {
|
|
988
|
+
cwd: state.root,
|
|
989
|
+
input: `${added.join('\0')}\0`,
|
|
990
|
+
allowFailure: true,
|
|
991
|
+
});
|
|
992
|
+
const skip = new Set(splitNulList(ignored.stdout).map(pathKey));
|
|
993
|
+
return files.filter((file) => file.status !== 'A' || !skip.has(pathKey(file.path)));
|
|
911
994
|
});
|
|
912
995
|
}
|
|
913
996
|
|
|
@@ -930,17 +1013,22 @@ function startRefresh(snapshot) {
|
|
|
930
1013
|
const flight = { seq: (snapshot.refreshSeq || 0) + 1, promise: null };
|
|
931
1014
|
snapshot.refreshSeq = flight.seq;
|
|
932
1015
|
snapshot.refreshing = flight;
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
1016
|
+
const after = captureSequence(snapshot.state);
|
|
1017
|
+
flight.promise = withStateLock(
|
|
1018
|
+
snapshot.state,
|
|
1019
|
+
async () => {
|
|
1020
|
+
const mark = await captureMark(snapshot.state);
|
|
1021
|
+
await materializeToolBaselines(snapshot);
|
|
1022
|
+
const currentTree = await captureTreeUnlocked(snapshot.state, snapshot.toolPaths, { after });
|
|
1023
|
+
if (currentTree !== snapshot.currentTree) {
|
|
1024
|
+
const review = await diffTreesUnlocked(snapshot.state, snapshot.baselineTree, currentTree, snapshot.scopePaths);
|
|
1025
|
+
Object.assign(snapshot, withOmittedBaselines(snapshot, review));
|
|
1026
|
+
}
|
|
1027
|
+
snapshot.captured = mark;
|
|
1028
|
+
return snapshot;
|
|
1029
|
+
},
|
|
1030
|
+
'refresh'
|
|
1031
|
+
).finally(() => {
|
|
944
1032
|
if (snapshot.refreshing === flight) snapshot.refreshing = null;
|
|
945
1033
|
});
|
|
946
1034
|
return flight.promise;
|
|
@@ -982,6 +1070,7 @@ function safeRelativePath(root, value) {
|
|
|
982
1070
|
}
|
|
983
1071
|
|
|
984
1072
|
async function restorePathFromTree(snapshot, rel) {
|
|
1073
|
+
snapshot.state.lastCapture = null;
|
|
985
1074
|
const listed = await runGit(
|
|
986
1075
|
shadowArgs(snapshot.state, ['ls-tree', '-z', '--name-only', snapshot.baselineTree, '--', rel]),
|
|
987
1076
|
{ cwd: snapshot.root }
|
|
@@ -19,7 +19,6 @@ export const DEFAULT_WORKTREE_BRANCH_PREFIX = 'mixdog/';
|
|
|
19
19
|
|
|
20
20
|
export const WORKSPACE_TRANSITION_STATES = Object.freeze(['idle', 'provisioning', 'switching', 'removing', 'recovery']);
|
|
21
21
|
const WORKTREE_ENTRY_STATES = Object.freeze(['creating', 'ready', 'deleting', 'recovery']);
|
|
22
|
-
const BRANCH_OCCUPANCY_KINDS = Object.freeze(['current', 'session', 'external', 'free']);
|
|
23
22
|
|
|
24
23
|
/**
|
|
25
24
|
* Stable error codes. Clients branch on `error.code`, never on message text.
|
|
@@ -302,29 +301,6 @@ export function normalizeWorktreeOwner(input) {
|
|
|
302
301
|
* @typedef {{ branch: string, kind: BranchOccupancyKind, sessionId?: string, path?: string }} BranchOccupancy
|
|
303
302
|
*/
|
|
304
303
|
|
|
305
|
-
/**
|
|
306
|
-
* @param {unknown} input
|
|
307
|
-
* @returns {BranchOccupancy}
|
|
308
|
-
*/
|
|
309
|
-
export function normalizeBranchOccupancy(input) {
|
|
310
|
-
const value = record(input, 'occupancy');
|
|
311
|
-
const branch = text(value.branch, 'occupancy.branch');
|
|
312
|
-
const kind = oneOf(value.kind, BRANCH_OCCUPANCY_KINDS, 'occupancy.kind');
|
|
313
|
-
if (kind === 'free') {
|
|
314
|
-
if (value.sessionId != null || value.path != null) {
|
|
315
|
-
throw new TypeError('A free branch carries no sessionId or path.');
|
|
316
|
-
}
|
|
317
|
-
return { branch, kind };
|
|
318
|
-
}
|
|
319
|
-
if (kind !== 'session' && value.sessionId != null) {
|
|
320
|
-
throw new TypeError('Only a session occupancy carries a sessionId.');
|
|
321
|
-
}
|
|
322
|
-
const occupancy = { branch, kind };
|
|
323
|
-
if (kind === 'session') occupancy.sessionId = text(value.sessionId, 'occupancy.sessionId');
|
|
324
|
-
if (value.path != null) occupancy.path = text(value.path, 'occupancy.path');
|
|
325
|
-
return occupancy;
|
|
326
|
-
}
|
|
327
|
-
|
|
328
304
|
/**
|
|
329
305
|
* The branch menu's rows (the desktop DesktopGitBranch shape) for the local
|
|
330
306
|
* branch names the manager lists: `current` marks the branch checked out in
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Temporary git repositories and data dirs for the worktree suites.
|
|
2
2
|
import { execFileSync } from 'node:child_process';
|
|
3
|
-
import { chmodSync, mkdtempSync, rmSync, writeFileSync, mkdirSync } from 'node:fs';
|
|
3
|
+
import { chmodSync, cpSync, existsSync, mkdtempSync, rmSync, writeFileSync, mkdirSync } from 'node:fs';
|
|
4
4
|
import { tmpdir } from 'node:os';
|
|
5
5
|
import { dirname, join } from 'node:path';
|
|
6
6
|
|
|
@@ -56,14 +56,29 @@ export function commitFile(cwd, rel, content, message = `edit ${rel}`) {
|
|
|
56
56
|
return git(cwd, 'rev-parse', 'HEAD');
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
59
|
+
// Built once per process and copied per repository: the five or six git
|
|
60
|
+
// spawns behind one fresh repository cost ~0.8 s on win32, and the worktree
|
|
61
|
+
// suites make dozens. A non-bare repository holds no absolute paths, so a copy
|
|
62
|
+
// is the same repository at its new location.
|
|
63
|
+
const templates = new Map();
|
|
64
|
+
|
|
65
|
+
function repoTemplate(commit) {
|
|
66
|
+
const existing = templates.get(commit);
|
|
67
|
+
if (existing && existsSync(existing)) return existing;
|
|
68
|
+
const dir = tempDir('mixdog-wt-template-');
|
|
62
69
|
git(dir, 'init', '-q', '-b', 'main');
|
|
63
70
|
git(dir, 'config', 'user.name', 'Mixdog Test');
|
|
64
71
|
git(dir, 'config', 'user.email', 'mixdog@example.com');
|
|
65
72
|
git(dir, 'config', 'core.autocrlf', 'false');
|
|
66
73
|
if (commit) commitFile(dir, 'README.md', 'hello\n', 'initial');
|
|
74
|
+
templates.set(commit, dir);
|
|
75
|
+
return dir;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** A repository on `main` with one commit. */
|
|
79
|
+
export function makeRepo({ commit = true } = {}) {
|
|
80
|
+
const dir = tempDir('mixdog-wt-repo-');
|
|
81
|
+
cpSync(repoTemplate(commit), dir, { recursive: true });
|
|
67
82
|
return dir;
|
|
68
83
|
}
|
|
69
84
|
|
|
@@ -451,12 +451,18 @@ function resourceApiFor(boot) {
|
|
|
451
451
|
STANDALONE_DATA_DIR,
|
|
452
452
|
saveConfigAndAdopt: boot.saveConfigAndAdopt,
|
|
453
453
|
connectConfiguredMcp: boot.connectConfiguredMcp,
|
|
454
|
+
cancelMcpLogin: boot.cancelMcpLogin,
|
|
455
|
+
completeMcpLogin: boot.completeMcpLogin,
|
|
456
|
+
clearMcpServerOAuth: (cfg) => boot.mcpClient.clearMcpServerOAuth(cfg),
|
|
457
|
+
getMcpServerOAuthState: (cfg) => boot.mcpClient.getMcpServerOAuthState(cfg),
|
|
458
|
+
probeMcpServerAuth: (cfg) => boot.mcpClient.probeMcpServerAuth(cfg),
|
|
454
459
|
invalidatePreSessionToolSurface: boot.invalidatePreSessionToolSurface,
|
|
455
460
|
refreshEmptySessionToolPolicy: boot.refreshEmptySessionToolPolicy,
|
|
456
461
|
normalizeMcpServerInput: boot.normalizeMcpServerInput,
|
|
457
462
|
mcpStatus: boot.mcpStatus,
|
|
458
463
|
getMcpServerConfig: boot.getMcpServerConfig,
|
|
459
464
|
resolveMcpCatalog: boot.resolveMcpCatalog,
|
|
465
|
+
resolveConfiguredMcpCatalog: boot.resolveConfiguredMcpCatalog,
|
|
460
466
|
pluginMcpServer: boot.pluginMcpServer,
|
|
461
467
|
skillsStatus: boot.skillsStatus,
|
|
462
468
|
skillContent: boot.skillContent,
|
|
@@ -40,11 +40,14 @@ function wireMcp(boot) {
|
|
|
40
40
|
const {
|
|
41
41
|
mcpStatus,
|
|
42
42
|
resolveMcpCatalog,
|
|
43
|
+
resolveConfiguredMcpCatalog,
|
|
43
44
|
pluginMcpServer,
|
|
44
45
|
pluginMcpSummary,
|
|
45
46
|
getMcpServerConfig,
|
|
46
47
|
connectConfiguredMcp,
|
|
47
48
|
awaitInitialMcpConnect,
|
|
49
|
+
cancelMcpLogin,
|
|
50
|
+
completeMcpLogin,
|
|
48
51
|
normalizeMcpServerInput,
|
|
49
52
|
} = createMcpGlue({
|
|
50
53
|
mcpClient,
|
|
@@ -59,11 +62,14 @@ function wireMcp(boot) {
|
|
|
59
62
|
Object.assign(boot, {
|
|
60
63
|
mcpStatus,
|
|
61
64
|
resolveMcpCatalog,
|
|
65
|
+
resolveConfiguredMcpCatalog,
|
|
62
66
|
pluginMcpServer,
|
|
63
67
|
pluginMcpSummary,
|
|
64
68
|
getMcpServerConfig,
|
|
65
69
|
connectConfiguredMcp,
|
|
66
70
|
awaitInitialMcpConnect,
|
|
71
|
+
cancelMcpLogin,
|
|
72
|
+
completeMcpLogin,
|
|
67
73
|
normalizeMcpServerInput,
|
|
68
74
|
});
|
|
69
75
|
}
|
|
@@ -93,6 +93,8 @@ function leadSessionRow(s, heartbeatMtimes) {
|
|
|
93
93
|
// sessions under the sidebar Automations section instead of Recent.
|
|
94
94
|
sourceType: sourceType || null,
|
|
95
95
|
sourceName: clean(s.sourceName || '') || null,
|
|
96
|
+
createdAt: Number(s.createdAt) || 0,
|
|
97
|
+
inheritedFromSessionId: clean(s.inheritedFromSessionId || '') || null,
|
|
96
98
|
};
|
|
97
99
|
}
|
|
98
100
|
|
|
@@ -178,7 +178,7 @@ export function createTeardown(deps, { ingestSessionIntoMemory, closeSurfaceSess
|
|
|
178
178
|
if (scope.detach) await settleDetached(withTeardownDeadline, stops);
|
|
179
179
|
else await settleAll(withTeardownDeadline, stops);
|
|
180
180
|
// The session went away (idle eviction included): free its save baselines.
|
|
181
|
-
if (scope.closingSessionId) forgetSessionSaveBaseline(scope.closingSessionId);
|
|
181
|
+
if (scope.closingSessionId) forgetSessionSaveBaseline(scope.closingSessionId, { releaseWriteControl: true });
|
|
182
182
|
// Self-update stages in the background and swaps on the next clean launch
|
|
183
183
|
// (see staged-update.mjs) — nothing installs at shutdown. On a real
|
|
184
184
|
// process exit we just drop this session's live-refcount pid file so a
|
|
@@ -24,11 +24,17 @@ export function createMcpConnect({
|
|
|
24
24
|
const fingerprint = (cfg) => JSON.stringify(cfg);
|
|
25
25
|
// Roots advertised by cwd-following servers track this runtime's cwd.
|
|
26
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());
|
|
27
31
|
|
|
28
32
|
// Connect/disconnect exactly one server in the live registry, leaving all
|
|
29
33
|
// others untouched. Used by the enable/disable toggle so a single-server
|
|
30
|
-
// change never triggers a full disconnectAll()/reconnect freeze.
|
|
31
|
-
|
|
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) {
|
|
32
38
|
const target = clean(name);
|
|
33
39
|
if (!target) return;
|
|
34
40
|
// Changing this server's state clears any stale failure record for it.
|
|
@@ -46,11 +52,16 @@ export function createMcpConnect({
|
|
|
46
52
|
// between the close and the connect never resurrects the server.
|
|
47
53
|
try {
|
|
48
54
|
ensurePluginServerData(cfg);
|
|
49
|
-
await mcpClient.reconnectSingleMcpServer(
|
|
55
|
+
await mcpClient.reconnectSingleMcpServer(
|
|
56
|
+
getMcpScopeId(),
|
|
57
|
+
target,
|
|
58
|
+
cfg,
|
|
59
|
+
login ? { interactive: true, openBrowser: login.openBrowser } : {}
|
|
60
|
+
);
|
|
50
61
|
} catch (error) {
|
|
51
|
-
const
|
|
52
|
-
|
|
53
|
-
|
|
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];
|
|
54
65
|
state.mcpFailures = [...(state.mcpFailures || []), ...failures];
|
|
55
66
|
}
|
|
56
67
|
}
|
|
@@ -72,7 +83,13 @@ export function createMcpConnect({
|
|
|
72
83
|
]);
|
|
73
84
|
}
|
|
74
85
|
|
|
75
|
-
async function connectConfiguredMcp({
|
|
86
|
+
async function connectConfiguredMcp({
|
|
87
|
+
reset = false,
|
|
88
|
+
only = null,
|
|
89
|
+
enabled = true,
|
|
90
|
+
reconcile = false,
|
|
91
|
+
login = null,
|
|
92
|
+
} = {}) {
|
|
76
93
|
if (envFlag('MIXDOG_DISABLE_MCP')) {
|
|
77
94
|
++state.mcpConnectGeneration;
|
|
78
95
|
state.mcpFailures = [];
|
|
@@ -87,6 +104,9 @@ export function createMcpConnect({
|
|
|
87
104
|
// been requested in the meantime. Registering as in-flight makes a later
|
|
88
105
|
// reset serialize behind us instead of interleaving disconnect/connect.
|
|
89
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);
|
|
90
110
|
// Atomically capture the current generation AND the prior in-flight
|
|
91
111
|
// promise in the same synchronous step, then chain our op onto it. No
|
|
92
112
|
// await sits between the capture and the `state.mcpConnectInFlight = run`
|
|
@@ -106,7 +126,7 @@ export function createMcpConnect({
|
|
|
106
126
|
}
|
|
107
127
|
if (gen !== state.mcpConnectGeneration) return;
|
|
108
128
|
if (reconcile) await reconcileServers();
|
|
109
|
-
else await applyMcpServerConnection(only, enabled);
|
|
129
|
+
else await applyMcpServerConnection(only, enabled, undefined, login);
|
|
110
130
|
})();
|
|
111
131
|
state.mcpConnectInFlight = run;
|
|
112
132
|
try {
|
|
@@ -207,5 +227,5 @@ export function createMcpConnect({
|
|
|
207
227
|
}
|
|
208
228
|
}
|
|
209
229
|
|
|
210
|
-
return { connectConfiguredMcp, awaitInitialMcpConnect };
|
|
230
|
+
return { connectConfiguredMcp, awaitInitialMcpConnect, cancelMcpLogin, completeMcpLogin };
|
|
211
231
|
}
|
|
@@ -5,9 +5,11 @@ import { clean } from '../../runtime/agent/orchestrator/runtime-core/session-tex
|
|
|
5
5
|
import { envFlag } from '../../runtime/shared/env.mjs';
|
|
6
6
|
import { resolveMcpServerCatalog } from './plugin-servers.mjs';
|
|
7
7
|
|
|
8
|
-
function mcpServerStatus(cfg, live, fail) {
|
|
8
|
+
function mcpServerStatus(cfg, live, fail, loginPending) {
|
|
9
9
|
if (cfg?.enabled === false) return 'disabled';
|
|
10
10
|
if (live) return 'connected';
|
|
11
|
+
// Until an interactive sign-in completes the server still needs it.
|
|
12
|
+
if (loginPending || fail?.authRequired) return 'sign-in required';
|
|
11
13
|
return fail ? 'failed' : 'disconnected';
|
|
12
14
|
}
|
|
13
15
|
|
|
@@ -43,6 +45,12 @@ export function createMcpServerCatalog({
|
|
|
43
45
|
* runtime's session cwd. */
|
|
44
46
|
function resolveCatalog() {
|
|
45
47
|
if (envFlag('MIXDOG_DISABLE_MCP')) return EMPTY_CATALOG;
|
|
48
|
+
return resolveConfiguredCatalog();
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Every configured server (user config plus plugin declarations, disabled
|
|
52
|
+
* ones included) whether or not this runtime connects MCP at all. */
|
|
53
|
+
function resolveConfiguredCatalog() {
|
|
46
54
|
let plugins = [];
|
|
47
55
|
let registryWarning = null;
|
|
48
56
|
try {
|
|
@@ -115,20 +123,28 @@ export function createMcpServerCatalog({
|
|
|
115
123
|
const fail =
|
|
116
124
|
failures.get(name) || (dead ? { msg: live.error || live.lastError || 'MCP server is not alive' } : undefined);
|
|
117
125
|
const entry = entries[name] || {};
|
|
126
|
+
const loginPending = !(live && !dead) && mcpClient.isMcpLoginPending?.(name, getMcpScopeId()) === true;
|
|
127
|
+
const status = mcpServerStatus(cfg, live && !dead, fail, loginPending);
|
|
128
|
+
// Needing an OAuth sign-in is a state, not an error.
|
|
129
|
+
const authRequired = status === 'sign-in required';
|
|
118
130
|
const row = {
|
|
119
131
|
name,
|
|
120
132
|
configured: true,
|
|
121
133
|
enabled: cfg?.enabled !== false,
|
|
122
134
|
connected: Boolean(live) && !dead,
|
|
123
|
-
status
|
|
135
|
+
status,
|
|
124
136
|
transport: mcpTransportLabel(cfg),
|
|
125
137
|
toolCount: live?.toolCount || 0,
|
|
126
138
|
tools: live?.tools || [],
|
|
127
139
|
capabilities: live?.capabilities || { tools: false, prompts: false, resources: false },
|
|
128
|
-
error: fail?.msg || null,
|
|
140
|
+
error: authRequired ? null : fail?.msg || null,
|
|
129
141
|
warnings: [...(entry.warnings || []), ...(live?.warnings || [])],
|
|
130
142
|
source: sources[name] || 'config',
|
|
131
143
|
};
|
|
144
|
+
if (authRequired) row.authRequired = true;
|
|
145
|
+
const oauth = mcpClient.getMcpServerOAuthState?.(cfg);
|
|
146
|
+
// Why the last sign-in did not complete (already credential-scrubbed).
|
|
147
|
+
if (oauth) row.oauth = authRequired && fail?.msg ? { ...oauth, lastError: fail.msg } : oauth;
|
|
132
148
|
if (entry.owner) {
|
|
133
149
|
row.owner = entry.owner;
|
|
134
150
|
row.pluginEnabled = entry.pluginEnabled;
|
|
@@ -180,6 +196,7 @@ export function createMcpServerCatalog({
|
|
|
180
196
|
return {
|
|
181
197
|
mcpTransportLabel,
|
|
182
198
|
resolveCatalog,
|
|
199
|
+
resolveConfiguredCatalog,
|
|
183
200
|
resolveEffectiveMcpServers,
|
|
184
201
|
pluginServer,
|
|
185
202
|
getMcpServerConfig,
|
|
@@ -37,11 +37,14 @@ export function createMcpGlue({
|
|
|
37
37
|
return {
|
|
38
38
|
mcpStatus: catalog.mcpStatus,
|
|
39
39
|
resolveMcpCatalog: catalog.resolveCatalog,
|
|
40
|
+
resolveConfiguredMcpCatalog: catalog.resolveConfiguredCatalog,
|
|
40
41
|
pluginMcpServer: catalog.pluginServer,
|
|
41
42
|
pluginMcpSummary: catalog.pluginMcpSummary,
|
|
42
43
|
getMcpServerConfig: catalog.getMcpServerConfig,
|
|
43
44
|
connectConfiguredMcp: connect.connectConfiguredMcp,
|
|
44
45
|
awaitInitialMcpConnect: connect.awaitInitialMcpConnect,
|
|
46
|
+
cancelMcpLogin: connect.cancelMcpLogin,
|
|
47
|
+
completeMcpLogin: connect.completeMcpLogin,
|
|
45
48
|
normalizeMcpServerInput,
|
|
46
49
|
};
|
|
47
50
|
}
|