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
|
@@ -7,8 +7,20 @@ import {
|
|
|
7
7
|
mergeMcpServerConfig,
|
|
8
8
|
MCP_TIMEOUT_FIELD_GROUPS,
|
|
9
9
|
} from '../runtime/agent/orchestrator/runtime-core/plugin-mcp.mjs';
|
|
10
|
+
import { resolveMcpOAuthUrl } from '../runtime/agent/orchestrator/mcp/client-config.mjs';
|
|
10
11
|
import { withAdoptedCopies, withPinnedCollisionNames, withPluginServerIntent } from './mcp-glue/plugin-servers.mjs';
|
|
11
12
|
import { findRegisteredPlugin } from './resource-plugins-api.mjs';
|
|
13
|
+
import { createMcpOAuthLogins } from './mcp-oauth-login.mjs';
|
|
14
|
+
|
|
15
|
+
/** The URL a server's OAuth credentials are stored under, or null. */
|
|
16
|
+
function oauthUrlOf(cfg) {
|
|
17
|
+
try {
|
|
18
|
+
return resolveMcpOAuthUrl(cfg);
|
|
19
|
+
} catch {
|
|
20
|
+
// No valid remote URL: such a server never logged in.
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
12
24
|
|
|
13
25
|
/** The `mcpServers` map of a config as a fresh object (never the live one). */
|
|
14
26
|
function mcpServersOf(config) {
|
|
@@ -59,14 +71,45 @@ export function createMcpResourceApi({ deps, sync, decorate }) {
|
|
|
59
71
|
mcpStatus,
|
|
60
72
|
getMcpServerConfig,
|
|
61
73
|
connectConfiguredMcp,
|
|
74
|
+
clearMcpServerOAuth,
|
|
62
75
|
reloadFullConfig,
|
|
63
76
|
awaitKeychainPrewarm,
|
|
64
77
|
pluginsStatus,
|
|
65
|
-
resolveMcpCatalog = () => ({ adoption: null, plugins: {}, entries: {} }),
|
|
78
|
+
resolveMcpCatalog = () => ({ servers: {}, adoption: null, plugins: {}, entries: {} }),
|
|
79
|
+
resolveConfiguredMcpCatalog = resolveMcpCatalog,
|
|
66
80
|
pluginMcpServer = () => null,
|
|
67
81
|
flushSkillsSave,
|
|
82
|
+
cancelMcpLogin = () => {},
|
|
83
|
+
completeMcpLogin,
|
|
84
|
+
getMcpServerOAuthState = () => null,
|
|
85
|
+
probeMcpServerAuth,
|
|
68
86
|
} = deps;
|
|
69
87
|
|
|
88
|
+
// Explicit OAuth sign-in: the one interactive MCP connect. On success the
|
|
89
|
+
// other runtimes reconnect the server non-interactively with the new tokens.
|
|
90
|
+
const oauthLogins = createMcpOAuthLogins({
|
|
91
|
+
async connect(name, openBrowser) {
|
|
92
|
+
const status = await connectConfiguredMcp({ only: name, enabled: true, login: { openBrowser } });
|
|
93
|
+
const row = status?.servers?.find((server) => server.name === name);
|
|
94
|
+
return { connected: row?.connected === true, error: row?.error || null };
|
|
95
|
+
},
|
|
96
|
+
cancel: (name) => cancelMcpLogin(name),
|
|
97
|
+
completeCallback: completeMcpLogin,
|
|
98
|
+
onComplete: (name) => sync.announce('mcp', { detail: { mcpChanges: [{ name, enabled: true }] } }),
|
|
99
|
+
});
|
|
100
|
+
/** The effective config of a server that signs in with MCP OAuth (HTTP/SSE
|
|
101
|
+
* without configured authentication). */
|
|
102
|
+
const oauthServerConfig = async (name) => {
|
|
103
|
+
const serverName = clean(name);
|
|
104
|
+
if (!serverName) throw new Error('MCP server name is required');
|
|
105
|
+
await awaitKeychainPrewarm();
|
|
106
|
+
reloadFullConfig();
|
|
107
|
+
const cfg = resolveMcpCatalog().servers[serverName];
|
|
108
|
+
if (!cfg) throw new Error(`MCP server not configured: ${serverName}`);
|
|
109
|
+
if (!getMcpServerOAuthState(cfg)) throw new Error(`MCP server "${serverName}" does not use OAuth sign-in`);
|
|
110
|
+
return { serverName, cfg };
|
|
111
|
+
};
|
|
112
|
+
|
|
70
113
|
/** The config with any adoptable plugin server copies already adopted, so
|
|
71
114
|
* every write also persists the adoption and never re-adds a copy. */
|
|
72
115
|
const adoptedConfig = () => withAdoptedCopies(getConfig(), resolveMcpCatalog().adoption);
|
|
@@ -115,6 +158,49 @@ export function createMcpResourceApi({ deps, sync, decorate }) {
|
|
|
115
158
|
const { enabled } = getMcpServerConfig(serverName);
|
|
116
159
|
return sync.reconnectMcpAndAnnounce({ only: serverName, enabled });
|
|
117
160
|
},
|
|
161
|
+
/** `{ flowId, state: 'pending', url }` once the browser login starts
|
|
162
|
+
* (`options.openBrowser === false` leaves opening `url` to the caller), or
|
|
163
|
+
* `{ flowId, state: 'complete' }` when stored credentials sufficed. */
|
|
164
|
+
async beginMcpOAuthLogin(name, options = {}) {
|
|
165
|
+
const { serverName, cfg } = await oauthServerConfig(name);
|
|
166
|
+
if (cfg.enabled === false) throw new Error(`MCP server is disabled: ${serverName}`);
|
|
167
|
+
return oauthLogins.begin(serverName, options);
|
|
168
|
+
},
|
|
169
|
+
/** `{ flowId, state: 'pending' | 'complete' | 'failed' | 'cancelled', error? }` */
|
|
170
|
+
getMcpOAuthLoginStatus(flowId) {
|
|
171
|
+
return oauthLogins.status(flowId);
|
|
172
|
+
},
|
|
173
|
+
cancelMcpOAuthLogin(flowId) {
|
|
174
|
+
return oauthLogins.cancel(flowId);
|
|
175
|
+
},
|
|
176
|
+
/** Remote / headless sign-in: complete the pending flow with the redirect
|
|
177
|
+
* URL pasted from the browser. `{ flowId, state: 'complete' | 'failed',
|
|
178
|
+
* error? }`; an invalid paste throws a fixed message and stays pending. */
|
|
179
|
+
completeMcpOAuthLogin(flowId, callbackUrl) {
|
|
180
|
+
return oauthLogins.complete(flowId, callbackUrl);
|
|
181
|
+
},
|
|
182
|
+
/** Forget the server's stored OAuth credentials (shared by every server
|
|
183
|
+
* with the same exact URL) and reconnect without a login, here and on
|
|
184
|
+
* peers, so it reports sign-in required. */
|
|
185
|
+
async signOutMcpServer(name) {
|
|
186
|
+
const { serverName, cfg } = await oauthServerConfig(name);
|
|
187
|
+
// A begin issued meanwhile waits until the credentials are gone.
|
|
188
|
+
return oauthLogins.cancelServer(serverName, async () => {
|
|
189
|
+
await clearMcpServerOAuth(cfg);
|
|
190
|
+
return sync.reconnectMcpAndAnnounce({ only: serverName, enabled: cfg.enabled !== false });
|
|
191
|
+
});
|
|
192
|
+
},
|
|
193
|
+
/** `{ auth: 'oauth' | 'none' | 'configured' | 'unreachable', error? }` for an
|
|
194
|
+
* HTTP/SSE config, without credentials or a browser. */
|
|
195
|
+
probeMcpServerAuth(cfg) {
|
|
196
|
+
if (!cfg || typeof cfg !== 'object' || Array.isArray(cfg)) throw new TypeError('MCP server config is required');
|
|
197
|
+
const probe = {};
|
|
198
|
+
for (const key of ['type', 'url', 'headers', 'bearer_token_env_var', 'env_http_headers']) {
|
|
199
|
+
if (cfg[key] !== undefined) probe[key] = cfg[key];
|
|
200
|
+
}
|
|
201
|
+
if (probe.type !== 'http' && probe.type !== 'sse') throw new TypeError("type must be 'http' or 'sse'");
|
|
202
|
+
return probeMcpServerAuth(probe);
|
|
203
|
+
},
|
|
118
204
|
async addMcpServer(input = {}) {
|
|
119
205
|
const { name, config: serverConfig } = normalizeMcpServerInput(input);
|
|
120
206
|
const servers = userServers();
|
|
@@ -162,9 +248,19 @@ export function createMcpResourceApi({ deps, sync, decorate }) {
|
|
|
162
248
|
refusePluginServer(serverName);
|
|
163
249
|
throw new Error(`MCP server not configured: ${serverName}`);
|
|
164
250
|
}
|
|
251
|
+
const removed = servers[serverName];
|
|
165
252
|
delete servers[serverName];
|
|
166
253
|
saveServers(servers);
|
|
167
|
-
|
|
254
|
+
const status = await sync.reconnectMcpAndAnnounce({ drop: [serverName] });
|
|
255
|
+
// A removed server keeps no OAuth tokens or client registration, unless
|
|
256
|
+
// another configured server (plugin or disabled ones included, even
|
|
257
|
+
// with MCP disabled for this runtime) still uses them through the same
|
|
258
|
+
// exact URL.
|
|
259
|
+
const url = oauthUrlOf(removed);
|
|
260
|
+
if (url && !Object.values(resolveConfiguredMcpCatalog().servers).some((cfg) => oauthUrlOf(cfg) === url)) {
|
|
261
|
+
await clearMcpServerOAuth(removed);
|
|
262
|
+
}
|
|
263
|
+
return status;
|
|
168
264
|
},
|
|
169
265
|
async setMcpServerEnabled(name, enabled) {
|
|
170
266
|
const serverName = clean(name);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { registerCacheInvalidationListener } from '../runtime/agent/orchestrator/tools/builtin/cache-layers.mjs';
|
|
2
2
|
import { turnReviewReply } from '../runtime/shared/turn-review-reply.mjs';
|
|
3
3
|
import {
|
|
4
|
+
clearSessionChangeHistory,
|
|
4
5
|
getSessionReviewDiff,
|
|
5
6
|
getTurnChanges,
|
|
6
7
|
getTurnReviewDiff,
|
|
@@ -10,6 +11,17 @@ import {
|
|
|
10
11
|
revertTurnReviewFile,
|
|
11
12
|
} from '../runtime/shared/turn-snapshot.mjs';
|
|
12
13
|
import { noteTurnWorktreeChange } from '../runtime/shared/turn-worktree-snapshot.mjs';
|
|
14
|
+
import { forgetSessionChangeHistoryMarker } from '../runtime/shared/turn-snapshot-store.mjs';
|
|
15
|
+
import { forgetSessionBaselines } from '../runtime/shared/session-baseline-store.mjs';
|
|
16
|
+
import { registerSessionPurgeHook } from '../runtime/agent/orchestrator/session/store.mjs';
|
|
17
|
+
|
|
18
|
+
// A deleted history stays deleted for as long as its session exists; the
|
|
19
|
+
// session's own deletion takes the marker — and its stored first-seen file
|
|
20
|
+
// contents — with it.
|
|
21
|
+
registerSessionPurgeHook((sessionId) => {
|
|
22
|
+
void forgetSessionChangeHistoryMarker(sessionId);
|
|
23
|
+
void forgetSessionBaselines(sessionId);
|
|
24
|
+
});
|
|
13
25
|
|
|
14
26
|
// Tool writes (apply_patch, mutating shell commands, git, watcher events)
|
|
15
27
|
// reach the live review snapshot as its change signal: a review poll right
|
|
@@ -31,5 +43,6 @@ export function createRuntimeReviewApi({ getCwd, getSessionId }) {
|
|
|
31
43
|
revertTurnChanges(getSessionId(), checkpointId, file, revision),
|
|
32
44
|
reapplyTurnChanges: (checkpointId, file, revision) =>
|
|
33
45
|
reapplyTurnChanges(getSessionId(), checkpointId, file, revision),
|
|
46
|
+
clearSessionChangeHistory: () => clearSessionChangeHistory(getSessionId()),
|
|
34
47
|
};
|
|
35
48
|
}
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
// Every call is idempotent (the first terminal state wins, so the outer reject
|
|
20
20
|
// path cannot double-notify) and absorbs its own errors, so reporting on a
|
|
21
21
|
// turn can never break the turn.
|
|
22
|
-
import { reconcileBackgroundTask } from '../../../runtime/shared/background-tasks.mjs';
|
|
22
|
+
import { reconcileBackgroundTask, settleCancelledBackgroundTask } from '../../../runtime/shared/background-tasks.mjs';
|
|
23
23
|
|
|
24
24
|
function reconcileJobTerminalResult(job, value) {
|
|
25
25
|
if (!job?.taskId) return;
|
|
@@ -50,6 +50,8 @@ export function terminalResultHook({ job, turnReview, completionValue, notifyEar
|
|
|
50
50
|
turnReview.complete();
|
|
51
51
|
const value = completionValue(terminalResult);
|
|
52
52
|
job._terminalResultValue = value;
|
|
53
|
+
// A cancelled job reports once, as `cancelled`, when its turn settles.
|
|
54
|
+
if (job.status === 'cancelled') return;
|
|
53
55
|
notifyEarly(job, value, notifyContext || {});
|
|
54
56
|
reconcileJobTerminalResult(job, value);
|
|
55
57
|
};
|
|
@@ -67,6 +69,16 @@ export function reconcileJobWatchdogPartial(job, value) {
|
|
|
67
69
|
} catch {}
|
|
68
70
|
}
|
|
69
71
|
|
|
72
|
+
/** The cancelled turn stopped: deliver the owner's single `cancelled`
|
|
73
|
+
* notification, carrying the output the turn produced before it stopped
|
|
74
|
+
* (header-only when it produced none). */
|
|
75
|
+
export function reconcileJobCancelled(job, value) {
|
|
76
|
+
if (!job?.taskId) return;
|
|
77
|
+
try {
|
|
78
|
+
settleCancelledBackgroundTask(job.taskId, value?.content ? { result: value } : {});
|
|
79
|
+
} catch {}
|
|
80
|
+
}
|
|
81
|
+
|
|
70
82
|
export function reconcileJobStreamStalled(job, error) {
|
|
71
83
|
if (!job?.taskId || job._terminalResultValue !== undefined) return;
|
|
72
84
|
try {
|
|
@@ -5,6 +5,8 @@ import { ACTIVE_STAGES } from '../tool-def.mjs';
|
|
|
5
5
|
import { isTerminalWorkerStatus } from '../worker-rows.mjs';
|
|
6
6
|
import { sessionUsageTotals } from '../../../../runtime/shared/session-usage-totals.mjs';
|
|
7
7
|
|
|
8
|
+
const CANCELLED_WORKER_STATUS = /^(?:cancelled|canceled|killed|cancel[-_\s]?(?:unconfirmed|pending))$/;
|
|
9
|
+
|
|
8
10
|
// Idle, closed, errored and reaped/unknown rows are history and stay reachable
|
|
9
11
|
// through the task section (status/read keep their terminal result).
|
|
10
12
|
export function isActiveWorkerRow(row = {}) {
|
|
@@ -14,8 +16,11 @@ export function isActiveWorkerRow(row = {}) {
|
|
|
14
16
|
const status = String(row.status ?? '')
|
|
15
17
|
.trim()
|
|
16
18
|
.toLowerCase();
|
|
17
|
-
|
|
18
|
-
|
|
19
|
+
// `status` decides; the stage is the runtime phase and only stands in for a
|
|
20
|
+
// row with no status. A cancel in either field is not work.
|
|
21
|
+
if (CANCELLED_WORKER_STATUS.test(stage) || CANCELLED_WORKER_STATUS.test(status)) return false;
|
|
22
|
+
const effective = status || stage;
|
|
23
|
+
if (ACTIVE_STAGES.has(effective)) return true;
|
|
19
24
|
if (!effective || effective === 'unknown') return false;
|
|
20
25
|
return !isTerminalWorkerStatus(effective);
|
|
21
26
|
}
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import {
|
|
2
2
|
resolveAgentWatchdogPolicy,
|
|
3
3
|
resolveHandoffMessageStartIndex,
|
|
4
|
+
turnPartialHandoff,
|
|
4
5
|
watchdogPartialHandoffFromError,
|
|
5
6
|
watchdogStoppedHandoff,
|
|
6
7
|
} from '../../../runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs';
|
|
7
8
|
import { clean, normalizeAgentName, resolvePrompt } from './helpers.mjs';
|
|
8
9
|
import { abnormalEmptyFinishError, renderResult } from './render.mjs';
|
|
9
10
|
import {
|
|
11
|
+
reconcileJobCancelled,
|
|
10
12
|
reconcileJobFinally,
|
|
11
13
|
reconcileJobStreamStalled,
|
|
12
14
|
reconcileJobWatchdogPartial,
|
|
@@ -93,6 +95,18 @@ export function createSendFlow({ mgr, defaultCwd, sessionSurface, canUseSessionS
|
|
|
93
95
|
turnStartedAt: new Date().toISOString(),
|
|
94
96
|
});
|
|
95
97
|
let handoffMsgStart = 0;
|
|
98
|
+
// A cancelled turn reports the output it produced before it stopped.
|
|
99
|
+
const settleCancelled = (error, result = null) => {
|
|
100
|
+
finalStatus = 'cancelled';
|
|
101
|
+
reconcileJobCancelled(job, {
|
|
102
|
+
tag,
|
|
103
|
+
sessionId,
|
|
104
|
+
agent: session.agent || null,
|
|
105
|
+
provider: session.provider,
|
|
106
|
+
model: session.model,
|
|
107
|
+
content: turnPartialHandoff(error, mgr.getSession(sessionId), handoffMsgStart) || result?.content || '',
|
|
108
|
+
});
|
|
109
|
+
};
|
|
96
110
|
try {
|
|
97
111
|
handoffMsgStart = resolveHandoffMessageStartIndex(mgr.getSession(sessionId));
|
|
98
112
|
const turnHooks = buildTurnHooks({ sessionId, notifyContext, job, turnReview, completionValue });
|
|
@@ -100,6 +114,10 @@ export function createSendFlow({ mgr, defaultCwd, sessionSurface, canUseSessionS
|
|
|
100
114
|
// Early preview no longer suppresses the canonical body notification;
|
|
101
115
|
// notifyTaskCompletion fires once with output via resolve/reconcile.
|
|
102
116
|
const finalValue = completionValue(result);
|
|
117
|
+
if (job?.status === 'cancelled') {
|
|
118
|
+
settleCancelled(null, result);
|
|
119
|
+
return finalValue;
|
|
120
|
+
}
|
|
103
121
|
if (finalValue.error) {
|
|
104
122
|
finalStatus = 'error';
|
|
105
123
|
if (job) job._terminalResultValue = finalValue;
|
|
@@ -107,6 +125,10 @@ export function createSendFlow({ mgr, defaultCwd, sessionSurface, canUseSessionS
|
|
|
107
125
|
}
|
|
108
126
|
return finalValue;
|
|
109
127
|
} catch (error) {
|
|
128
|
+
if (job?.status === 'cancelled') {
|
|
129
|
+
settleCancelled(error);
|
|
130
|
+
throw error;
|
|
131
|
+
}
|
|
110
132
|
const partial = watchdogPartialHandoffFromError(error, mgr.getSession(sessionId), handoffMsgStart);
|
|
111
133
|
if (partial) {
|
|
112
134
|
finalStatus = 'idle';
|
|
@@ -123,10 +145,6 @@ export function createSendFlow({ mgr, defaultCwd, sessionSurface, canUseSessionS
|
|
|
123
145
|
reconcileJobWatchdogPartial(job, value);
|
|
124
146
|
return value;
|
|
125
147
|
}
|
|
126
|
-
if (job?.status === 'cancelled') {
|
|
127
|
-
finalStatus = 'cancelled';
|
|
128
|
-
throw error;
|
|
129
|
-
}
|
|
130
148
|
finalStatus = 'error';
|
|
131
149
|
reconcileJobStreamStalled(job, error);
|
|
132
150
|
throw error;
|
|
@@ -44,6 +44,8 @@ export function createJobStarter({ mgr, notifyStatusChange, notifySessionComplet
|
|
|
44
44
|
meta: jobMeta,
|
|
45
45
|
resultType: 'agent_task_result',
|
|
46
46
|
renderResult: (result) => renderResult(result),
|
|
47
|
+
// Cancellation reaches the owner once, with the worker's partial output.
|
|
48
|
+
notifyOnCancel: true,
|
|
47
49
|
cancel: () => {
|
|
48
50
|
try {
|
|
49
51
|
admissionController.abort(new Error('agent task cancelled before resource admission'));
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import { createNotify } from '../notify.mjs';
|
|
8
8
|
import {
|
|
9
|
+
reconcileJobCancelled,
|
|
9
10
|
reconcileJobFinally,
|
|
10
11
|
reconcileJobStreamStalled,
|
|
11
12
|
reconcileJobWatchdogPartial,
|
|
@@ -15,11 +16,13 @@ import { abnormalEmptyFinishError } from '../render.mjs';
|
|
|
15
16
|
import { createProgressWatchdogRegistry } from '../../agent-watchdog-registry.mjs';
|
|
16
17
|
import {
|
|
17
18
|
resolveHandoffMessageStartIndex,
|
|
19
|
+
turnPartialHandoff,
|
|
18
20
|
watchdogPartialHandoffFromError,
|
|
19
21
|
watchdogStoppedHandoff,
|
|
20
22
|
} from '../../../../runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs';
|
|
21
23
|
import { saveSessionAsync } from '../../../../runtime/agent/orchestrator/session/store.mjs';
|
|
22
24
|
import { presetDescriptor } from './spawn-prep.mjs';
|
|
25
|
+
import { workerStageForStatus } from '../worker-rows.mjs';
|
|
23
26
|
|
|
24
27
|
// Mid-turn worker persistence cadence. The watchdog sweep runs every second;
|
|
25
28
|
// a transcript save is far more expensive than a bookkeeping stamp, so the
|
|
@@ -68,7 +71,7 @@ export function createSpawnRunner({
|
|
|
68
71
|
const messages = Number(progress?.messages) || 0;
|
|
69
72
|
upsertWorkerSessionDeferred(session, tag, {
|
|
70
73
|
status: 'running',
|
|
71
|
-
stage:
|
|
74
|
+
stage: workerStageForStatus('running', progress.stage),
|
|
72
75
|
...(messages > 0 ? { messages } : {}),
|
|
73
76
|
});
|
|
74
77
|
// The canonical runtime owns persistence; this session is only its descriptor.
|
|
@@ -135,6 +138,16 @@ export function createSpawnRunner({
|
|
|
135
138
|
});
|
|
136
139
|
notifyStatusChange();
|
|
137
140
|
let handoffMsgStart = 0;
|
|
141
|
+
// A cancelled turn reports the output it produced before it stopped.
|
|
142
|
+
const settleCancelled = (error, result = null) => {
|
|
143
|
+
finalStatus = 'cancelled';
|
|
144
|
+
reconcileJobCancelled(job, {
|
|
145
|
+
tag,
|
|
146
|
+
sessionId: session.id,
|
|
147
|
+
...presetDescriptor(agent, preset, presetName),
|
|
148
|
+
content: turnPartialHandoff(error, mgr.getSession(session.id), handoffMsgStart) || result?.content || '',
|
|
149
|
+
});
|
|
150
|
+
};
|
|
138
151
|
try {
|
|
139
152
|
handoffMsgStart = resolveHandoffMessageStartIndex(mgr.getSession(session.id));
|
|
140
153
|
const turnHooks = {
|
|
@@ -157,6 +170,10 @@ export function createSpawnRunner({
|
|
|
157
170
|
// notifyTaskCompletion is left to fire exactly once with output via the
|
|
158
171
|
// resolve/reconcile/finally path.
|
|
159
172
|
const finalValue = completionValue(result);
|
|
173
|
+
if (job?.status === 'cancelled') {
|
|
174
|
+
settleCancelled(null, result);
|
|
175
|
+
return finalValue;
|
|
176
|
+
}
|
|
160
177
|
// Non-job return path (or job path where the terminal-result reconcile
|
|
161
178
|
// already ran): if the finish was abnormal-empty, surface it as a thrown
|
|
162
179
|
// error so finalStatus becomes 'error' and the caller's error path (and
|
|
@@ -168,6 +185,10 @@ export function createSpawnRunner({
|
|
|
168
185
|
}
|
|
169
186
|
return finalValue;
|
|
170
187
|
} catch (error) {
|
|
188
|
+
if (job?.status === 'cancelled') {
|
|
189
|
+
settleCancelled(error);
|
|
190
|
+
throw error;
|
|
191
|
+
}
|
|
171
192
|
const partial = watchdogPartialHandoffFromError(error, mgr.getSession(session.id), handoffMsgStart);
|
|
172
193
|
if (partial) {
|
|
173
194
|
finalStatus = 'idle';
|
|
@@ -182,10 +203,6 @@ export function createSpawnRunner({
|
|
|
182
203
|
reconcileJobWatchdogPartial(job, value);
|
|
183
204
|
return value;
|
|
184
205
|
}
|
|
185
|
-
if (job?.status === 'cancelled') {
|
|
186
|
-
finalStatus = 'cancelled';
|
|
187
|
-
throw error;
|
|
188
|
-
}
|
|
189
206
|
finalStatus = 'error';
|
|
190
207
|
reconcileJobStreamStalled(job, error);
|
|
191
208
|
throw error;
|
|
@@ -11,8 +11,15 @@ const ACTIVE_WORKER_STATUS =
|
|
|
11
11
|
/^(?:connecting|requesting|streaming|tool[-_\s]?running|running|queued|pending|starting|cancelling)$/i;
|
|
12
12
|
const WORKER_POOL_FRESH_MS = 2 * 60 * 1000;
|
|
13
13
|
|
|
14
|
+
const CANCELLED_WORKER_STATUS = /^(?:cancelled|canceled|killed|cancel[-_\s]?(?:unconfirmed|pending))$/i;
|
|
15
|
+
|
|
16
|
+
/** `status` decides; `stage` is the runtime phase copied in by the progress
|
|
17
|
+
* sweep and only decides for a row with no status. A cancel in either field
|
|
18
|
+
* is not work (bare `cancelling` still is). */
|
|
14
19
|
export const isActiveWorkerRow = (row) =>
|
|
15
|
-
|
|
20
|
+
!CANCELLED_WORKER_STATUS.test(clean(row?.status)) &&
|
|
21
|
+
!CANCELLED_WORKER_STATUS.test(clean(row?.stage)) &&
|
|
22
|
+
ACTIVE_WORKER_STATUS.test(clean(row?.status) || clean(row?.stage));
|
|
16
23
|
|
|
17
24
|
/** A persisted approval wait whose runtime is gone: the in-memory wait died
|
|
18
25
|
* with that process (daemon restart), so the flag must not stick. */
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// The one row shape of the worker index: coercing whatever a stored file or a
|
|
3
3
|
// caller hands in, and projecting a live session into a row.
|
|
4
4
|
import { agentTagOf, clean, positiveInt } from '../helpers.mjs';
|
|
5
|
-
import { isLeadPoolAgent } from '../worker-rows.mjs';
|
|
5
|
+
import { isLeadPoolAgent, workerStageForStatus } from '../worker-rows.mjs';
|
|
6
6
|
|
|
7
7
|
/** An explicit boolean wins; otherwise the session's own fast flag (true) or unknown. */
|
|
8
8
|
function fastFlag(explicit, sessionFast) {
|
|
@@ -72,7 +72,7 @@ export function workerRowFromSession(session, fallbackTag = '', extra = {}, getS
|
|
|
72
72
|
if (!tag || !sessionId) return null;
|
|
73
73
|
const runtime = getSessionRuntime(sessionId);
|
|
74
74
|
const status = clean(extra.status) || (session?.closed === true ? 'closed' : clean(session?.status) || 'idle');
|
|
75
|
-
const stage = clean(extra.stage) ||
|
|
75
|
+
const stage = clean(extra.stage) || workerStageForStatus(status, runtime?.stage);
|
|
76
76
|
const nowIso = new Date().toISOString();
|
|
77
77
|
return {
|
|
78
78
|
tag,
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { agentTagOf, clean, positiveInt, stampMs } from './helpers.mjs';
|
|
2
|
+
import { ACTIVE_STAGES } from './tool-def.mjs';
|
|
2
3
|
|
|
3
4
|
import { TAG_TOMBSTONE_TTL_MS, tagTombstoneKey } from '../../../runtime/shared/agent-reap-state.mjs';
|
|
4
5
|
export { TAG_TOMBSTONE_TTL_MS, tagTombstoneKey };
|
|
@@ -27,6 +28,22 @@ export function isTerminalWorkerStatus(status) {
|
|
|
27
28
|
);
|
|
28
29
|
}
|
|
29
30
|
|
|
31
|
+
const WORKING_WORKER_STATUS =
|
|
32
|
+
/^(?:connecting|requesting|streaming|tool[-_\s]?running|running|queued|pending|starting|cancelling)$/i;
|
|
33
|
+
const WORKING_WORKER_STAGES = new Set([...ACTIVE_STAGES, 'resource_wait']);
|
|
34
|
+
|
|
35
|
+
/** The `stage` to store beside a worker `status`, from a stage read off the
|
|
36
|
+
* live runtime. That stage is the runtime's own phase, so mid-work it can say
|
|
37
|
+
* `idle`/`done`/`error` (a fresh or just-settled runtime entry) while the
|
|
38
|
+
* worker is still running. Stored next to a working status it made a mixed
|
|
39
|
+
* row that readers classified as finished, so a working status keeps a
|
|
40
|
+
* working stage and falls back to the status itself. */
|
|
41
|
+
export function workerStageForStatus(status, runtimeStage) {
|
|
42
|
+
const stage = clean(runtimeStage);
|
|
43
|
+
if (!WORKING_WORKER_STATUS.test(clean(status))) return stage || clean(status);
|
|
44
|
+
return WORKING_WORKER_STAGES.has(stage.toLowerCase()) ? stage : clean(status);
|
|
45
|
+
}
|
|
46
|
+
|
|
30
47
|
export function recoverTagTombstoneOwner(row, sessions) {
|
|
31
48
|
if (clean(row.parentSessionId || row.ownerSessionId)) return row;
|
|
32
49
|
const pid = positiveInt(row.clientHostPid);
|
|
@@ -82,6 +82,7 @@ export function createCompactionSettings({
|
|
|
82
82
|
const next = { ...current };
|
|
83
83
|
if (hasOwn(input, 'auto')) next.auto = input.auto !== false;
|
|
84
84
|
if (hasOwn(input, 'enabled')) next.auto = input.enabled !== false;
|
|
85
|
+
if (hasOwn(input, 'onModelChange')) next.onModelChange = input.onModelChange !== false;
|
|
85
86
|
// Legacy Compact type fields are intentionally ignored. There is one
|
|
86
87
|
// fresh-context Compact contract for every session.
|
|
87
88
|
// A new budget representation replaces the previous one. Otherwise a
|
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
/** Whether an idle GC sweep may start: no service call and no running turn
|
|
2
|
+
* (provider stream / tool call). Background-task-only sessions and memory
|
|
3
|
+
* agents do not block it — unlike drainDeferral, which guards shutdown. */
|
|
4
|
+
export function idleGcEligible(work) {
|
|
5
|
+
const { activeCalls = 0, queuedCalls = 0, turnBusySessions = 0 } = work || {};
|
|
6
|
+
return !(activeCalls > 0 || queuedCalls > 0 || turnBusySessions > 0);
|
|
7
|
+
}
|
|
8
|
+
|
|
1
9
|
/** Whether self-shutdown must wait for in-flight work. A forced replacement
|
|
2
10
|
* never waits: shutdown() aborts busy sessions, service calls and agent runs. */
|
|
3
11
|
export function drainDeferral(work, { force = false } = {}) {
|
|
@@ -93,6 +93,52 @@ export function collectDaemonTelemetry({
|
|
|
93
93
|
};
|
|
94
94
|
}
|
|
95
95
|
|
|
96
|
+
/**
|
|
97
|
+
* Tracks the smallest heapUsed observed right after a major GC (mark-sweep-
|
|
98
|
+
* compact) — the retained-set size, which heapUsed at an arbitrary instant
|
|
99
|
+
* hides. Sampled per telemetry interval; no per-GC logging. Returns null when
|
|
100
|
+
* PerformanceObserver is unavailable.
|
|
101
|
+
*/
|
|
102
|
+
export function createPostGcHeapTracker({
|
|
103
|
+
heapUsed = () => v8.getHeapStatistics().used_heap_size,
|
|
104
|
+
observe = (onGc) => {
|
|
105
|
+
const obs = new PerformanceObserver((list) => {
|
|
106
|
+
for (const entry of list.getEntries()) onGc(entry);
|
|
107
|
+
});
|
|
108
|
+
obs.observe({ entryTypes: ['gc'] });
|
|
109
|
+
return () => obs.disconnect();
|
|
110
|
+
},
|
|
111
|
+
} = {}) {
|
|
112
|
+
let min = -1;
|
|
113
|
+
let disconnect = null;
|
|
114
|
+
try {
|
|
115
|
+
disconnect = observe((entry) => {
|
|
116
|
+
// NODE_PERFORMANCE_GC_MAJOR = 4 (older Node: entry.kind); newer: entry.detail.kind.
|
|
117
|
+
const kind = entry?.detail?.kind ?? entry?.kind;
|
|
118
|
+
if (kind !== 4) return;
|
|
119
|
+
const used = byteCount(heapUsed());
|
|
120
|
+
if (used >= 0 && (min < 0 || used < min)) min = used;
|
|
121
|
+
});
|
|
122
|
+
} catch {
|
|
123
|
+
return null;
|
|
124
|
+
}
|
|
125
|
+
return {
|
|
126
|
+
/** Min post-major-GC heap since the last take; -1 when none ran. */
|
|
127
|
+
take() {
|
|
128
|
+
const value = min;
|
|
129
|
+
min = -1;
|
|
130
|
+
return value;
|
|
131
|
+
},
|
|
132
|
+
stop() {
|
|
133
|
+
try {
|
|
134
|
+
disconnect?.();
|
|
135
|
+
} catch {
|
|
136
|
+
/* already gone */
|
|
137
|
+
}
|
|
138
|
+
},
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
|
|
96
142
|
export function formatDaemonTelemetry(record, reason = 'periodic') {
|
|
97
143
|
const row = record && typeof record === 'object' ? record : collectDaemonTelemetry();
|
|
98
144
|
return [
|
|
@@ -110,6 +156,8 @@ export function formatDaemonTelemetry(record, reason = 'periodic') {
|
|
|
110
156
|
`queuedCalls=${row.queuedCalls}`,
|
|
111
157
|
`busySessions=${row.busySessions}`,
|
|
112
158
|
`busyMemoryAgents=${row.busyMemoryAgents}`,
|
|
159
|
+
// Present only when a major GC ran since the previous line.
|
|
160
|
+
...(byteCount(row.postGcHeapMinBytes) >= 0 ? [`postGcHeapMinBytes=${row.postGcHeapMinBytes}`] : []),
|
|
113
161
|
].join(' ');
|
|
114
162
|
}
|
|
115
163
|
|
|
@@ -123,8 +171,10 @@ export function createDaemonTelemetry({
|
|
|
123
171
|
heapStatistics,
|
|
124
172
|
setIntervalFn = setInterval,
|
|
125
173
|
clearIntervalFn = clearInterval,
|
|
174
|
+
createGcTracker = createPostGcHeapTracker,
|
|
126
175
|
} = {}) {
|
|
127
176
|
let timer = null;
|
|
177
|
+
let gcTracker = null;
|
|
128
178
|
|
|
129
179
|
function emit(reason = 'periodic') {
|
|
130
180
|
try {
|
|
@@ -141,6 +191,8 @@ export function createDaemonTelemetry({
|
|
|
141
191
|
heapStatistics,
|
|
142
192
|
work,
|
|
143
193
|
});
|
|
194
|
+
const postGc = gcTracker?.take() ?? -1;
|
|
195
|
+
if (postGc >= 0) record.postGcHeapMinBytes = postGc;
|
|
144
196
|
log(formatDaemonTelemetry(record, reason));
|
|
145
197
|
return record;
|
|
146
198
|
} catch {
|
|
@@ -159,6 +211,7 @@ export function createDaemonTelemetry({
|
|
|
159
211
|
|
|
160
212
|
function start(intervalMs = DAEMON_TELEMETRY_INTERVAL_MS) {
|
|
161
213
|
if (timer) return timer;
|
|
214
|
+
gcTracker ??= createGcTracker();
|
|
162
215
|
const delay = Number(intervalMs);
|
|
163
216
|
const ms = Number.isFinite(delay) && delay > 0 ? delay : DAEMON_TELEMETRY_INTERVAL_MS;
|
|
164
217
|
const handle = setIntervalFn(() => {
|
|
@@ -174,6 +227,8 @@ export function createDaemonTelemetry({
|
|
|
174
227
|
if (!timer) return;
|
|
175
228
|
clearIntervalFn(timer);
|
|
176
229
|
timer = null;
|
|
230
|
+
gcTracker?.stop();
|
|
231
|
+
gcTracker = null;
|
|
177
232
|
}
|
|
178
233
|
|
|
179
234
|
return { emit, start, stop };
|
|
@@ -65,7 +65,7 @@ import {
|
|
|
65
65
|
} from '../runtime/agent/orchestrator/providers/stream-json-pool.mjs';
|
|
66
66
|
import { createAgentDispatchBroker } from './agent-dispatch-broker.mjs';
|
|
67
67
|
import { createChannelTransport } from './channel-transport.mjs';
|
|
68
|
-
import { drainDeferral } from './daemon-drain-policy.mjs';
|
|
68
|
+
import { drainDeferral, idleGcEligible } from './daemon-drain-policy.mjs';
|
|
69
69
|
import { createChannelSessionRouter } from '../session-runtime/services/channel-session-router.mjs';
|
|
70
70
|
import { createSessionTransport } from './session-transport.mjs';
|
|
71
71
|
import { createSessionService } from './session-service.mjs';
|
|
@@ -309,6 +309,7 @@ function inFlightWork() {
|
|
|
309
309
|
queuedCalls: (transport?.queuedCount ?? 0) + (sessionTransport?.queuedCount ?? 0),
|
|
310
310
|
busySessions: sessionService?.busyCount ?? 0,
|
|
311
311
|
busyMemoryAgents: agentDispatchBroker?.snapshot?.().inFlight ?? 0,
|
|
312
|
+
turnBusySessions: sessionService?.turnBusyCount ?? 0,
|
|
312
313
|
};
|
|
313
314
|
}
|
|
314
315
|
|
|
@@ -353,11 +354,6 @@ daemonTelemetry = createDaemonTelemetry({
|
|
|
353
354
|
},
|
|
354
355
|
});
|
|
355
356
|
|
|
356
|
-
function daemonHasWorkInFlight() {
|
|
357
|
-
const work = inFlightWork();
|
|
358
|
-
return work.activeCalls > 0 || work.queuedCalls > 0 || work.busySessions > 0 || work.busyMemoryAgents > 0;
|
|
359
|
-
}
|
|
360
|
-
|
|
361
357
|
/** One process, two front doors (channels + sessions): an idle side must never
|
|
362
358
|
* evict a busy one, so every self-shutdown trigger checks BOTH registries. */
|
|
363
359
|
let _lastDeferredLog = { key: '', at: 0, suppressed: 0 };
|
|
@@ -823,7 +819,7 @@ async function main() {
|
|
|
823
819
|
// Reclaim deferred garbage while nothing is in flight. V8 keeps a long-lived
|
|
824
820
|
// daemon's dead transcripts resident for as long as the heap limit stays out
|
|
825
821
|
// of sight; a measured sweep on this daemon returned 140MB in 98ms.
|
|
826
|
-
idleGc = createIdleGc({ isBusy:
|
|
822
|
+
idleGc = createIdleGc({ isBusy: () => !idleGcEligible(inFlightWork()), log });
|
|
827
823
|
if (idleGc.arm()) log('idle gc armed');
|
|
828
824
|
|
|
829
825
|
const channelsRuntime = createChannelsRuntimeLoader({
|
|
@@ -19,6 +19,7 @@ export const SESSION_READ_ACTIONS = Object.freeze([
|
|
|
19
19
|
'getMediaDefault',
|
|
20
20
|
'getMediaJob',
|
|
21
21
|
'getMcpServerConfig',
|
|
22
|
+
'getMcpOAuthLoginStatus',
|
|
22
23
|
'getOnboardingStatus',
|
|
23
24
|
'getOAuthProviderLoginStatus',
|
|
24
25
|
'getOutputStyle',
|
|
@@ -85,16 +86,19 @@ export const SESSION_CONFIGURE_ACTIONS = Object.freeze([
|
|
|
85
86
|
'agentControl',
|
|
86
87
|
'authenticateProvider',
|
|
87
88
|
'beginOAuthProviderLogin',
|
|
89
|
+
'beginMcpOAuthLogin',
|
|
88
90
|
'updateProviderAccounts',
|
|
89
91
|
'cacheMediaThumbnail',
|
|
90
92
|
'cancelMediaJob',
|
|
91
93
|
'cancelOAuthProviderLogin',
|
|
94
|
+
'cancelMcpOAuthLogin',
|
|
92
95
|
'cancelConversationImport',
|
|
93
96
|
'checkForUpdate',
|
|
94
97
|
'clear',
|
|
95
98
|
'compact',
|
|
96
99
|
'completeOnboarding',
|
|
97
100
|
'completeOAuthProviderLogin',
|
|
101
|
+
'completeMcpOAuthLogin',
|
|
98
102
|
'consumeCodexRateLimitResetCredit',
|
|
99
103
|
'createWorkflow',
|
|
100
104
|
'deleteAgentDefinition',
|
|
@@ -118,6 +122,7 @@ export const SESSION_CONFIGURE_ACTIONS = Object.freeze([
|
|
|
118
122
|
'memoryControl',
|
|
119
123
|
'openMediaAsset',
|
|
120
124
|
'openMediaFolder',
|
|
125
|
+
'probeMcpServerAuth',
|
|
121
126
|
'pushNotice',
|
|
122
127
|
'recall',
|
|
123
128
|
'reconnectMcp',
|
|
@@ -143,6 +148,7 @@ export const SESSION_CONFIGURE_ACTIONS = Object.freeze([
|
|
|
143
148
|
'revertTurnReviewFile',
|
|
144
149
|
'revertTurnChanges',
|
|
145
150
|
'reapplyTurnChanges',
|
|
151
|
+
'clearSessionChangeHistory',
|
|
146
152
|
'rewindToItem',
|
|
147
153
|
'runDoctor',
|
|
148
154
|
'runScheduleNow',
|
|
@@ -200,6 +206,7 @@ export const SESSION_CONFIGURE_ACTIONS = Object.freeze([
|
|
|
200
206
|
'setWebhookEnabled',
|
|
201
207
|
'setWorkflow',
|
|
202
208
|
'setOrchestrationMode',
|
|
209
|
+
'signOutMcpServer',
|
|
203
210
|
'saveStarterGuideState',
|
|
204
211
|
'skipOnboarding',
|
|
205
212
|
'startConversationImport',
|