mixdog 0.9.45 → 0.9.47
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 +5 -2
- package/scripts/agent-dispatch-abort-compose-test.mjs +31 -0
- package/scripts/agent-model-liveness-test.mjs +618 -0
- package/scripts/agent-trace-io-test.mjs +68 -4
- package/scripts/bench-run.mjs +30 -3
- package/scripts/compact-pressure-test.mjs +187 -4
- package/scripts/compact-prior-context-flatten-test.mjs +252 -0
- package/scripts/compact-trigger-migration-smoke.mjs +8 -6
- package/scripts/compacted-placeholder-scrub-test.mjs +20 -34
- package/scripts/context-mcp-metering-test.mjs +75 -0
- package/scripts/explore-prompt-policy-test.mjs +15 -16
- package/scripts/explore-timeout-cancel-test.mjs +345 -0
- package/scripts/headless-pristine-execution-test.mjs +614 -0
- package/scripts/internal-comms-smoke.mjs +15 -6
- package/scripts/live-worker-smoke.mjs +1 -1
- package/scripts/memory-core-input-test.mjs +137 -0
- package/scripts/memory-rule-contract-test.mjs +5 -5
- package/scripts/openai-oauth-refresh-race-test.mjs +120 -0
- package/scripts/openai-oauth-ws-1006-retry-test.mjs +26 -0
- package/scripts/parent-abort-link-test.mjs +22 -0
- package/scripts/provider-toolcall-test.mjs +22 -0
- package/scripts/reactive-compact-persist-smoke.mjs +8 -4
- package/scripts/session-bench.mjs +3 -70
- package/scripts/task-bench.mjs +0 -2
- package/scripts/terminal-bench-isolation-guards-test.mjs +102 -0
- package/scripts/tool-smoke.mjs +21 -21
- package/scripts/tool-tui-presentation-test.mjs +68 -0
- package/scripts/tui-transcript-jitter-harness-entry.jsx +91 -10
- package/src/app.mjs +28 -103
- package/src/cli.mjs +17 -13
- package/src/defaults/cycle3-review-prompt.md +10 -4
- package/src/defaults/memory-promote-prompt.md +4 -3
- package/src/headless-command.mjs +139 -0
- package/src/headless-role.mjs +121 -10
- package/src/help.mjs +4 -1
- package/src/rules/agent/00-common.md +3 -3
- package/src/rules/agent/00-core.md +8 -9
- package/src/rules/agent/20-skip-protocol.md +2 -3
- package/src/rules/agent/30-explorer.md +50 -56
- package/src/rules/agent/40-cycle1-agent.md +10 -12
- package/src/rules/agent/41-cycle2-agent.md +12 -9
- package/src/rules/agent/42-cycle3-agent.md +4 -6
- package/src/rules/lead/01-general.md +5 -6
- package/src/rules/lead/02-channels.md +1 -1
- package/src/rules/lead/lead-brief.md +14 -17
- package/src/rules/lead/lead-tool.md +3 -3
- package/src/rules/shared/01-tool.md +41 -43
- package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch.mjs +46 -10
- package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +44 -31
- package/src/runtime/agent/orchestrator/agent-trace-io.mjs +18 -3
- package/src/runtime/agent/orchestrator/config.mjs +96 -30
- package/src/runtime/agent/orchestrator/context/collect.mjs +9 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth-credentials.mjs +3 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +18 -5
- package/src/runtime/agent/orchestrator/providers/anthropic.mjs +6 -6
- package/src/runtime/agent/orchestrator/providers/gemini.mjs +6 -6
- package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +123 -3
- package/src/runtime/agent/orchestrator/providers/oauth-credential-probes.mjs +12 -3
- package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +148 -30
- package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +5 -7
- package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +62 -14
- package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +141 -19
- package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +12 -4
- package/src/runtime/agent/orchestrator/providers/openai-ws-pool.mjs +19 -1
- package/src/runtime/agent/orchestrator/providers/openai-ws-stream.mjs +85 -17
- package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +6 -8
- package/src/runtime/agent/orchestrator/providers/registry.mjs +47 -17
- package/src/runtime/agent/orchestrator/session/compact/summary.mjs +159 -20
- package/src/runtime/agent/orchestrator/session/context-utils.mjs +83 -10
- package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +75 -7
- package/src/runtime/agent/orchestrator/session/loop/steering-ladder.mjs +4 -374
- package/src/runtime/agent/orchestrator/session/loop/stored-tool-args.mjs +0 -75
- package/src/runtime/agent/orchestrator/session/loop/transcript-repair.mjs +0 -5
- package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +20 -3
- package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +32 -16
- package/src/runtime/agent/orchestrator/session/manager/context-meta.mjs +5 -2
- package/src/runtime/agent/orchestrator/session/manager/runtime-liveness.mjs +86 -15
- package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +30 -27
- package/src/runtime/agent/orchestrator/session/tool-batch.mjs +1 -14
- package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +21 -27
- package/src/runtime/agent/orchestrator/tools/builtin/search-tool.mjs +1 -3
- package/src/runtime/agent/orchestrator/tools/builtin.mjs +1 -51
- package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +4 -4
- package/src/runtime/agent/orchestrator/tools/patch/orchestrator.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/patch-manifest.json +26 -0
- package/src/runtime/memory/index.mjs +0 -1
- package/src/runtime/memory/lib/core-memory-store.mjs +44 -24
- package/src/runtime/memory/lib/http-router.mjs +0 -193
- package/src/runtime/memory/lib/memory-action-handlers.mjs +37 -11
- package/src/runtime/memory/lib/memory-cycle2-gate.mjs +13 -5
- package/src/runtime/memory/lib/memory-cycle2.mjs +8 -5
- package/src/runtime/memory/lib/memory-cycle3.mjs +41 -6
- package/src/runtime/memory/lib/query-handlers.mjs +49 -6
- package/src/runtime/memory/lib/recall-format.mjs +21 -6
- package/src/runtime/memory/tool-defs.mjs +5 -6
- package/src/runtime/shared/config.mjs +11 -34
- package/src/runtime/shared/pristine-execution-contract.json +75 -0
- package/src/runtime/shared/pristine-execution.mjs +356 -0
- package/src/runtime/shared/provider-api-key.mjs +43 -0
- package/src/runtime/shared/provider-auth-binding.mjs +21 -0
- package/src/session-runtime/context-status.mjs +61 -13
- package/src/session-runtime/mcp-glue.mjs +29 -2
- package/src/session-runtime/plugin-mcp.mjs +7 -0
- package/src/session-runtime/resource-api.mjs +38 -5
- package/src/session-runtime/runtime-core.mjs +5 -1
- package/src/session-runtime/session-turn-api.mjs +14 -2
- package/src/session-runtime/settings-api.mjs +5 -0
- package/src/session-runtime/tool-catalog.mjs +13 -2
- package/src/session-runtime/tool-defs.mjs +1 -3
- package/src/standalone/agent-task-status.mjs +50 -11
- package/src/standalone/agent-tool/tool-def.mjs +1 -1
- package/src/standalone/explore-tool.mjs +257 -49
- package/src/standalone/seeds.mjs +1 -0
- package/src/tui/App.jsx +23 -10
- package/src/tui/app/use-transcript-scroll.mjs +4 -3
- package/src/tui/app/use-transcript-window.mjs +12 -21
- package/src/tui/components/ContextPanel.jsx +19 -25
- package/src/tui/dist/index.mjs +77 -65
- package/src/tui/engine/agent-envelope.mjs +16 -5
- package/src/tui/engine/labels.mjs +1 -1
- package/src/workflows/default/WORKFLOW.md +21 -51
- package/src/workflows/solo/WORKFLOW.md +12 -17
- package/src/workflows/solo-review/WORKFLOW.md +0 -47
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { createRequire } from 'node:module';
|
|
2
|
+
|
|
3
|
+
const require = createRequire(import.meta.url);
|
|
4
|
+
const { getSecret } = require('../../lib/keychain-cjs.cjs');
|
|
5
|
+
|
|
6
|
+
// Lightweight API-key lookup shared by pristine startup and the general config
|
|
7
|
+
// loader. This module deliberately has no mixdog-config.json dependency: asking
|
|
8
|
+
// for one provider probes only that provider's environment/keychain account.
|
|
9
|
+
export const AGENT_PROVIDER_ENV = Object.freeze({
|
|
10
|
+
openai: 'OPENAI_API_KEY',
|
|
11
|
+
anthropic: 'ANTHROPIC_API_KEY',
|
|
12
|
+
gemini: 'GEMINI_API_KEY',
|
|
13
|
+
deepseek: 'DEEPSEEK_API_KEY',
|
|
14
|
+
xai: 'XAI_API_KEY',
|
|
15
|
+
'opencode-go': 'OPENCODE_API_KEY',
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
export const AGENT_PROVIDER_ENV_ALIASES = Object.freeze({
|
|
19
|
+
xai: ['GROK_API_KEY'],
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
function providerSecretEnv(provider) {
|
|
23
|
+
return `MIXDOG_AGENT_${String(provider || '').replace(/[.\s]+/g, '_').toUpperCase()}_APIKEY`;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function getAgentApiKey(provider) {
|
|
27
|
+
const id = String(provider || '').trim();
|
|
28
|
+
const standardEnv = AGENT_PROVIDER_ENV[id];
|
|
29
|
+
if (standardEnv && process.env[standardEnv]) return process.env[standardEnv];
|
|
30
|
+
const mixdogEnv = providerSecretEnv(id);
|
|
31
|
+
if (process.env[mixdogEnv]) return process.env[mixdogEnv];
|
|
32
|
+
try {
|
|
33
|
+
const stored = getSecret(`agent.${id}.apiKey`);
|
|
34
|
+
if (stored) return stored;
|
|
35
|
+
} catch {
|
|
36
|
+
// A missing/locked keychain is equivalent to no stored key. Explicit
|
|
37
|
+
// environment aliases remain available as the final provider-local source.
|
|
38
|
+
}
|
|
39
|
+
for (const alias of AGENT_PROVIDER_ENV_ALIASES[id] || []) {
|
|
40
|
+
if (process.env[alias]) return process.env[alias];
|
|
41
|
+
}
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
const AUTH_BINDINGS = Symbol.for('mixdog.providerAuthBindings');
|
|
2
|
+
|
|
3
|
+
export function boundProviderAuthPath(provider) {
|
|
4
|
+
const bindings = globalThis[AUTH_BINDINGS];
|
|
5
|
+
const value = bindings && typeof bindings === 'object'
|
|
6
|
+
? bindings[String(provider || '').trim()]
|
|
7
|
+
: null;
|
|
8
|
+
return typeof value === 'string' && value ? value : null;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function replaceProviderAuthBindings(next = {}) {
|
|
12
|
+
const previous = globalThis[AUTH_BINDINGS];
|
|
13
|
+
globalThis[AUTH_BINDINGS] = Object.freeze({ ...next });
|
|
14
|
+
let restored = false;
|
|
15
|
+
return () => {
|
|
16
|
+
if (restored) return;
|
|
17
|
+
restored = true;
|
|
18
|
+
if (previous === undefined) delete globalThis[AUTH_BINDINGS];
|
|
19
|
+
else globalThis[AUTH_BINDINGS] = previous;
|
|
20
|
+
};
|
|
21
|
+
}
|
|
@@ -7,8 +7,30 @@ import {
|
|
|
7
7
|
summarizeContextMessages,
|
|
8
8
|
toolSchemaSignature,
|
|
9
9
|
} from '../runtime/agent/orchestrator/session/context-utils.mjs';
|
|
10
|
+
import { resolveWorkerCompactPolicy } from '../runtime/agent/orchestrator/session/loop/compact-policy.mjs';
|
|
10
11
|
import { estimateToolSchemaBreakdown } from './tool-catalog.mjs';
|
|
11
12
|
|
|
13
|
+
const DEFERRED_CATALOG_TOOL_PROVIDERS = new Set(['anthropic', 'anthropic-oauth']);
|
|
14
|
+
|
|
15
|
+
// Mirrors the tool-list portion of the Anthropic adapters without changing
|
|
16
|
+
// their wire serialization. Other native-deferred providers expose the
|
|
17
|
+
// catalog through BP1/system content, which is already metered there.
|
|
18
|
+
export function requestSerializedToolsForContext(session, provider) {
|
|
19
|
+
const activeTools = Array.isArray(session?.tools) ? session.tools : [];
|
|
20
|
+
const normalizedProvider = String(provider || '').trim().toLowerCase();
|
|
21
|
+
if (!DEFERRED_CATALOG_TOOL_PROVIDERS.has(normalizedProvider)
|
|
22
|
+
|| session?.deferredNativeTools !== true
|
|
23
|
+
|| activeTools.length === 0) {
|
|
24
|
+
return activeTools;
|
|
25
|
+
}
|
|
26
|
+
const activeNames = new Set(activeTools.map((tool) => String(tool?.name || '').trim()).filter(Boolean));
|
|
27
|
+
const catalog = Array.isArray(session?.deferredToolCatalog) ? session.deferredToolCatalog : [];
|
|
28
|
+
const deferredTools = catalog
|
|
29
|
+
.filter((tool) => tool?.name && !activeNames.has(String(tool.name)))
|
|
30
|
+
.map((tool) => ({ ...tool, deferLoading: true }));
|
|
31
|
+
return deferredTools.length ? [...activeTools, ...deferredTools] : activeTools;
|
|
32
|
+
}
|
|
33
|
+
|
|
12
34
|
// Live /context gauge computation + its self-owned memoization cache. Extracted
|
|
13
35
|
// verbatim from the runtime API object; the runtime injects live getters for
|
|
14
36
|
// the mutable session/route/cwd/mode locals. The cache (key + value) is owned
|
|
@@ -18,7 +40,15 @@ export function createContextStatus({ getSession, getRoute, getCurrentCwd, getMo
|
|
|
18
40
|
let contextStatusCacheKey = null;
|
|
19
41
|
let contextStatusCacheValue = null;
|
|
20
42
|
|
|
21
|
-
function contextStatusCacheKeyFor({
|
|
43
|
+
function contextStatusCacheKeyFor({
|
|
44
|
+
messages,
|
|
45
|
+
tools,
|
|
46
|
+
messagesRevision,
|
|
47
|
+
toolsSignature,
|
|
48
|
+
requestProvider,
|
|
49
|
+
requestToolCount,
|
|
50
|
+
requestToolsSignature,
|
|
51
|
+
}) {
|
|
22
52
|
const session = getSession();
|
|
23
53
|
const route = getRoute();
|
|
24
54
|
const compaction = session?.compaction || {};
|
|
@@ -39,6 +69,9 @@ export function createContextStatus({ getSession, getRoute, getCurrentCwd, getMo
|
|
|
39
69
|
tools,
|
|
40
70
|
toolCount: tools.length,
|
|
41
71
|
toolsSignature,
|
|
72
|
+
requestProvider,
|
|
73
|
+
requestToolCount,
|
|
74
|
+
requestToolsSignature,
|
|
42
75
|
contextWindow: session?.contextWindow || null,
|
|
43
76
|
rawContextWindow: session?.rawContextWindow || null,
|
|
44
77
|
effectiveContextWindowPercent: session?.effectiveContextWindowPercent || null,
|
|
@@ -89,22 +122,33 @@ export function createContextStatus({ getSession, getRoute, getCurrentCwd, getMo
|
|
|
89
122
|
const liveTurnMessages = Array.isArray(session?.liveTurnMessages) ? session.liveTurnMessages : null;
|
|
90
123
|
const messages = liveTurnMessages || (Array.isArray(session?.messages) ? session.messages : []);
|
|
91
124
|
const tools = Array.isArray(session?.tools) ? session.tools : [];
|
|
125
|
+
const requestProvider = session?.provider || route.provider;
|
|
126
|
+
const requestTools = requestSerializedToolsForContext(session, requestProvider);
|
|
92
127
|
const messagesRevision = contextMessagesRevision(messages);
|
|
93
128
|
const toolsSignature = toolSchemaSignature(tools);
|
|
94
|
-
const
|
|
129
|
+
const requestToolsSignature = toolSchemaSignature(requestTools);
|
|
130
|
+
const cacheKey = contextStatusCacheKeyFor({
|
|
131
|
+
messages,
|
|
132
|
+
tools,
|
|
133
|
+
messagesRevision,
|
|
134
|
+
toolsSignature,
|
|
135
|
+
requestProvider,
|
|
136
|
+
requestToolCount: requestTools.length,
|
|
137
|
+
requestToolsSignature,
|
|
138
|
+
});
|
|
95
139
|
if (contextStatusCacheValue && sameContextStatusCacheKey(cacheKey, contextStatusCacheKey)) {
|
|
96
140
|
return contextStatusCacheValue;
|
|
97
141
|
}
|
|
98
142
|
|
|
99
143
|
const messageSummary = summarizeContextMessages(messages);
|
|
100
|
-
const toolSchemaTokens = estimateToolSchemaTokens(
|
|
101
|
-
const toolSchemaBreakdown = estimateToolSchemaBreakdown(
|
|
102
|
-
const requestReserveTokens = estimateRequestReserveTokens(
|
|
144
|
+
const toolSchemaTokens = estimateToolSchemaTokens(requestTools);
|
|
145
|
+
const toolSchemaBreakdown = estimateToolSchemaBreakdown(requestTools);
|
|
146
|
+
const requestReserveTokens = estimateRequestReserveTokens(requestTools);
|
|
103
147
|
const requestOverheadTokens = Math.max(0, requestReserveTokens - toolSchemaTokens);
|
|
104
148
|
const rawWindow = Number(session?.rawContextWindow || session?.contextWindow || 0);
|
|
105
149
|
const effectiveWindow = Number(session?.contextWindow || rawWindow || 0);
|
|
106
150
|
const lastContextTokens = Number(session?.lastContextTokens || 0);
|
|
107
|
-
const estimatedContextTokens = estimateTranscriptContextUsage(messages,
|
|
151
|
+
const estimatedContextTokens = estimateTranscriptContextUsage(messages, requestTools, {
|
|
108
152
|
messageCount: messageSummary.count,
|
|
109
153
|
estimatedMessageTokens: messageSummary.estimatedTokens,
|
|
110
154
|
});
|
|
@@ -125,15 +169,18 @@ export function createContextStatus({ getSession, getRoute, getCurrentCwd, getMo
|
|
|
125
169
|
// the gauge numerator.
|
|
126
170
|
const usedTokens = estimatedContextTokens;
|
|
127
171
|
const freeTokens = displayWindow ? Math.max(0, displayWindow - usedTokens) : 0;
|
|
128
|
-
// Use the
|
|
129
|
-
//
|
|
130
|
-
//
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
172
|
+
// Use the worker policy when a boundary is available so target/reserve
|
|
173
|
+
// headroom, trigger, buffer tokens, and buffer ratio stay identical to the
|
|
174
|
+
// auto-compact decision. Fall back only for incomplete session metadata.
|
|
175
|
+
const workerCompactPolicy = resolveWorkerCompactPolicy(session, tools);
|
|
176
|
+
const compactPolicy = workerCompactPolicy?.boundaryTokens
|
|
177
|
+
? workerCompactPolicy
|
|
178
|
+
: resolveSessionCompactPolicy(session || {}, compactBoundaryTokens);
|
|
135
179
|
const compactTriggerTokens = compactPolicy.triggerTokens || 0;
|
|
136
180
|
const compactBufferTokens = compactPolicy.bufferTokens || 0;
|
|
181
|
+
const compactBufferRatio = Number.isFinite(compactPolicy.bufferRatio)
|
|
182
|
+
? compactPolicy.bufferRatio
|
|
183
|
+
: null;
|
|
137
184
|
const value = {
|
|
138
185
|
sessionId: session?.id || null,
|
|
139
186
|
provider: route.provider,
|
|
@@ -155,6 +202,7 @@ export function createContextStatus({ getSession, getRoute, getCurrentCwd, getMo
|
|
|
155
202
|
boundaryTokens: compactBoundaryTokens || null,
|
|
156
203
|
triggerTokens: compactTriggerTokens || null,
|
|
157
204
|
bufferTokens: compactBufferTokens || null,
|
|
205
|
+
bufferRatio: compactBufferRatio,
|
|
158
206
|
currentEstimatedTokens: estimatedContextTokens,
|
|
159
207
|
lastApiRequestTokens: lastContextTokens || 0,
|
|
160
208
|
lastApiRequestStale: lastUsageStale,
|
|
@@ -6,13 +6,19 @@
|
|
|
6
6
|
import { resolve } from 'node:path';
|
|
7
7
|
import { statSync } from 'node:fs';
|
|
8
8
|
import { clean } from './session-text.mjs';
|
|
9
|
-
import { readProjectMcpServers } from './plugin-mcp.mjs';
|
|
9
|
+
import { normalizeMcpProjectPathKey, readProjectMcpServers } from './plugin-mcp.mjs';
|
|
10
10
|
|
|
11
11
|
// Cache project-local `.mcp.json` reads by path + mtime so repeated mcpStatus()
|
|
12
12
|
// calls skip existsSync+readFileSync+JSON.parse when the file is unchanged.
|
|
13
13
|
// Invalidated automatically on any mtime change (or create/delete via mtime=0).
|
|
14
14
|
const projectMcpCache = new Map();
|
|
15
15
|
const PROJECT_MCP_CACHE_MAX = 32;
|
|
16
|
+
export function invalidateProjectMcpCache(cwd) {
|
|
17
|
+
projectMcpCache.delete(resolve(cwd || '.', '.mcp.json'));
|
|
18
|
+
}
|
|
19
|
+
function mcpDisabled() {
|
|
20
|
+
return /^(?:1|true|on|yes)$/i.test(String(process.env.MIXDOG_DISABLE_MCP || ''));
|
|
21
|
+
}
|
|
16
22
|
function cachedProjectMcpServers(cwd) {
|
|
17
23
|
const path = resolve(cwd || '.', '.mcp.json');
|
|
18
24
|
let mtimeMs = 0;
|
|
@@ -48,12 +54,23 @@ export function createMcpGlue({
|
|
|
48
54
|
// (precedence: project > user config). `sources[name]` records each server's
|
|
49
55
|
// origin ('config' | 'project') for status reporting.
|
|
50
56
|
function resolveEffectiveMcpServers() {
|
|
57
|
+
// Benchmark/embedding guard: do not even inspect project `.mcp.json`.
|
|
58
|
+
if (mcpDisabled()) return { servers: {}, sources: {} };
|
|
51
59
|
const config = getConfig();
|
|
52
60
|
const configured = config?.mcpServers && typeof config.mcpServers === 'object'
|
|
53
61
|
? config.mcpServers
|
|
54
62
|
: {};
|
|
63
|
+
const projectKey = normalizeMcpProjectPathKey(getCurrentCwd());
|
|
64
|
+
const overrides = config?.mcpProjectOverrides?.[projectKey];
|
|
65
|
+
const foldedConfigured = {};
|
|
66
|
+
for (const [name, cfg] of Object.entries(configured)) {
|
|
67
|
+
const override = overrides?.[name];
|
|
68
|
+
foldedConfigured[name] = typeof override?.enabled === 'boolean'
|
|
69
|
+
? { ...cfg, enabled: override.enabled }
|
|
70
|
+
: cfg;
|
|
71
|
+
}
|
|
55
72
|
const project = cachedProjectMcpServers(getCurrentCwd());
|
|
56
|
-
const servers = { ...
|
|
73
|
+
const servers = { ...foldedConfigured, ...project };
|
|
57
74
|
const sources = {};
|
|
58
75
|
for (const name of Object.keys(configured)) sources[name] = 'config';
|
|
59
76
|
for (const name of Object.keys(project)) sources[name] = 'project';
|
|
@@ -61,6 +78,9 @@ export function createMcpGlue({
|
|
|
61
78
|
}
|
|
62
79
|
|
|
63
80
|
function mcpStatus() {
|
|
81
|
+
if (mcpDisabled()) {
|
|
82
|
+
return { servers: [], configuredCount: 0, connectedCount: 0, failedCount: 0 };
|
|
83
|
+
}
|
|
64
84
|
const { servers: configured, sources } = resolveEffectiveMcpServers();
|
|
65
85
|
const connected = new Map((mcpClient.getMcpServerStatus?.() || []).map((row) => [row.name, row]));
|
|
66
86
|
const failures = new Map((state.mcpFailures || []).map((row) => [row.name, row]));
|
|
@@ -129,6 +149,13 @@ export function createMcpGlue({
|
|
|
129
149
|
}
|
|
130
150
|
|
|
131
151
|
async function connectConfiguredMcp({ reset = false, only = null, enabled = true } = {}) {
|
|
152
|
+
if (mcpDisabled()) {
|
|
153
|
+
++state.mcpConnectGeneration;
|
|
154
|
+
state.mcpFailures = [];
|
|
155
|
+
if (only) await mcpClient.disconnectMcpServer?.(only);
|
|
156
|
+
else await mcpClient.disconnectAll?.();
|
|
157
|
+
return mcpStatus();
|
|
158
|
+
}
|
|
132
159
|
// Scoped single-server toggle: non-superseding. It must NEVER cancel a
|
|
133
160
|
// pending full {reset} (cwd-change/boot). So do not bump the generation;
|
|
134
161
|
// just wait for any in-flight run, then bail if a newer full reset has
|
|
@@ -4,6 +4,13 @@ import { isAbsolute, join, relative, resolve } from 'node:path';
|
|
|
4
4
|
import { clean } from './session-text.mjs';
|
|
5
5
|
import { readJsonSafe } from './fs-utils.mjs';
|
|
6
6
|
|
|
7
|
+
// Config keys must compare the same cwd spelling on read and write. Windows
|
|
8
|
+
// paths are case-insensitive, so canonicalize their resolved form to lowercase.
|
|
9
|
+
export function normalizeMcpProjectPathKey(cwd) {
|
|
10
|
+
const path = resolve(cwd || '.');
|
|
11
|
+
return process.platform === 'win32' ? path.toLowerCase() : path;
|
|
12
|
+
}
|
|
13
|
+
|
|
7
14
|
// Project-local MCP ingress: read `.mcp.json` from the project root and return
|
|
8
15
|
// a cleaned { name: cfg } map. Best-effort — never throws. Accepts either the
|
|
9
16
|
// standard `{ mcpServers: {...} }` shape or a bare name->cfg map. Self-ref
|
|
@@ -18,7 +18,9 @@ import {
|
|
|
18
18
|
pluginMcpEnableScript,
|
|
19
19
|
resolveContainedPluginPath,
|
|
20
20
|
setProjectMcpServerEnabled,
|
|
21
|
+
normalizeMcpProjectPathKey,
|
|
21
22
|
} from './plugin-mcp.mjs';
|
|
23
|
+
import { invalidateProjectMcpCache } from './mcp-glue.mjs';
|
|
22
24
|
|
|
23
25
|
// MCP servers, skills, plugins, hooks, and memory/recall surfaces. Extracted
|
|
24
26
|
// verbatim from the runtime API object; stateless helpers are imported directly
|
|
@@ -137,7 +139,19 @@ export function createResourceApi(deps) {
|
|
|
137
139
|
throw new Error(`MCP server not configured: ${serverName}`);
|
|
138
140
|
}
|
|
139
141
|
delete current[serverName];
|
|
140
|
-
|
|
142
|
+
const currentOverrides = nextConfig.mcpProjectOverrides && typeof nextConfig.mcpProjectOverrides === 'object'
|
|
143
|
+
? nextConfig.mcpProjectOverrides
|
|
144
|
+
: {};
|
|
145
|
+
const mcpProjectOverrides = {};
|
|
146
|
+
for (const [projectKey, serverOverrides] of Object.entries(currentOverrides)) {
|
|
147
|
+
if (!serverOverrides || typeof serverOverrides !== 'object' || Array.isArray(serverOverrides)) continue;
|
|
148
|
+
const nextServerOverrides = { ...serverOverrides };
|
|
149
|
+
delete nextServerOverrides[serverName];
|
|
150
|
+
if (Object.keys(nextServerOverrides).length > 0) {
|
|
151
|
+
mcpProjectOverrides[projectKey] = nextServerOverrides;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
saveConfigAndAdopt({ ...nextConfig, mcpServers: current, mcpProjectOverrides });
|
|
141
155
|
const status = await connectConfiguredMcp({ reset: true });
|
|
142
156
|
invalidatePreSessionToolSurface();
|
|
143
157
|
const session = getSession();
|
|
@@ -152,11 +166,13 @@ export function createResourceApi(deps) {
|
|
|
152
166
|
// A project-local `.mcp.json` entry WINS over config for this name, so the
|
|
153
167
|
// durable toggle must land in whichever file actually drives the server.
|
|
154
168
|
// For project-sourced servers, persist the `enabled` flag into `.mcp.json`
|
|
155
|
-
//
|
|
169
|
+
// then explicitly invalidate the project cache (mtime granularity is not
|
|
170
|
+
// reliable for same-tick writes), before running the same
|
|
156
171
|
// background connect/recreate chain used for config servers.
|
|
157
172
|
const shadowRow = mcpStatus().servers.find((s) => s.name === serverName);
|
|
158
173
|
if (shadowRow && shadowRow.source === 'project') {
|
|
159
174
|
setProjectMcpServerEnabled(getCurrentCwd(), serverName, want);
|
|
175
|
+
invalidateProjectMcpCache(getCurrentCwd());
|
|
160
176
|
return scheduleMcpToggle(serverName, want);
|
|
161
177
|
}
|
|
162
178
|
const nextConfig = { ...getConfig() };
|
|
@@ -166,12 +182,29 @@ export function createResourceApi(deps) {
|
|
|
166
182
|
if (!Object.prototype.hasOwnProperty.call(current, serverName)) {
|
|
167
183
|
throw new Error(`MCP server not configured: ${serverName}`);
|
|
168
184
|
}
|
|
169
|
-
//
|
|
185
|
+
// Keep the global server definition single-source; only this project's
|
|
186
|
+
// enabled override is adopted + persisted synchronously (fast), then
|
|
170
187
|
// hand the heavy connect/close/recreate to the per-server background
|
|
171
188
|
// chain. Return that chain's promise so callers can settle the picker on
|
|
172
189
|
// completion, but the store no longer blocks on it.
|
|
173
|
-
|
|
174
|
-
|
|
190
|
+
const projectKey = normalizeMcpProjectPathKey(getCurrentCwd());
|
|
191
|
+
const currentOverrides = nextConfig.mcpProjectOverrides && typeof nextConfig.mcpProjectOverrides === 'object'
|
|
192
|
+
? nextConfig.mcpProjectOverrides
|
|
193
|
+
: {};
|
|
194
|
+
const projectOverrides = currentOverrides[projectKey] && typeof currentOverrides[projectKey] === 'object'
|
|
195
|
+
? currentOverrides[projectKey]
|
|
196
|
+
: {};
|
|
197
|
+
cfgMod.markMcpProjectOverrideDirty(projectKey, serverName, want);
|
|
198
|
+
saveConfigAndAdopt({
|
|
199
|
+
...nextConfig,
|
|
200
|
+
mcpProjectOverrides: {
|
|
201
|
+
...currentOverrides,
|
|
202
|
+
[projectKey]: {
|
|
203
|
+
...projectOverrides,
|
|
204
|
+
[serverName]: { enabled: want },
|
|
205
|
+
},
|
|
206
|
+
},
|
|
207
|
+
});
|
|
175
208
|
return scheduleMcpToggle(serverName, want);
|
|
176
209
|
},
|
|
177
210
|
skillsStatus() {
|
|
@@ -1051,7 +1051,7 @@ export async function createMixdogSessionRuntime({
|
|
|
1051
1051
|
const toolsStartedAt = performance.now();
|
|
1052
1052
|
const standaloneTools = [
|
|
1053
1053
|
TOOL_SEARCH_TOOL,
|
|
1054
|
-
SKILL_TOOL,
|
|
1054
|
+
...(envFlag('MIXDOG_DISABLE_SKILLS') ? [] : [SKILL_TOOL]),
|
|
1055
1055
|
CWD_TOOL,
|
|
1056
1056
|
SESSION_MANAGE_TOOL,
|
|
1057
1057
|
EXPLORE_TOOL,
|
|
@@ -1141,6 +1141,10 @@ export async function createMixdogSessionRuntime({
|
|
|
1141
1141
|
routingSessionId: callerSessionId,
|
|
1142
1142
|
clientHostPid: callerCtx?.clientHostPid || session?.clientHostPid || process.pid,
|
|
1143
1143
|
notifyFn: notifyFnForSession(callerSessionId),
|
|
1144
|
+
// Cascade caller cancellation (ESC / owner abort) into the explore
|
|
1145
|
+
// fan-out so every child dispatch tears down immediately — same
|
|
1146
|
+
// signal the adjacent native search already forwards.
|
|
1147
|
+
signal: callerCtx?.signal || session?.controller?.signal,
|
|
1144
1148
|
});
|
|
1145
1149
|
}
|
|
1146
1150
|
if (name === 'cwd') {
|
|
@@ -11,6 +11,18 @@ import {
|
|
|
11
11
|
} from './tool-catalog.mjs';
|
|
12
12
|
import { getMcpTools } from '../runtime/agent/orchestrator/mcp/client.mjs';
|
|
13
13
|
|
|
14
|
+
export function splitToolStatusCounts(rows) {
|
|
15
|
+
const list = Array.isArray(rows) ? rows : [];
|
|
16
|
+
const regular = list.filter((row) => row?.kind !== 'mcp' && row?.kind !== 'skill');
|
|
17
|
+
const mcp = list.filter((row) => row?.kind === 'mcp');
|
|
18
|
+
return {
|
|
19
|
+
count: regular.length,
|
|
20
|
+
activeCount: regular.filter((row) => row.active).length,
|
|
21
|
+
mcpToolCount: mcp.length,
|
|
22
|
+
activeMcpToolCount: mcp.filter((row) => row.active).length,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
14
26
|
// Turn execution (ask) + session-manage/tool-surface/agent surfaces. Extracted
|
|
15
27
|
// verbatim from the runtime API object; stateless helpers are imported directly
|
|
16
28
|
// and the runtime injects live getters/setters for the mutable session/mode/
|
|
@@ -311,13 +323,13 @@ export function createSessionTurnApi(deps) {
|
|
|
311
323
|
const activeNames = new Set((surface?.tools || []).map((tool) => tool?.name).filter(Boolean));
|
|
312
324
|
const needle = clean(query).toLowerCase();
|
|
313
325
|
const rows = catalog.map((tool) => toolRow(tool, activeNames)).filter((row) => row.name);
|
|
326
|
+
const counts = splitToolStatusCounts(rows);
|
|
314
327
|
const tools = needle
|
|
315
328
|
? rows.filter((row) => toolSearchMatches(row, needle))
|
|
316
329
|
: rows;
|
|
317
330
|
return {
|
|
318
331
|
mode: getMode(),
|
|
319
|
-
|
|
320
|
-
activeCount: rows.filter((row) => row.active).length,
|
|
332
|
+
...counts,
|
|
321
333
|
tools,
|
|
322
334
|
activeTools: sortedNamesByMeasuredUsage(activeNames),
|
|
323
335
|
discoveredTools: sortedNamesByMeasuredUsage(surface?.deferredDiscoveredTools || []),
|
|
@@ -189,6 +189,11 @@ export function createSettingsApi({
|
|
|
189
189
|
next.type = compactType;
|
|
190
190
|
next.compactType = compactType;
|
|
191
191
|
}
|
|
192
|
+
// These controls apply only to main/user recall-fasttrack sessions;
|
|
193
|
+
// agent-owned semantic sessions retain their existing `buffer*` policy.
|
|
194
|
+
for (const key of ['mainBufferTokens', 'mainBuffer', 'mainBufferPercent', 'mainBufferPct', 'mainBufferRatio', 'mainBufferFraction']) {
|
|
195
|
+
if (hasOwn(input, key)) next[key] = input[key];
|
|
196
|
+
}
|
|
192
197
|
const nextConfig = { ...config };
|
|
193
198
|
nextConfig.compaction = normalizeCompactionConfig(next, { memoryEnabled: memoryEnabled() });
|
|
194
199
|
saveConfigAndAdopt(nextConfig);
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// tool_search ranking + auto-selection, and the session tool-surface
|
|
3
3
|
// application/selection logic. Pure module (session objects passed in).
|
|
4
4
|
import { clean, LATE_TOOL_ANNOUNCEMENT_SENTINEL } from './session-text.mjs';
|
|
5
|
-
import { estimateToolSchemaTokens } from '../runtime/agent/orchestrator/session/context-utils.mjs';
|
|
5
|
+
import { estimateToolSchemaTokens, toolSchemaSignature } from '../runtime/agent/orchestrator/session/context-utils.mjs';
|
|
6
6
|
import { applyInitialDeferredToolManifestToBp1, buildDeferredToolManifest } from '../runtime/agent/orchestrator/context/collect.mjs';
|
|
7
7
|
import { getMcpServerInstructionsMap } from '../runtime/agent/orchestrator/mcp/client.mjs';
|
|
8
8
|
import {
|
|
@@ -40,7 +40,12 @@ function sameToolSchemaEntries(cached, tools) {
|
|
|
40
40
|
|| entry.inputSchema !== tool?.inputSchema
|
|
41
41
|
|| entry.input_schema !== tool?.input_schema
|
|
42
42
|
|| entry.parameters !== tool?.parameters
|
|
43
|
-
|| entry.schema !== tool?.schema
|
|
43
|
+
|| entry.schema !== tool?.schema
|
|
44
|
+
|| entry.deferLoading !== tool?.deferLoading
|
|
45
|
+
|| entry.defer_loading !== tool?.defer_loading
|
|
46
|
+
|| entry.annotationsMixdogKind !== tool?.annotations?.mixdogKind
|
|
47
|
+
|| entry.annotationsAgentHidden !== tool?.annotations?.agentHidden
|
|
48
|
+
|| entry.wireSignature !== toolSchemaSignature([tool])) return false;
|
|
44
49
|
}
|
|
45
50
|
return true;
|
|
46
51
|
}
|
|
@@ -54,6 +59,11 @@ function toolSchemaEntry(tool) {
|
|
|
54
59
|
input_schema: tool?.input_schema,
|
|
55
60
|
parameters: tool?.parameters,
|
|
56
61
|
schema: tool?.schema,
|
|
62
|
+
deferLoading: tool?.deferLoading,
|
|
63
|
+
defer_loading: tool?.defer_loading,
|
|
64
|
+
annotationsMixdogKind: tool?.annotations?.mixdogKind,
|
|
65
|
+
annotationsAgentHidden: tool?.annotations?.agentHidden,
|
|
66
|
+
wireSignature: toolSchemaSignature([tool]),
|
|
57
67
|
};
|
|
58
68
|
}
|
|
59
69
|
export const DEFERRED_DEFAULT_FULL_TOOLS = Object.freeze([
|
|
@@ -143,6 +153,7 @@ export function toolSchemaBucket(tool) {
|
|
|
143
153
|
const kind = toolKind(tool);
|
|
144
154
|
if (kind === 'mcp') return 'mcp';
|
|
145
155
|
if (kind === 'skill') return 'skills';
|
|
156
|
+
if (kind === 'control') return 'control';
|
|
146
157
|
if (name === 'memory' || name === 'recall' || name.includes('memory')) return 'memory';
|
|
147
158
|
if (name === 'search' || name === 'web_fetch') return 'web';
|
|
148
159
|
if (['read', 'grep', 'find', 'glob', 'list', 'code_graph', 'explore'].includes(name)) return 'code';
|
|
@@ -13,13 +13,11 @@ export const TOOL_SEARCH_TOOL = {
|
|
|
13
13
|
openWorldHint: false,
|
|
14
14
|
agentHidden: true,
|
|
15
15
|
},
|
|
16
|
-
description: '
|
|
16
|
+
description: 'Load deferred tools by exact name via names[]; reports loaded/already-active/missing plus MCP servers still connecting or failed. Deferred tools also auto-load when called directly.',
|
|
17
17
|
inputSchema: {
|
|
18
18
|
type: 'object',
|
|
19
19
|
properties: {
|
|
20
20
|
names: { anyOf: [{ type: 'string' }, { type: 'array', items: { type: 'string' } }], description: 'Exact deferred tool names/aliases to load.' },
|
|
21
|
-
select: { anyOf: [{ type: 'string' }, { type: 'array', items: { type: 'string' } }], description: 'Legacy alias for names (accepts "select:a,b").' },
|
|
22
|
-
query: { type: 'string', description: 'Legacy: only "select:a,b" is honored (mapped to names); free-text keyword queries are rejected — this tool does not search.' },
|
|
23
21
|
},
|
|
24
22
|
additionalProperties: false,
|
|
25
23
|
},
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
agentWatchdogPolicyActive,
|
|
3
3
|
evaluateAgentWatchdogAbort,
|
|
4
|
+
resolveEffectiveToolRunningCeilingMs,
|
|
4
5
|
} from '../runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs';
|
|
5
6
|
|
|
6
7
|
const ACTIVE_RUNTIME_STAGES = new Set([
|
|
@@ -18,10 +19,26 @@ function positiveSeconds(now, ts) {
|
|
|
18
19
|
return Math.max(0, Math.floor((now - n) / 1000));
|
|
19
20
|
}
|
|
20
21
|
|
|
21
|
-
export function formatAgentWatchdogSummary(policy) {
|
|
22
|
+
export function formatAgentWatchdogSummary(policy, snapshot = null) {
|
|
22
23
|
if (!policy || !agentWatchdogPolicyActive(policy)) return null;
|
|
24
|
+
const transportMs = policy.firstTransportMs ?? policy.firstResponseMs ?? 0;
|
|
25
|
+
const semanticMs = policy.firstSemanticMs ?? policy.firstVisibleCeilingMs ?? 0;
|
|
26
|
+
if (snapshot) {
|
|
27
|
+
if (snapshot.waitingForTransport && transportMs > 0) {
|
|
28
|
+
return `armed transport=${Math.round(transportMs / 1000)}s`;
|
|
29
|
+
}
|
|
30
|
+
if ((snapshot.waitingForFirstSemantic ?? snapshot.waitingForFirstActivity) && semanticMs > 0) {
|
|
31
|
+
return `armed semantic=${Math.round(semanticMs / 1000)}s`;
|
|
32
|
+
}
|
|
33
|
+
if (snapshot.stage === 'tool_running' && policy.toolRunningMs > 0) {
|
|
34
|
+
const effectiveMs = resolveEffectiveToolRunningCeilingMs(snapshot, policy);
|
|
35
|
+
return `armed tool=${Math.round(effectiveMs / 1000)}s`;
|
|
36
|
+
}
|
|
37
|
+
if (policy.idleStaleMs > 0) return `armed idle=${Math.round(policy.idleStaleMs / 1000)}s`;
|
|
38
|
+
}
|
|
23
39
|
const parts = [];
|
|
24
|
-
if (
|
|
40
|
+
if (transportMs > 0) parts.push(`transport=${Math.round(transportMs / 1000)}s`);
|
|
41
|
+
if (semanticMs > 0) parts.push(`semantic=${Math.round(semanticMs / 1000)}s`);
|
|
25
42
|
if (policy.idleStaleMs > 0) parts.push(`idle=${Math.round(policy.idleStaleMs / 1000)}s`);
|
|
26
43
|
if (policy.toolRunningMs > 0) parts.push(`tool=${Math.round(policy.toolRunningMs / 1000)}s`);
|
|
27
44
|
return parts.length ? `armed ${parts.join(' ')}` : null;
|
|
@@ -55,7 +72,7 @@ export function buildAgentTaskProgressFields({
|
|
|
55
72
|
const stage = cleanStage(runtimeStage || snapshot?.stage || sessionStatus || 'unknown');
|
|
56
73
|
const workerStage = stage;
|
|
57
74
|
const silentFor = resolveSilentForSeconds(now, snapshot, runtime);
|
|
58
|
-
const watchdog = formatAgentWatchdogSummary(policy);
|
|
75
|
+
const watchdog = formatAgentWatchdogSummary(policy, snapshot);
|
|
59
76
|
const queued = Number.isFinite(Number(queuedFollowups)) && Number(queuedFollowups) > 0
|
|
60
77
|
? Math.floor(Number(queuedFollowups))
|
|
61
78
|
: null;
|
|
@@ -96,13 +113,24 @@ function cleanStage(value) {
|
|
|
96
113
|
}
|
|
97
114
|
|
|
98
115
|
function describeLastProgress({ stage, snapshot, runtime, silentFor, lastToolCall, now }) {
|
|
99
|
-
if (snapshot?.
|
|
116
|
+
if (snapshot?.waitingForTransport) return 'awaiting model transport';
|
|
117
|
+
if (snapshot?.waitingForFirstSemantic ?? snapshot?.waitingForFirstActivity) {
|
|
118
|
+
return 'transport active; awaiting first model event';
|
|
119
|
+
}
|
|
100
120
|
if (stage === 'tool_running') {
|
|
101
121
|
const tool = cleanStage(lastToolCall);
|
|
102
122
|
return tool && tool !== 'unknown' ? `tool: ${tool}` : 'tool running';
|
|
103
123
|
}
|
|
104
124
|
if (stage === 'connecting' || stage === 'requesting') return 'connecting to model';
|
|
105
125
|
if (stage === 'streaming') {
|
|
126
|
+
if (snapshot?.lastSemanticKind === 'tool') return 'tool protocol progress';
|
|
127
|
+
if (snapshot?.lastSemanticKind === 'text') return 'visible model text';
|
|
128
|
+
if (snapshot?.lastSemanticKind === 'reasoning') {
|
|
129
|
+
return snapshot?.lastVisibleTextAt
|
|
130
|
+
? 'model reasoning (hidden; visible output previously emitted)'
|
|
131
|
+
: 'model reasoning (hidden; no visible output yet)';
|
|
132
|
+
}
|
|
133
|
+
if (snapshot?.hasFirstSemantic) return 'model active (no visible output yet)';
|
|
106
134
|
const streamSilent = positiveSeconds(
|
|
107
135
|
now,
|
|
108
136
|
snapshot?.lastStreamDeltaAt || runtime?.lastStreamDeltaAt || 0,
|
|
@@ -149,9 +177,25 @@ function describeAgentDiagnostic({
|
|
|
149
177
|
return `idle, ${queued} follow-up${queued === 1 ? '' : 's'} queued`;
|
|
150
178
|
}
|
|
151
179
|
|
|
152
|
-
if (
|
|
180
|
+
if (policy) {
|
|
181
|
+
const abortErr = evaluateAgentWatchdogAbort(snapshot, now, policy);
|
|
182
|
+
if (abortErr) return `stale: ${abortErr.message.replace(/^agent /i, '')}`;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (snapshot?.waitingForTransport) return 'waiting for model transport';
|
|
186
|
+
if (snapshot?.waitingForFirstSemantic ?? snapshot?.waitingForFirstActivity) {
|
|
187
|
+
return 'transport healthy; waiting for first semantic model event';
|
|
188
|
+
}
|
|
153
189
|
|
|
154
190
|
if (stage === 'streaming') {
|
|
191
|
+
if (snapshot?.lastSemanticKind === 'tool') return 'tool protocol active';
|
|
192
|
+
if (snapshot?.lastSemanticKind === 'text') return 'visible text streaming';
|
|
193
|
+
if (snapshot?.lastSemanticKind === 'reasoning') {
|
|
194
|
+
return snapshot?.lastVisibleTextAt
|
|
195
|
+
? 'hidden reasoning active; visible output previously emitted'
|
|
196
|
+
: 'hidden reasoning active; no visible output yet';
|
|
197
|
+
}
|
|
198
|
+
if (snapshot?.hasFirstSemantic) return 'model active; no visible output yet';
|
|
155
199
|
const streamSilent = positiveSeconds(
|
|
156
200
|
now,
|
|
157
201
|
snapshot?.lastStreamDeltaAt || 0,
|
|
@@ -171,12 +215,7 @@ function describeAgentDiagnostic({
|
|
|
171
215
|
now,
|
|
172
216
|
policy,
|
|
173
217
|
);
|
|
174
|
-
if (abortErr) {
|
|
175
|
-
return `stale: ${abortErr.message.replace(/^agent /i, '')}`;
|
|
176
|
-
}
|
|
177
|
-
if (silentFor >= 30) {
|
|
178
|
-
return `stale: no stream/tool progress for ${silentFor}s`;
|
|
179
|
-
}
|
|
218
|
+
if (abortErr) return `stale: ${abortErr.message.replace(/^agent /i, '')}`;
|
|
180
219
|
}
|
|
181
220
|
|
|
182
221
|
if (normalizedTask === 'running' && (stage === 'idle' || stage === 'done')) {
|
|
@@ -35,7 +35,7 @@ export const AGENT_TOOL = {
|
|
|
35
35
|
openWorldHint: true,
|
|
36
36
|
agentHidden: true,
|
|
37
37
|
},
|
|
38
|
-
description: 'Delegate scoped work
|
|
38
|
+
description: 'Delegate scoped work; handoffs always start background tasks (task ids return immediately). Distinct tags for independent scopes; spawn/send with the same tag reuses the live session for the same scope. Wait for the completion notification; do not call status/read after spawn (manual recovery only).',
|
|
39
39
|
inputSchema: {
|
|
40
40
|
type: 'object',
|
|
41
41
|
properties: {
|