mixdog 0.9.0 → 0.9.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 +10 -3
- package/scripts/_bench-cwc.json +20 -0
- package/scripts/agent-loop-policy-test.mjs +37 -0
- package/scripts/agent-parallel-smoke.mjs +54 -10
- package/scripts/background-task-meta-smoke.mjs +1 -1
- package/scripts/bench-run.mjs +262 -0
- package/scripts/compact-smoke.mjs +12 -0
- package/scripts/compact-trigger-migration-smoke.mjs +67 -1
- package/scripts/ingest-pure-conversation-smoke.mjs +148 -0
- package/scripts/internal-comms-bench.mjs +727 -0
- package/scripts/internal-comms-smoke.mjs +75 -0
- package/scripts/lead-workflow-smoke.mjs +4 -4
- package/scripts/live-worker-smoke.mjs +9 -9
- package/scripts/output-style-bench.mjs +285 -0
- package/scripts/output-style-smoke.mjs +13 -10
- package/scripts/patch-replay.mjs +90 -0
- package/scripts/provider-stream-stall-test.mjs +276 -0
- package/scripts/provider-toolcall-test.mjs +599 -1
- package/scripts/routing-corpus.mjs +281 -0
- package/scripts/session-bench.mjs +1526 -0
- package/scripts/session-diag.mjs +595 -0
- package/scripts/session-ingest-smoke.mjs +2 -2
- package/scripts/task-bench.mjs +207 -0
- package/scripts/tool-failures.mjs +6 -6
- package/scripts/tool-smoke.mjs +306 -66
- package/scripts/toolcall-args-test.mjs +81 -0
- package/src/agents/debugger/AGENT.md +4 -4
- package/src/agents/heavy-worker/AGENT.md +4 -2
- package/src/agents/reviewer/AGENT.md +4 -4
- package/src/agents/worker/AGENT.md +4 -2
- package/src/app.mjs +10 -6
- package/src/defaults/{hidden-roles.json → agents.json} +7 -7
- package/src/examples/schedules/SCHEDULE.example.md +32 -0
- package/src/examples/webhooks/WEBHOOK.example.md +40 -0
- package/src/headless-role.mjs +14 -14
- package/src/help.mjs +1 -0
- package/src/lib/mixdog-debug.cjs +0 -22
- package/src/lib/plugin-paths.cjs +1 -7
- package/src/lib/rules-builder.cjs +34 -56
- package/src/mixdog-session-runtime.mjs +710 -319
- package/src/output-styles/default.md +12 -7
- package/src/output-styles/minimal.md +25 -0
- package/src/output-styles/oneline.md +21 -0
- package/src/output-styles/simple.md +10 -9
- package/src/repl.mjs +12 -4
- package/src/rules/agent/00-common.md +7 -5
- package/src/rules/agent/30-explorer.md +7 -8
- package/src/rules/lead/01-general.md +3 -1
- package/src/rules/lead/lead-tool.md +7 -0
- package/src/rules/shared/01-tool.md +17 -12
- package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch.mjs +90 -32
- package/src/runtime/agent/orchestrator/agent-runtime/agent-loop-policy.mjs +32 -0
- package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +18 -6
- package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +23 -20
- package/src/runtime/agent/orchestrator/agent-runtime/session-builder.mjs +48 -14
- package/src/runtime/agent/orchestrator/agent-trace.mjs +87 -12
- package/src/runtime/agent/orchestrator/config.mjs +3 -0
- package/src/runtime/agent/orchestrator/context/collect.mjs +131 -67
- package/src/runtime/agent/orchestrator/{internal-roles.mjs → internal-agents.mjs} +72 -72
- package/src/runtime/agent/orchestrator/internal-tools.mjs +13 -26
- package/src/runtime/agent/orchestrator/mcp/client.mjs +94 -16
- package/src/runtime/agent/orchestrator/providers/anthropic-betas.mjs +7 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-effort.mjs +188 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-leaked-toolcall.mjs +444 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +359 -106
- package/src/runtime/agent/orchestrator/providers/anthropic.mjs +63 -51
- package/src/runtime/agent/orchestrator/providers/api-usage.mjs +27 -20
- package/src/runtime/agent/orchestrator/providers/gemini.mjs +184 -17
- package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +8 -1
- package/src/runtime/agent/orchestrator/providers/model-catalog.mjs +18 -8
- package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +210 -21
- package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +86 -30
- package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +254 -280
- package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +191 -50
- package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +18 -0
- package/src/runtime/agent/orchestrator/providers/opencode-go-usage.mjs +11 -5
- package/src/runtime/agent/orchestrator/providers/registry.mjs +2 -1
- package/src/runtime/agent/orchestrator/providers/retry-classifier.mjs +265 -1
- package/src/runtime/agent/orchestrator/session/compact.mjs +560 -51
- package/src/runtime/agent/orchestrator/session/context-utils.mjs +250 -3
- package/src/runtime/agent/orchestrator/session/loop.mjs +394 -132
- package/src/runtime/agent/orchestrator/session/manager.mjs +217 -170
- package/src/runtime/agent/orchestrator/session/store.mjs +4 -4
- package/src/runtime/agent/orchestrator/session/tool-envelope.mjs +61 -0
- package/src/runtime/agent/orchestrator/session/tool-result-offload.mjs +5 -0
- package/src/runtime/agent/orchestrator/stall-policy.mjs +63 -15
- package/src/runtime/agent/orchestrator/tools/bash-session.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +194 -32
- package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.test.mjs +143 -0
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +1 -44
- package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +34 -18
- package/src/runtime/agent/orchestrator/tools/builtin/external-tool-adapters.mjs +0 -0
- package/src/runtime/agent/orchestrator/tools/builtin/list-formatting.mjs +10 -0
- package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +5 -4
- package/src/runtime/agent/orchestrator/tools/builtin/path-utils.mjs +15 -0
- package/src/runtime/agent/orchestrator/tools/builtin/read-args.mjs +9 -44
- package/src/runtime/agent/orchestrator/tools/builtin/read-constants.mjs +2 -1
- package/src/runtime/agent/orchestrator/tools/builtin/read-formatting.mjs +13 -4
- package/src/runtime/agent/orchestrator/tools/builtin/read-tool.mjs +10 -17
- package/src/runtime/agent/orchestrator/tools/builtin/search-tool.mjs +18 -2
- package/src/runtime/agent/orchestrator/tools/builtin/shell-output.mjs +3 -2
- package/src/runtime/agent/orchestrator/tools/builtin/tool-output-limit.mjs +10 -0
- package/src/runtime/agent/orchestrator/tools/builtin.mjs +59 -1
- package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +5 -5
- package/src/runtime/agent/orchestrator/tools/code-graph.mjs +4076 -3985
- package/src/runtime/agent/orchestrator/tools/patch.mjs +116 -2
- package/src/runtime/channels/backends/discord.mjs +99 -9
- package/src/runtime/channels/backends/telegram.mjs +501 -0
- package/src/runtime/channels/index.mjs +441 -1254
- package/src/runtime/channels/lib/cli-worker-host.mjs +1 -8
- package/src/runtime/channels/lib/config.mjs +54 -3
- package/src/runtime/channels/lib/drop-trace.mjs +1 -1
- package/src/runtime/channels/lib/executor.mjs +0 -3
- package/src/runtime/channels/lib/format.mjs +4 -2
- package/src/runtime/channels/lib/memory-client.mjs +0 -38
- package/src/runtime/channels/lib/output-forwarder.mjs +77 -71
- package/src/runtime/channels/lib/runtime-paths.mjs +29 -6
- package/src/runtime/channels/lib/scheduler.mjs +1 -1
- package/src/runtime/channels/lib/session-discovery.mjs +0 -4
- package/src/runtime/channels/lib/telegram-format.mjs +283 -0
- package/src/runtime/channels/lib/tool-format.mjs +1 -2
- package/src/runtime/channels/lib/transcript-discovery.mjs +20 -11
- package/src/runtime/channels/lib/webhook.mjs +59 -31
- package/src/runtime/channels/tool-defs.mjs +1 -1
- package/src/runtime/lib/keychain-cjs.cjs +0 -1
- package/src/runtime/memory/data/runtime-manifest.json +6 -7
- package/src/runtime/memory/index.mjs +187 -43
- package/src/runtime/memory/lib/agent-ipc.mjs +2 -2
- package/src/runtime/memory/lib/core-memory-store.mjs +1 -1
- package/src/runtime/memory/lib/llm-worker-host.mjs +0 -4
- package/src/runtime/memory/lib/memory-cycle1.mjs +1 -1
- package/src/runtime/memory/lib/memory-cycle2.mjs +9 -6
- package/src/runtime/memory/lib/memory-cycle3.mjs +1 -1
- package/src/runtime/memory/lib/memory-ops-policy.mjs +0 -1
- package/src/runtime/memory/lib/memory.mjs +101 -4
- package/src/runtime/memory/lib/pg/adapter.mjs +139 -15
- package/src/runtime/memory/lib/runtime-fetcher.mjs +43 -18
- package/src/runtime/memory/lib/session-ingest.mjs +116 -7
- package/src/runtime/memory/lib/trace-store.mjs +69 -22
- package/src/runtime/memory/tool-defs.mjs +6 -3
- package/src/runtime/search/index.mjs +2 -7
- package/src/runtime/search/lib/config.mjs +0 -4
- package/src/runtime/search/lib/state.mjs +1 -15
- package/src/runtime/search/lib/web-tools.mjs +0 -1
- package/src/runtime/shared/channel-notification-routing.mjs +12 -0
- package/src/runtime/shared/channel-notification-routing.test.mjs +45 -0
- package/src/runtime/shared/child-spawn-gate.mjs +0 -6
- package/src/runtime/shared/config.mjs +9 -0
- package/src/runtime/shared/llm/http-agent.mjs +12 -5
- package/src/runtime/shared/schedules-store.mjs +21 -19
- package/src/runtime/shared/tool-surface.mjs +98 -13
- package/src/runtime/shared/transcript-writer.mjs +129 -0
- package/src/runtime/shared/update-checker.mjs +214 -0
- package/src/standalone/agent-tool.mjs +255 -109
- package/src/standalone/channel-admin.mjs +133 -40
- package/src/standalone/channel-worker.mjs +8 -291
- package/src/standalone/explore-tool.mjs +2 -2
- package/src/standalone/memory-runtime-proxy.mjs +3 -1
- package/src/standalone/provider-admin.mjs +11 -0
- package/src/standalone/seeds.mjs +1 -11
- package/src/standalone/usage-dashboard.mjs +1 -1
- package/src/tui/App.jsx +2137 -750
- package/src/tui/components/ConfirmBar.jsx +47 -0
- package/src/tui/components/ContextPanel.jsx +5 -3
- package/src/tui/components/ItemRightHintOverprint.jsx +54 -0
- package/src/tui/components/Markdown.jsx +22 -98
- package/src/tui/components/Message.jsx +14 -35
- package/src/tui/components/Picker.jsx +87 -12
- package/src/tui/components/PromptInput.jsx +146 -9
- package/src/tui/components/QueuedCommands.jsx +1 -1
- package/src/tui/components/SlashCommandPalette.jsx +8 -5
- package/src/tui/components/Spinner.jsx +7 -7
- package/src/tui/components/StatusLine.jsx +40 -21
- package/src/tui/components/TextEntryPanel.jsx +51 -7
- package/src/tui/components/ToolExecution.jsx +177 -100
- package/src/tui/components/TurnDone.jsx +4 -4
- package/src/tui/components/UsagePanel.jsx +1 -1
- package/src/tui/components/tool-output-format.mjs +312 -40
- package/src/tui/components/tool-output-format.test.mjs +180 -1
- package/src/tui/display-width.mjs +69 -0
- package/src/tui/display-width.test.mjs +35 -0
- package/src/tui/dist/index.mjs +7324 -2393
- package/src/tui/engine.mjs +287 -126
- package/src/tui/index.jsx +117 -7
- package/src/tui/keyboard-protocol.mjs +42 -0
- package/src/tui/lib/voice-recorder.mjs +453 -0
- package/src/tui/markdown/format-token.mjs +354 -142
- package/src/tui/markdown/format-token.test.mjs +155 -17
- package/src/tui/markdown/measure-rendered-rows.mjs +85 -0
- package/src/tui/markdown/render-ansi.test.mjs +1 -1
- package/src/tui/markdown/streaming-markdown.mjs +167 -0
- package/src/tui/markdown/streaming-markdown.test.mjs +70 -0
- package/src/tui/markdown/table-layout.mjs +9 -9
- package/src/tui/paste-attachments.mjs +0 -11
- package/src/tui/prompt-history-store.mjs +129 -0
- package/src/tui/prompt-history-store.test.mjs +52 -0
- package/src/tui/statusline-ansi-bridge.test.mjs +3 -3
- package/src/tui/theme.mjs +41 -647
- package/src/tui/themes/base.mjs +86 -0
- package/src/tui/themes/basic.mjs +85 -0
- package/src/tui/themes/catppuccin.mjs +72 -0
- package/src/tui/themes/dracula.mjs +70 -0
- package/src/tui/themes/everforest.mjs +71 -0
- package/src/tui/themes/gruvbox.mjs +71 -0
- package/src/tui/themes/index.mjs +71 -0
- package/src/tui/themes/indigo.mjs +78 -0
- package/src/tui/themes/kanagawa.mjs +80 -0
- package/src/tui/themes/light.mjs +81 -0
- package/src/tui/themes/nord.mjs +72 -0
- package/src/tui/themes/onedark.mjs +16 -0
- package/src/tui/themes/rosepine.mjs +70 -0
- package/src/tui/themes/teal.mjs +81 -0
- package/src/tui/themes/tokyonight.mjs +79 -0
- package/src/tui/themes/utils.mjs +106 -0
- package/src/tui/themes/warm.mjs +79 -0
- package/src/tui/transcript-tool-failures.mjs +13 -2
- package/src/ui/markdown.mjs +1 -1
- package/src/ui/model-display.mjs +2 -2
- package/src/ui/statusline.mjs +26 -27
- package/src/vendor/statusline/bin/statusline-lib.mjs +0 -623
- package/src/vendor/statusline/bin/statusline-route.mjs +5 -12
- package/src/vendor/statusline/src/gateway/claude-current.mjs +3 -3
- package/src/vendor/statusline/src/gateway/route-meta.mjs +30 -16
- package/src/workflows/default/WORKFLOW.md +39 -12
- package/src/workflows/sequential/WORKFLOW.md +46 -0
- package/src/workflows/solo/WORKFLOW.md +7 -0
- package/vendor/ink/build/display-width.js +62 -0
- package/vendor/ink/build/ink.js +154 -20
- package/vendor/ink/build/measure-text.js +4 -1
- package/vendor/ink/build/output.js +115 -9
- package/vendor/ink/build/render-node-to-output.js +4 -1
- package/vendor/ink/build/render.js +4 -0
- package/src/hooks/lib/permission-rules.cjs +0 -170
- package/src/hooks/lib/settings-loader.cjs +0 -112
- package/src/lib/hook-pipe-path.cjs +0 -10
- package/src/output-styles/extreme-simple.md +0 -20
- package/src/rules/lead/04-workflow.md +0 -51
- package/src/runtime/channels/lib/hook-pipe-server.mjs +0 -671
- package/src/workflows/default/workflow.json +0 -13
- package/src/workflows/solo/workflow.json +0 -7
|
@@ -33,6 +33,7 @@ import {
|
|
|
33
33
|
} from './agent-task-status.mjs';
|
|
34
34
|
import { AGENT_OWNER } from '../runtime/agent/orchestrator/agent-owner.mjs';
|
|
35
35
|
import { clearGatewaySessionRoute, writeGatewaySessionRoute } from '../vendor/statusline/src/gateway/session-routes.mjs';
|
|
36
|
+
import { isKnownProvider } from './provider-admin.mjs';
|
|
36
37
|
|
|
37
38
|
const STANDALONE_SOURCE_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..');
|
|
38
39
|
|
|
@@ -44,11 +45,16 @@ const DEFAULT_AGENT_PRESETS = Object.freeze({
|
|
|
44
45
|
explore: 'sonnet-high',
|
|
45
46
|
maintainer: 'haiku',
|
|
46
47
|
worker: 'sonnet-high',
|
|
47
|
-
'heavy-worker': '
|
|
48
|
+
'heavy-worker': 'sonnet-high',
|
|
48
49
|
reviewer: 'opus-xhigh',
|
|
49
50
|
debugger: 'opus-xhigh',
|
|
50
51
|
});
|
|
51
52
|
|
|
53
|
+
// Mirrors DEFAULT_PROVIDER in mixdog-session-runtime.mjs. Used only as the
|
|
54
|
+
// last-resort fallback when a stored agent route omits its provider and the
|
|
55
|
+
// config carries no defaultProvider.
|
|
56
|
+
const DEFAULT_PROVIDER = 'anthropic-oauth';
|
|
57
|
+
|
|
52
58
|
export const AGENT_TOOL = {
|
|
53
59
|
name: 'agent',
|
|
54
60
|
title: 'Agent',
|
|
@@ -60,17 +66,17 @@ export const AGENT_TOOL = {
|
|
|
60
66
|
openWorldHint: true,
|
|
61
67
|
agentHidden: true,
|
|
62
68
|
},
|
|
63
|
-
description: 'Delegate scoped work. Agent handoffs always start background tasks and return task IDs immediately. Spawn independent
|
|
69
|
+
description: 'Delegate scoped work. Agent handoffs always start background tasks and return task IDs immediately. Spawn independent scopes in parallel with distinct tags; for the same scope/tag, use send or spawn with the same tag to reuse the live session. Wait for the completion notification before dependent work. Do not call status/read after spawn; status/read are manual recovery only.',
|
|
64
70
|
inputSchema: {
|
|
65
71
|
type: 'object',
|
|
66
72
|
properties: {
|
|
67
73
|
type: { type: 'string', enum: ['spawn', 'send', 'list', 'close', 'cancel', 'status', 'read', 'cleanup'], description: 'Action. Default spawn.' },
|
|
68
74
|
task_id: { type: 'string', description: 'Manual recovery task ID.' },
|
|
69
75
|
agent: { type: 'string', description: 'Workflow agent id.' },
|
|
70
|
-
tag: { type: 'string', description: 'Stable
|
|
76
|
+
tag: { type: 'string', description: 'Stable scope handle. Reuse the same tag for follow-up on the same scope; use distinct tags only for independent scopes.' },
|
|
71
77
|
sessionId: { type: 'string', description: 'Raw sess_ id.' },
|
|
72
78
|
prompt: { type: 'string', description: 'Scoped task brief.' },
|
|
73
|
-
message: { type: 'string', description: 'Follow-up or brief.' },
|
|
79
|
+
message: { type: 'string', description: 'Follow-up for send/reuse, or brief.' },
|
|
74
80
|
file: { type: 'string', description: 'Prompt file.' },
|
|
75
81
|
cwd: { type: 'string', description: 'Working directory.' },
|
|
76
82
|
context: { type: 'string', description: 'Extra agent context.' },
|
|
@@ -80,6 +86,45 @@ export const AGENT_TOOL = {
|
|
|
80
86
|
};
|
|
81
87
|
|
|
82
88
|
const WORKER_INDEX_FILE = 'agent-workers.json';
|
|
89
|
+
|
|
90
|
+
// A worker that hits the loop iteration ceiling, gets truncated mid-synthesis,
|
|
91
|
+
// or produces an empty terminal turn returns content:'' but never throws — so
|
|
92
|
+
// the background task would otherwise reconcile as a benign `completed` empty
|
|
93
|
+
// success. That is wrong: an empty final answer is an error. loop.mjs is the
|
|
94
|
+
// single classifier: it tags result.terminationReason for abnormal finishes
|
|
95
|
+
// (carried through manager.mjs terminalResultPreview). We key purely off that
|
|
96
|
+
// here. iteration_cap / truncated are real problems for EVERY agent (hidden
|
|
97
|
+
// too). The plain `empty` case is tagged by the loop ONLY for public agents;
|
|
98
|
+
// hidden agents (explorer/cycle/…) legitimately emit empty terminal turns and
|
|
99
|
+
// are left untagged, so they stay benign.
|
|
100
|
+
function abnormalEmptyFinishError(result, agent) {
|
|
101
|
+
// The loop (loop.mjs) is the single classifier: it tags terminationReason
|
|
102
|
+
// ONLY for abnormal finishes, and gates the `empty` case behind !hidden.
|
|
103
|
+
// So we key purely off terminationReason here — no separate content/hidden
|
|
104
|
+
// check, which previously (a) exempted hidden agents from cap/truncated and
|
|
105
|
+
// (b) let a capped tool-call turn with preamble text slip through as success.
|
|
106
|
+
const reason = result?.terminationReason;
|
|
107
|
+
if (!reason) return null;
|
|
108
|
+
const iterations = result?.iterations ?? 0;
|
|
109
|
+
const toolCallsTotal = result?.toolCallsTotal ?? 0;
|
|
110
|
+
const maxLoopIterations = result?.maxLoopIterations ?? 0;
|
|
111
|
+
const stopReason = result?.stopReason ?? result?.stop_reason ?? null;
|
|
112
|
+
switch (reason) {
|
|
113
|
+
case 'iteration_cap':
|
|
114
|
+
// Real problem for EVERY agent (hidden too): the loop never terminated on
|
|
115
|
+
// its own contract, so any preamble text is not a trustworthy final answer.
|
|
116
|
+
return `agent '${agent}' hit the loop iteration ceiling (${maxLoopIterations} iterations, ${toolCallsTotal} tool calls) without producing a final answer`;
|
|
117
|
+
case 'truncated':
|
|
118
|
+
return `agent '${agent}' response was truncated (stopReason=${stopReason}) before a final answer (${iterations} iterations, ${toolCallsTotal} tool calls)`;
|
|
119
|
+
case 'empty':
|
|
120
|
+
// Only tagged for PUBLIC agents (hidden agents legitimately emit empty
|
|
121
|
+
// terminal turns and are left untagged by the loop).
|
|
122
|
+
return `agent '${agent}' finished without a final answer (stopReason=${stopReason ?? 'none'}, ${iterations} iterations, ${toolCallsTotal} tool calls)`;
|
|
123
|
+
default:
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
83
128
|
function envTimeoutMs(name, fallback) {
|
|
84
129
|
const raw = process.env[name];
|
|
85
130
|
if (raw === undefined || raw === '') return fallback;
|
|
@@ -126,21 +171,21 @@ function normalizeAgentName(value) {
|
|
|
126
171
|
return id;
|
|
127
172
|
}
|
|
128
173
|
|
|
129
|
-
function readAgentFrontmatterPermission(
|
|
130
|
-
const
|
|
131
|
-
if (!
|
|
132
|
-
const cacheKey = `${dataDir || ''}\u0000${
|
|
174
|
+
function readAgentFrontmatterPermission(agent, dataDir) {
|
|
175
|
+
const cleanAgent = clean(agent);
|
|
176
|
+
if (!cleanAgent) return null;
|
|
177
|
+
const cacheKey = `${dataDir || ''}\u0000${cleanAgent}`;
|
|
133
178
|
const cached = _frontmatterPermCache.get(cacheKey);
|
|
134
179
|
if (cached && Date.now() - cached.atMs < FRONTMATTER_PERM_CACHE_TTL_MS) {
|
|
135
180
|
return cached.value;
|
|
136
181
|
}
|
|
137
182
|
const candidates = [];
|
|
138
183
|
if (dataDir) {
|
|
139
|
-
candidates.push(join(dataDir, 'agents',
|
|
140
|
-
candidates.push(join(dataDir, 'agents', `${
|
|
184
|
+
candidates.push(join(dataDir, 'agents', cleanAgent, 'AGENT.md'));
|
|
185
|
+
candidates.push(join(dataDir, 'agents', `${cleanAgent}.md`));
|
|
141
186
|
}
|
|
142
|
-
candidates.push(join(STANDALONE_SOURCE_ROOT, 'agents',
|
|
143
|
-
candidates.push(join(STANDALONE_SOURCE_ROOT, 'agents', `${
|
|
187
|
+
candidates.push(join(STANDALONE_SOURCE_ROOT, 'agents', cleanAgent, 'AGENT.md'));
|
|
188
|
+
candidates.push(join(STANDALONE_SOURCE_ROOT, 'agents', `${cleanAgent}.md`));
|
|
144
189
|
let resolved = null;
|
|
145
190
|
for (const file of candidates) {
|
|
146
191
|
if (!existsSync(file)) continue;
|
|
@@ -248,8 +293,8 @@ function synthesizePreset(config, key) {
|
|
|
248
293
|
};
|
|
249
294
|
}
|
|
250
295
|
|
|
251
|
-
function normalizeAgentRoute(routeLike) {
|
|
252
|
-
const provider = clean(routeLike?.provider);
|
|
296
|
+
function normalizeAgentRoute(routeLike, fallbackProvider = '') {
|
|
297
|
+
const provider = clean(routeLike?.provider) || clean(fallbackProvider);
|
|
253
298
|
const model = clean(routeLike?.model);
|
|
254
299
|
if (!provider || !model) return null;
|
|
255
300
|
return {
|
|
@@ -260,8 +305,8 @@ function normalizeAgentRoute(routeLike) {
|
|
|
260
305
|
};
|
|
261
306
|
}
|
|
262
307
|
|
|
263
|
-
function agentPresetName(
|
|
264
|
-
return `AGENT ${String(
|
|
308
|
+
function agentPresetName(agent) {
|
|
309
|
+
return `AGENT ${String(agent || '').toUpperCase()}`;
|
|
265
310
|
}
|
|
266
311
|
|
|
267
312
|
async function resolvePrompt(args, cwd) {
|
|
@@ -334,7 +379,7 @@ function renderResult(value) {
|
|
|
334
379
|
for (const worker of workers) {
|
|
335
380
|
const tokens = worker.windowTokens ? ` ctx=${worker.windowTokens}${worker.windowCap ? `/${worker.windowCap}` : ''}` : '';
|
|
336
381
|
const terminal = worker.clientHostPid ? ` term=${worker.clientHostPid}` : '';
|
|
337
|
-
const base = `- ${worker.tag} ${worker.
|
|
382
|
+
const base = `- ${worker.tag} ${worker.agent || 'agent'} ${worker.status || 'idle'}/${worker.worker_stage || worker.stage || 'idle'} ${worker.provider}/${worker.model}${terminal}${tokens}`;
|
|
338
383
|
lines.push(appendAgentProgressKv(base, worker));
|
|
339
384
|
}
|
|
340
385
|
const jobs = Array.isArray(value.jobs) ? value.jobs : [];
|
|
@@ -355,7 +400,7 @@ function renderResult(value) {
|
|
|
355
400
|
if (value.type) lines.push(`type: ${value.type}`);
|
|
356
401
|
if (value.reused) lines.push('reused: true');
|
|
357
402
|
if (value.tag || value.sessionId) lines.push(`target: ${value.tag || '-'} ${value.sessionId || ''}`.trim());
|
|
358
|
-
if (value.
|
|
403
|
+
if (value.agent) lines.push(`agent: ${value.agent}`);
|
|
359
404
|
if (value.provider && value.model) lines.push(`model: ${value.provider}/${value.model}`);
|
|
360
405
|
if (value.effort) lines.push(`effort: ${value.effort}`);
|
|
361
406
|
if (value.fast === true || value.fast === false) lines.push(`fast: ${value.fast ? 'on' : 'off'}`);
|
|
@@ -393,7 +438,7 @@ function renderResult(value) {
|
|
|
393
438
|
'agent message queued',
|
|
394
439
|
value.reused ? 'reused: true' : null,
|
|
395
440
|
`target: ${value.tag || '-'} ${value.sessionId || ''}`.trim(),
|
|
396
|
-
value.
|
|
441
|
+
value.agent ? `agent: ${value.agent}` : null,
|
|
397
442
|
`queueDepth: ${value.queueDepth ?? 1}`,
|
|
398
443
|
].filter(Boolean).join('\n');
|
|
399
444
|
}
|
|
@@ -412,7 +457,7 @@ function renderResult(value) {
|
|
|
412
457
|
const header = [
|
|
413
458
|
value.respawned ? 'agent respawned' : 'agent result',
|
|
414
459
|
value.tag ? `tag=${value.tag}` : null,
|
|
415
|
-
value.
|
|
460
|
+
value.agent ? `agent=${value.agent}` : null,
|
|
416
461
|
value.provider && value.model ? `${value.provider}/${value.model}` : null,
|
|
417
462
|
].filter(Boolean).join(' ');
|
|
418
463
|
return `${header}\n${stripFinalAnswerWrapper(value.content)}`;
|
|
@@ -423,7 +468,7 @@ function renderResult(value) {
|
|
|
423
468
|
|
|
424
469
|
export function createStandaloneAgent({ cfgMod, reg, mgr, dataDir, cwd: defaultCwd }) {
|
|
425
470
|
const tags = new Map();
|
|
426
|
-
const
|
|
471
|
+
const tagAgents = new Map();
|
|
427
472
|
const tagCwds = new Map();
|
|
428
473
|
const reapTimers = new Map();
|
|
429
474
|
const workerIndexMutators = [];
|
|
@@ -463,7 +508,7 @@ export function createStandaloneAgent({ cfgMod, reg, mgr, dataDir, cwd: defaultC
|
|
|
463
508
|
.map((row) => ({
|
|
464
509
|
tag: clean(row.tag),
|
|
465
510
|
sessionId: clean(row.sessionId),
|
|
466
|
-
|
|
511
|
+
agent: clean(row.agent) || null,
|
|
467
512
|
provider: clean(row.provider) || null,
|
|
468
513
|
model: clean(row.model) || null,
|
|
469
514
|
preset: clean(row.preset) || null,
|
|
@@ -562,7 +607,7 @@ export function createStandaloneAgent({ cfgMod, reg, mgr, dataDir, cwd: defaultC
|
|
|
562
607
|
if (!key) return;
|
|
563
608
|
const prev = byKey.get(key) || {};
|
|
564
609
|
const merged = { ...prev, ...normalized };
|
|
565
|
-
for (const field of ['
|
|
610
|
+
for (const field of ['agent', 'provider', 'model', 'preset', 'effort', 'fast', 'clientHostPid', 'cwd', 'task_id', 'permission', 'toolPermission']) {
|
|
566
611
|
if ((merged[field] === null || merged[field] === '') && prev[field] != null && prev[field] !== '') {
|
|
567
612
|
merged[field] = prev[field];
|
|
568
613
|
}
|
|
@@ -610,7 +655,7 @@ export function createStandaloneAgent({ cfgMod, reg, mgr, dataDir, cwd: defaultC
|
|
|
610
655
|
return {
|
|
611
656
|
tag,
|
|
612
657
|
sessionId,
|
|
613
|
-
|
|
658
|
+
agent: clean(extra.agent) || clean(session?.agent) || null,
|
|
614
659
|
provider: clean(extra.provider) || clean(session?.provider) || null,
|
|
615
660
|
model: clean(extra.model) || clean(session?.model) || null,
|
|
616
661
|
preset: clean(extra.preset) || clean(session?.presetName) || null,
|
|
@@ -637,7 +682,7 @@ export function createStandaloneAgent({ cfgMod, reg, mgr, dataDir, cwd: defaultC
|
|
|
637
682
|
return {
|
|
638
683
|
id: row.sessionId,
|
|
639
684
|
agentTag: row.tag,
|
|
640
|
-
|
|
685
|
+
agent: row.agent || null,
|
|
641
686
|
provider: row.provider || null,
|
|
642
687
|
model: row.model || null,
|
|
643
688
|
presetName: row.preset || null,
|
|
@@ -661,7 +706,7 @@ export function createStandaloneAgent({ cfgMod, reg, mgr, dataDir, cwd: defaultC
|
|
|
661
706
|
const normalized = normalizeWorkerRows({ workers: [row] })[0];
|
|
662
707
|
if (!normalized) return false;
|
|
663
708
|
tags.set(normalized.tag, normalized.sessionId);
|
|
664
|
-
if (normalized.
|
|
709
|
+
if (normalized.agent) tagAgents.set(normalized.tag, normalized.agent);
|
|
665
710
|
if (normalized.cwd) tagCwds.set(normalized.tag, normalized.cwd);
|
|
666
711
|
if (defer) {
|
|
667
712
|
return queueWorkerIndexMutation((byKey) => applyWorkerRowUpsert(byKey, normalized));
|
|
@@ -698,7 +743,7 @@ export function createStandaloneAgent({ cfgMod, reg, mgr, dataDir, cwd: defaultC
|
|
|
698
743
|
for (const row of rows) {
|
|
699
744
|
if (!row.tag || !row.sessionId) continue;
|
|
700
745
|
tags.set(row.tag, row.sessionId);
|
|
701
|
-
if (row.
|
|
746
|
+
if (row.agent) tagAgents.set(row.tag, row.agent);
|
|
702
747
|
if (row.cwd) tagCwds.set(row.tag, row.cwd);
|
|
703
748
|
}
|
|
704
749
|
return rows;
|
|
@@ -790,14 +835,14 @@ export function createStandaloneAgent({ cfgMod, reg, mgr, dataDir, cwd: defaultC
|
|
|
790
835
|
return rows;
|
|
791
836
|
}
|
|
792
837
|
|
|
793
|
-
function nextTag(
|
|
838
|
+
function nextTag(agent, context = {}) {
|
|
794
839
|
refreshTagsFromSessions({ context });
|
|
795
|
-
// Auto tags are
|
|
796
|
-
// ("worker3", "heavy-worker7", or "agent1" when the
|
|
797
|
-
// index is the max existing `^
|
|
798
|
-
// hyphenated
|
|
840
|
+
// Auto tags are agent + a per-agent local index with NO hyphen
|
|
841
|
+
// ("worker3", "heavy-worker7", or "agent1" when the agent is unset). The
|
|
842
|
+
// index is the max existing `^agent(\d+)$` + 1, escaping the agent so a
|
|
843
|
+
// hyphenated agent ("heavy-worker") is matched literally. Keep incrementing
|
|
799
844
|
// on any live collision.
|
|
800
|
-
const base = clean(
|
|
845
|
+
const base = clean(agent) || 'agent';
|
|
801
846
|
const escaped = base.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
802
847
|
const re = new RegExp(`^${escaped}(\\d+)$`);
|
|
803
848
|
let maxN = 0;
|
|
@@ -827,7 +872,7 @@ export function createStandaloneAgent({ cfgMod, reg, mgr, dataDir, cwd: defaultC
|
|
|
827
872
|
if (!tag || tags.has(tag)) continue;
|
|
828
873
|
if (!sessionMatchesContext(session, context)) continue;
|
|
829
874
|
tags.set(tag, session.id);
|
|
830
|
-
if (session.
|
|
875
|
+
if (session.agent) tagAgents.set(tag, session.agent);
|
|
831
876
|
if (session.cwd) tagCwds.set(tag, session.cwd);
|
|
832
877
|
upsertWorkerSessionDeferred(session, tag);
|
|
833
878
|
}
|
|
@@ -836,7 +881,7 @@ export function createStandaloneAgent({ cfgMod, reg, mgr, dataDir, cwd: defaultC
|
|
|
836
881
|
function bindTag(tag, session, extra = {}) {
|
|
837
882
|
if (!tag || !session?.id) return;
|
|
838
883
|
tags.set(tag, session.id);
|
|
839
|
-
if (session.
|
|
884
|
+
if (session.agent) tagAgents.set(tag, session.agent);
|
|
840
885
|
if (session.cwd) tagCwds.set(tag, session.cwd);
|
|
841
886
|
upsertWorkerSessionDeferred(session, tag, extra);
|
|
842
887
|
}
|
|
@@ -845,7 +890,7 @@ export function createStandaloneAgent({ cfgMod, reg, mgr, dataDir, cwd: defaultC
|
|
|
845
890
|
if (!tag) return;
|
|
846
891
|
const sessionId = tags.get(tag) || '';
|
|
847
892
|
tags.delete(tag);
|
|
848
|
-
|
|
893
|
+
tagAgents.delete(tag);
|
|
849
894
|
tagCwds.delete(tag);
|
|
850
895
|
removeWorkerRow({ tag, sessionId });
|
|
851
896
|
}
|
|
@@ -1034,10 +1079,14 @@ export function createStandaloneAgent({ cfgMod, reg, mgr, dataDir, cwd: defaultC
|
|
|
1034
1079
|
};
|
|
1035
1080
|
}
|
|
1036
1081
|
|
|
1037
|
-
const agentName = normalizeAgentName(args.agent
|
|
1082
|
+
const agentName = normalizeAgentName(args.agent);
|
|
1083
|
+
const configuredDefault = clean(config?.defaultProvider);
|
|
1084
|
+
const fallbackProvider = configuredDefault && isKnownProvider(configuredDefault)
|
|
1085
|
+
? configuredDefault
|
|
1086
|
+
: DEFAULT_PROVIDER;
|
|
1038
1087
|
const agentRoute = !clean(args.preset)
|
|
1039
|
-
? (normalizeAgentRoute(config?.agents?.[agentName])
|
|
1040
|
-
|| (agentName === 'maintainer' ? normalizeAgentRoute(config?.agents?.maintenance) : null))
|
|
1088
|
+
? (normalizeAgentRoute(config?.agents?.[agentName], fallbackProvider)
|
|
1089
|
+
|| (agentName === 'maintainer' ? normalizeAgentRoute(config?.agents?.maintenance, fallbackProvider) : null))
|
|
1041
1090
|
: null;
|
|
1042
1091
|
if (agentRoute) {
|
|
1043
1092
|
return {
|
|
@@ -1073,11 +1122,11 @@ export function createStandaloneAgent({ cfgMod, reg, mgr, dataDir, cwd: defaultC
|
|
|
1073
1122
|
const stage = session.stage || (status === 'idle' || status === 'error' || status === 'closed'
|
|
1074
1123
|
? status
|
|
1075
1124
|
: (runtime?.stage || status));
|
|
1076
|
-
const progress = sessionProgressExtras(sessionId, session.
|
|
1125
|
+
const progress = sessionProgressExtras(sessionId, session.agent || null, now);
|
|
1077
1126
|
rows.push({
|
|
1078
1127
|
tag,
|
|
1079
1128
|
sessionId,
|
|
1080
|
-
|
|
1129
|
+
agent: session.agent || null,
|
|
1081
1130
|
provider: session.provider,
|
|
1082
1131
|
model: session.model,
|
|
1083
1132
|
preset: session.presetName || null,
|
|
@@ -1133,7 +1182,7 @@ export function createStandaloneAgent({ cfgMod, reg, mgr, dataDir, cwd: defaultC
|
|
|
1133
1182
|
if (!session) return null;
|
|
1134
1183
|
const runtime = mgr.getSessionRuntime?.(sessionId);
|
|
1135
1184
|
const status = session.closed === true ? 'closed' : (session.status || 'idle');
|
|
1136
|
-
const progress = sessionProgressExtras(sessionId, session.
|
|
1185
|
+
const progress = sessionProgressExtras(sessionId, session.agent || null);
|
|
1137
1186
|
return {
|
|
1138
1187
|
workerStatus: status,
|
|
1139
1188
|
stage: progress.worker_stage || runtime?.stage || status,
|
|
@@ -1151,7 +1200,7 @@ export function createStandaloneAgent({ cfgMod, reg, mgr, dataDir, cwd: defaultC
|
|
|
1151
1200
|
status: task.status,
|
|
1152
1201
|
tag: task.tag || null,
|
|
1153
1202
|
sessionId: task.sessionId || null,
|
|
1154
|
-
|
|
1203
|
+
agent: task.agent || null,
|
|
1155
1204
|
preset: task.preset || null,
|
|
1156
1205
|
provider: task.provider || null,
|
|
1157
1206
|
model: task.model || null,
|
|
@@ -1162,7 +1211,7 @@ export function createStandaloneAgent({ cfgMod, reg, mgr, dataDir, cwd: defaultC
|
|
|
1162
1211
|
finishedAt: task.finishedAt || null,
|
|
1163
1212
|
error: task.error || null,
|
|
1164
1213
|
...jobWorkerSnapshot(task.sessionId),
|
|
1165
|
-
...sessionProgressExtras(task.sessionId, task.
|
|
1214
|
+
...sessionProgressExtras(task.sessionId, task.agent || null, Date.now(), task.status),
|
|
1166
1215
|
}));
|
|
1167
1216
|
return wantedPid ? rows.filter((row) => positiveInt(row.clientHostPid) === wantedPid) : rows;
|
|
1168
1217
|
}
|
|
@@ -1177,7 +1226,7 @@ export function createStandaloneAgent({ cfgMod, reg, mgr, dataDir, cwd: defaultC
|
|
|
1177
1226
|
|
|
1178
1227
|
function renderJob(job, includeResult = false) {
|
|
1179
1228
|
const meta = job.meta || {};
|
|
1180
|
-
let progress = sessionProgressExtras(meta.sessionId, meta.
|
|
1229
|
+
let progress = sessionProgressExtras(meta.sessionId, meta.agent || null, Date.now(), job.status);
|
|
1181
1230
|
// Spawn is deferred: before the worker session exists, sessionProgressExtras
|
|
1182
1231
|
// returns {} and the status card would show only "status: running" with no
|
|
1183
1232
|
// stage/progress. Fill a minimal stage so the caller can tell the job is
|
|
@@ -1196,7 +1245,7 @@ export function createStandaloneAgent({ cfgMod, reg, mgr, dataDir, cwd: defaultC
|
|
|
1196
1245
|
status: job.status,
|
|
1197
1246
|
tag: meta.tag || null,
|
|
1198
1247
|
sessionId: meta.sessionId || null,
|
|
1199
|
-
|
|
1248
|
+
agent: meta.agent || null,
|
|
1200
1249
|
preset: meta.preset || null,
|
|
1201
1250
|
provider: meta.provider || null,
|
|
1202
1251
|
model: meta.model || null,
|
|
@@ -1217,7 +1266,7 @@ export function createStandaloneAgent({ cfgMod, reg, mgr, dataDir, cwd: defaultC
|
|
|
1217
1266
|
...(extras || {}),
|
|
1218
1267
|
tag: prepared.tag,
|
|
1219
1268
|
sessionId: prepared.session.id,
|
|
1220
|
-
|
|
1269
|
+
agent: prepared.agent,
|
|
1221
1270
|
preset: presetKey(prepared.preset) || prepared.presetName,
|
|
1222
1271
|
provider: prepared.preset.provider,
|
|
1223
1272
|
model: prepared.preset.model,
|
|
@@ -1228,7 +1277,7 @@ export function createStandaloneAgent({ cfgMod, reg, mgr, dataDir, cwd: defaultC
|
|
|
1228
1277
|
}
|
|
1229
1278
|
|
|
1230
1279
|
function pendingSpawnMeta(args = {}, extras = {}) {
|
|
1231
|
-
const
|
|
1280
|
+
const agent = normalizeAgentName(args.agent);
|
|
1232
1281
|
// Best-effort resolve the default preset so the pending "Spawn …" card can
|
|
1233
1282
|
// already show the model (e.g. "Spawn Heavy Worker (Opus 4.8)") even when
|
|
1234
1283
|
// the caller did not pass an explicit provider/model. Never throw: fall back
|
|
@@ -1242,7 +1291,7 @@ export function createStandaloneAgent({ cfgMod, reg, mgr, dataDir, cwd: defaultC
|
|
|
1242
1291
|
...(extras || {}),
|
|
1243
1292
|
tag: clean(args.tag) || null,
|
|
1244
1293
|
sessionId: null,
|
|
1245
|
-
|
|
1294
|
+
agent: agent || null,
|
|
1246
1295
|
preset: clean(args.preset) || presetKey(resolved) || null,
|
|
1247
1296
|
provider: clean(args.provider) || clean(resolved?.provider) || null,
|
|
1248
1297
|
model: clean(args.model) || clean(resolved?.model) || null,
|
|
@@ -1260,7 +1309,7 @@ export function createStandaloneAgent({ cfgMod, reg, mgr, dataDir, cwd: defaultC
|
|
|
1260
1309
|
...job.input,
|
|
1261
1310
|
tag: next.tag || job.input.tag || null,
|
|
1262
1311
|
sessionId: next.sessionId || job.input.sessionId || null,
|
|
1263
|
-
|
|
1312
|
+
agent: next.agent || job.input.agent || null,
|
|
1264
1313
|
};
|
|
1265
1314
|
}
|
|
1266
1315
|
job.label = next.tag || next.sessionId || job.label;
|
|
@@ -1321,14 +1370,19 @@ export function createStandaloneAgent({ cfgMod, reg, mgr, dataDir, cwd: defaultC
|
|
|
1321
1370
|
const ownerSessionId = clean(notifyContext?.callerSessionId || notifyContext?.sessionId);
|
|
1322
1371
|
const upstream = typeof notifyContext?.notifyFn === 'function' ? notifyContext.notifyFn : null;
|
|
1323
1372
|
const finishedAt = new Date().toISOString();
|
|
1373
|
+
// An abnormal-empty finish carries an `error` — the early preview must NOT
|
|
1374
|
+
// present it as a benign `completed` card, or the Lead sees success before
|
|
1375
|
+
// the later `failed` reconcile lands. Mirror the terminal status/instruction.
|
|
1376
|
+
const earlyStatus = resultValue && resultValue.error ? 'failed' : 'completed';
|
|
1324
1377
|
const snapshot = {
|
|
1325
1378
|
...job,
|
|
1326
|
-
status:
|
|
1379
|
+
status: earlyStatus,
|
|
1327
1380
|
finishedAt,
|
|
1328
1381
|
finishedAtMs: Date.now(),
|
|
1329
1382
|
result: resultValue,
|
|
1330
1383
|
resultType: job.resultType || 'agent_task_result',
|
|
1331
1384
|
meta: sanitizeTaskMeta(job.meta || {}),
|
|
1385
|
+
...(resultValue && resultValue.error ? { error: resultValue.error } : {}),
|
|
1332
1386
|
};
|
|
1333
1387
|
// An early notification is only a header-only *preview*: it fires before
|
|
1334
1388
|
// the worker's session is persisted to signal the running→completed
|
|
@@ -1341,8 +1395,8 @@ export function createStandaloneAgent({ cfgMod, reg, mgr, dataDir, cwd: defaultC
|
|
|
1341
1395
|
type: snapshot.resultType,
|
|
1342
1396
|
execution_surface: 'agent',
|
|
1343
1397
|
execution_id: job.taskId || null,
|
|
1344
|
-
status:
|
|
1345
|
-
instruction: `The async agent task ${job.taskId || ''} has finished (
|
|
1398
|
+
status: earlyStatus,
|
|
1399
|
+
instruction: `The async agent task ${job.taskId || ''} has finished (${earlyStatus}) - review this result in your next step.`,
|
|
1346
1400
|
...(ownerSessionId ? { caller_session_id: ownerSessionId } : {}),
|
|
1347
1401
|
};
|
|
1348
1402
|
let delivered = false;
|
|
@@ -1377,7 +1431,7 @@ export function createStandaloneAgent({ cfgMod, reg, mgr, dataDir, cwd: defaultC
|
|
|
1377
1431
|
surface: 'agent',
|
|
1378
1432
|
operation: type,
|
|
1379
1433
|
label: jobMeta?.tag || jobMeta?.sessionId || type,
|
|
1380
|
-
input: { type, tag: jobMeta?.tag || null, sessionId: jobMeta?.sessionId || null,
|
|
1434
|
+
input: { type, tag: jobMeta?.tag || null, sessionId: jobMeta?.sessionId || null, agent: jobMeta?.agent || null },
|
|
1381
1435
|
context: notifyContext,
|
|
1382
1436
|
meta: jobMeta,
|
|
1383
1437
|
resultType: 'agent_task_result',
|
|
@@ -1406,8 +1460,10 @@ export function createStandaloneAgent({ cfgMod, reg, mgr, dataDir, cwd: defaultC
|
|
|
1406
1460
|
// installs its progress watchdog, so guard prep with an internal env-
|
|
1407
1461
|
// backed cap rather than exposing per-call timeout knobs on the agent
|
|
1408
1462
|
// tool surface.
|
|
1409
|
-
const prepDeadlineMs =
|
|
1463
|
+
const prepDeadlineMs = nonNegativeInt(args.spawnPrepTimeoutMs ?? args.prepTimeoutMs)
|
|
1464
|
+
?? DEFAULT_SPAWN_PREP_TIMEOUT_MS;
|
|
1410
1465
|
let prepared;
|
|
1466
|
+
const prepState = { timedOut: false };
|
|
1411
1467
|
if (prepDeadlineMs > 0) {
|
|
1412
1468
|
let prepTimer = null;
|
|
1413
1469
|
let timedOut = false;
|
|
@@ -1415,13 +1471,14 @@ export function createStandaloneAgent({ cfgMod, reg, mgr, dataDir, cwd: defaultC
|
|
|
1415
1471
|
// prepareSpawn promise may still resolve later with a fully-built
|
|
1416
1472
|
// session/tag/route — attach a cleanup so the late-arriving prepared is
|
|
1417
1473
|
// torn down, otherwise the orphaned tag would collide on re-spawn.
|
|
1418
|
-
const prepPromise = prepareSpawn(args, callerCwd, context);
|
|
1474
|
+
const prepPromise = prepareSpawn(args, callerCwd, context, prepState);
|
|
1419
1475
|
prepPromise.then((late) => {
|
|
1420
1476
|
if (timedOut) closePreparedSpawn(late, 'agent-spawn-prep-timeout');
|
|
1421
1477
|
}, () => {});
|
|
1422
1478
|
const timeout = new Promise((_resolve, reject) => {
|
|
1423
1479
|
prepTimer = setTimeout(() => {
|
|
1424
1480
|
timedOut = true;
|
|
1481
|
+
prepState.timedOut = true;
|
|
1425
1482
|
reject(new Error(`agent spawn prep timed out (${prepDeadlineMs}ms) before model request`));
|
|
1426
1483
|
}, prepDeadlineMs);
|
|
1427
1484
|
prepTimer.unref?.();
|
|
@@ -1432,7 +1489,7 @@ export function createStandaloneAgent({ cfgMod, reg, mgr, dataDir, cwd: defaultC
|
|
|
1432
1489
|
if (prepTimer) clearTimeout(prepTimer);
|
|
1433
1490
|
}
|
|
1434
1491
|
} else {
|
|
1435
|
-
prepared = await prepareSpawn(args, callerCwd, context);
|
|
1492
|
+
prepared = await prepareSpawn(args, callerCwd, context, prepState);
|
|
1436
1493
|
}
|
|
1437
1494
|
mergeJobMeta(job, preparedSpawnMeta(prepared, extras));
|
|
1438
1495
|
upsertWorkerSessionDeferred(prepared.session, prepared.tag, {
|
|
@@ -1483,17 +1540,20 @@ export function createStandaloneAgent({ cfgMod, reg, mgr, dataDir, cwd: defaultC
|
|
|
1483
1540
|
};
|
|
1484
1541
|
}
|
|
1485
1542
|
|
|
1486
|
-
async function prepareSpawn(args, callerCwd = null, context = {}) {
|
|
1543
|
+
async function prepareSpawn(args, callerCwd = null, context = {}, prepState = null) {
|
|
1487
1544
|
refreshTagsFromSessions({ context });
|
|
1488
1545
|
const config = cfgMod.loadConfig();
|
|
1489
|
-
const
|
|
1490
|
-
if (!
|
|
1491
|
-
const agentPermission = readAgentFrontmatterPermission(
|
|
1492
|
-
const
|
|
1546
|
+
const agent = normalizeAgentName(args.agent);
|
|
1547
|
+
if (!agent) throw new Error('agent spawn: agent is required');
|
|
1548
|
+
const agentPermission = readAgentFrontmatterPermission(agent, dataDir);
|
|
1549
|
+
const agentPerm = normalizeAgentPermission(agentPermission) || null;
|
|
1493
1550
|
const { presetName, preset } = resolvePreset(config, args);
|
|
1494
1551
|
await ensureProvider(config, preset.provider);
|
|
1552
|
+
if (prepState?.timedOut) {
|
|
1553
|
+
throw new Error('agent spawn prep timed out before session bind');
|
|
1554
|
+
}
|
|
1495
1555
|
|
|
1496
|
-
const tag = clean(args.tag) || nextTag(
|
|
1556
|
+
const tag = clean(args.tag) || nextTag(agent, context);
|
|
1497
1557
|
// Any resolved same-tag binding in this terminal (live or lingering trace)
|
|
1498
1558
|
// blocks a fresh spawn. execute() routes live reuse before prepareSpawn.
|
|
1499
1559
|
if (resolveTag(tag, context, { scanSessions: wantsSessionScan(args) })) {
|
|
@@ -1502,25 +1562,28 @@ export function createStandaloneAgent({ cfgMod, reg, mgr, dataDir, cwd: defaultC
|
|
|
1502
1562
|
const baseCwd = resolve(callerCwd || defaultCwd || process.cwd());
|
|
1503
1563
|
const workerCwd = clean(args.cwd) ? resolve(baseCwd, args.cwd) : baseCwd;
|
|
1504
1564
|
const prompt = withCwdHeader(await resolvePrompt(args, workerCwd), workerCwd);
|
|
1565
|
+
if (prepState?.timedOut) {
|
|
1566
|
+
throw new Error('agent spawn prep timed out before session bind');
|
|
1567
|
+
}
|
|
1505
1568
|
const runtimeSpec = cfgMod.resolveRuntimeSpec(preset, { lane: 'agent', agentId: tag });
|
|
1506
1569
|
const maxLoopIterations = positiveInt(args.maxLoopIterations) || null;
|
|
1507
|
-
const watchdogPolicy = resolveAgentWatchdogPolicy(
|
|
1570
|
+
const watchdogPolicy = resolveAgentWatchdogPolicy(agent);
|
|
1508
1571
|
const { session, effectiveCwd } = prepareAgentSession({
|
|
1509
|
-
|
|
1572
|
+
agent,
|
|
1510
1573
|
presetName,
|
|
1511
1574
|
preset,
|
|
1512
1575
|
runtimeSpec,
|
|
1513
1576
|
owner: AGENT_OWNER,
|
|
1514
1577
|
cwd: workerCwd,
|
|
1515
1578
|
sourceType: 'cli',
|
|
1516
|
-
sourceName:
|
|
1579
|
+
sourceName: agent,
|
|
1517
1580
|
parentSessionId: clean(context?.callerSessionId || context?.sessionId) || null,
|
|
1518
1581
|
ownerSessionId: clean(context?.callerSessionId || context?.sessionId) || null,
|
|
1519
1582
|
clientHostPid: terminalPidForContext(context) || null,
|
|
1520
1583
|
agentTag: tag,
|
|
1521
1584
|
taskType: clean(args.taskType) || clean(args.typeHint) || undefined,
|
|
1522
1585
|
maxLoopIterations: maxLoopIterations || undefined,
|
|
1523
|
-
permission:
|
|
1586
|
+
permission: agentPerm || undefined,
|
|
1524
1587
|
cacheKeyOverride: args.cacheKey || undefined,
|
|
1525
1588
|
});
|
|
1526
1589
|
// Lead sessions write a gateway-session route when created; agent sessions
|
|
@@ -1528,7 +1591,7 @@ export function createStandaloneAgent({ cfgMod, reg, mgr, dataDir, cwd: defaultC
|
|
|
1528
1591
|
// or the vendored L1/L2 statusline cannot resolve the agent route/model.
|
|
1529
1592
|
writeAgentStatuslineRoute(session.id, preset);
|
|
1530
1593
|
bindTag(tag, session, {
|
|
1531
|
-
|
|
1594
|
+
agent,
|
|
1532
1595
|
preset: presetKey(preset) || presetName,
|
|
1533
1596
|
provider: preset.provider,
|
|
1534
1597
|
model: preset.model,
|
|
@@ -1542,7 +1605,7 @@ export function createStandaloneAgent({ cfgMod, reg, mgr, dataDir, cwd: defaultC
|
|
|
1542
1605
|
args,
|
|
1543
1606
|
tag,
|
|
1544
1607
|
session,
|
|
1545
|
-
|
|
1608
|
+
agent,
|
|
1546
1609
|
preset,
|
|
1547
1610
|
presetName,
|
|
1548
1611
|
workerCwd: effectiveCwd || workerCwd,
|
|
@@ -1553,11 +1616,11 @@ export function createStandaloneAgent({ cfgMod, reg, mgr, dataDir, cwd: defaultC
|
|
|
1553
1616
|
}
|
|
1554
1617
|
|
|
1555
1618
|
async function runSpawn(prepared, notifyContext = null, job = null) {
|
|
1556
|
-
const { args, tag, session,
|
|
1619
|
+
const { args, tag, session, agent, preset, presetName, workerCwd, prompt, watchdogPolicy } = prepared;
|
|
1557
1620
|
const watchdog = startProgressIdleWatchdog(session.id, watchdogPolicy);
|
|
1558
1621
|
let finalStatus = 'idle';
|
|
1559
1622
|
upsertWorkerSessionDeferred(session, tag, {
|
|
1560
|
-
|
|
1623
|
+
agent,
|
|
1561
1624
|
preset: presetKey(preset) || presetName,
|
|
1562
1625
|
provider: preset.provider,
|
|
1563
1626
|
model: preset.model,
|
|
@@ -1567,17 +1630,26 @@ export function createStandaloneAgent({ cfgMod, reg, mgr, dataDir, cwd: defaultC
|
|
|
1567
1630
|
stage: 'running',
|
|
1568
1631
|
});
|
|
1569
1632
|
try {
|
|
1570
|
-
const completionValue = (result) =>
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1633
|
+
const completionValue = (result) => {
|
|
1634
|
+
// Promote an abnormal finish (iteration cap, truncation, or a public
|
|
1635
|
+
// agent's empty terminal turn) to an explicit error, so the Lead
|
|
1636
|
+
// receives it as a failure with an accurate reason instead of a silent
|
|
1637
|
+
// `completed` empty result. Keyed off loop.mjs terminationReason;
|
|
1638
|
+
// hidden agents finishing normally-empty are left untagged (benign).
|
|
1639
|
+
const abnormalError = abnormalEmptyFinishError(result, agent);
|
|
1640
|
+
return {
|
|
1641
|
+
tag,
|
|
1642
|
+
sessionId: session.id,
|
|
1643
|
+
agent,
|
|
1644
|
+
preset: presetKey(preset) || presetName,
|
|
1645
|
+
provider: preset.provider,
|
|
1646
|
+
model: preset.model,
|
|
1647
|
+
effort: preset.effort || null,
|
|
1648
|
+
fast: preset.fast === true,
|
|
1649
|
+
content: result?.content || '',
|
|
1650
|
+
...(abnormalError ? { error: abnormalError } : {}),
|
|
1651
|
+
};
|
|
1652
|
+
};
|
|
1581
1653
|
const result = await mgr.askSession(session.id, prompt, args.context || null, null, workerCwd, null, {
|
|
1582
1654
|
notifyFn: workerNotifyFn(session.id, notifyContext || {}),
|
|
1583
1655
|
...(job ? {
|
|
@@ -1591,11 +1663,21 @@ export function createStandaloneAgent({ cfgMod, reg, mgr, dataDir, cwd: defaultC
|
|
|
1591
1663
|
// reconcile remains a backup for the error/no-terminal-result path.
|
|
1592
1664
|
if (job?.taskId) {
|
|
1593
1665
|
try {
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1666
|
+
// An empty/abnormal finish is a failure, not a completion:
|
|
1667
|
+
// reconcile as `failed` with the accurate error so the Lead
|
|
1668
|
+
// card renders `error: …` instead of a header-only empty card.
|
|
1669
|
+
reconcileBackgroundTask(job.taskId, value.error
|
|
1670
|
+
? {
|
|
1671
|
+
status: 'failed',
|
|
1672
|
+
result: value,
|
|
1673
|
+
error: value.error,
|
|
1674
|
+
terminalReason: 'agent-empty-final',
|
|
1675
|
+
}
|
|
1676
|
+
: {
|
|
1677
|
+
status: 'completed',
|
|
1678
|
+
result: value,
|
|
1679
|
+
terminalReason: 'agent-terminal-result',
|
|
1680
|
+
});
|
|
1599
1681
|
} catch {}
|
|
1600
1682
|
}
|
|
1601
1683
|
},
|
|
@@ -1604,14 +1686,40 @@ export function createStandaloneAgent({ cfgMod, reg, mgr, dataDir, cwd: defaultC
|
|
|
1604
1686
|
// The early preview no longer promises body suppression, so the canonical
|
|
1605
1687
|
// notifyTaskCompletion is left to fire exactly once with output via the
|
|
1606
1688
|
// resolve/reconcile/finally path.
|
|
1607
|
-
|
|
1689
|
+
const finalValue = completionValue(result);
|
|
1690
|
+
// Non-job return path (or job path where the terminal-result reconcile
|
|
1691
|
+
// already ran): if the finish was abnormal-empty, surface it as a thrown
|
|
1692
|
+
// error so finalStatus becomes 'error' and the caller's error path (and
|
|
1693
|
+
// the finally reconcile below, as `failed`) render the accurate reason.
|
|
1694
|
+
if (finalValue.error) {
|
|
1695
|
+
finalStatus = 'error';
|
|
1696
|
+
if (job) job._terminalResultValue = finalValue;
|
|
1697
|
+
throw new Error(finalValue.error);
|
|
1698
|
+
}
|
|
1699
|
+
return finalValue;
|
|
1608
1700
|
} catch (error) {
|
|
1609
1701
|
finalStatus = 'error';
|
|
1702
|
+
// Part C: a mid-stream stall (StreamStalledError / ESTREAMSTALL) throws
|
|
1703
|
+
// here WITHOUT a terminal result, so the finally reconcile below (gated on
|
|
1704
|
+
// _terminalResultValue) would be skipped and only the outer task-reject
|
|
1705
|
+
// path would notify. Belt-and-suspenders: reconcile this job to `failed`
|
|
1706
|
+
// now so the owner (Lead) always gets a failure notification instead of a
|
|
1707
|
+
// task stranded in `running`. Idempotent — completeBackgroundTask no-ops
|
|
1708
|
+
// once terminal, so the outer reject path can't double-notify.
|
|
1709
|
+
if (job?.taskId && job._terminalResultValue === undefined) {
|
|
1710
|
+
try {
|
|
1711
|
+
reconcileBackgroundTask(job.taskId, {
|
|
1712
|
+
status: 'failed',
|
|
1713
|
+
error: presentErrorText(error, { surface: 'agent' }),
|
|
1714
|
+
terminalReason: 'agent-stream-stalled',
|
|
1715
|
+
});
|
|
1716
|
+
} catch {}
|
|
1717
|
+
}
|
|
1610
1718
|
throw error;
|
|
1611
1719
|
} finally {
|
|
1612
1720
|
watchdog?.stop?.();
|
|
1613
1721
|
upsertWorkerSessionDeferred(session, tag, {
|
|
1614
|
-
|
|
1722
|
+
agent,
|
|
1615
1723
|
preset: presetKey(preset) || presetName,
|
|
1616
1724
|
provider: preset.provider,
|
|
1617
1725
|
model: preset.model,
|
|
@@ -1631,6 +1739,9 @@ export function createStandaloneAgent({ cfgMod, reg, mgr, dataDir, cwd: defaultC
|
|
|
1631
1739
|
reconcileBackgroundTask(job.taskId, {
|
|
1632
1740
|
status: finalStatus === 'error' ? 'failed' : 'completed',
|
|
1633
1741
|
result: job._terminalResultValue,
|
|
1742
|
+
...(finalStatus === 'error' && job._terminalResultValue?.error
|
|
1743
|
+
? { error: job._terminalResultValue.error }
|
|
1744
|
+
: {}),
|
|
1634
1745
|
terminalReason: 'agent-finally-reconcile',
|
|
1635
1746
|
});
|
|
1636
1747
|
} catch {}
|
|
@@ -1658,20 +1769,27 @@ export function createStandaloneAgent({ cfgMod, reg, mgr, dataDir, cwd: defaultC
|
|
|
1658
1769
|
|
|
1659
1770
|
async function runSend(prepared, notifyContext = null, job = null) {
|
|
1660
1771
|
const { args, session, sessionId, prompt } = prepared;
|
|
1661
|
-
const
|
|
1662
|
-
const watchdog = startProgressIdleWatchdog(sessionId, resolveAgentWatchdogPolicy(
|
|
1772
|
+
const sendAgent = session.agent || normalizeAgentName(args.agent);
|
|
1773
|
+
const watchdog = startProgressIdleWatchdog(sessionId, resolveAgentWatchdogPolicy(sendAgent));
|
|
1663
1774
|
const tag = tagForSession(sessionId);
|
|
1664
1775
|
let finalStatus = 'idle';
|
|
1665
1776
|
upsertWorkerSessionDeferred(session, tag, { status: 'running', stage: 'running' });
|
|
1666
1777
|
try {
|
|
1667
|
-
const completionValue = (result) =>
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1778
|
+
const completionValue = (result) => {
|
|
1779
|
+
// Same abnormal-empty → error promotion as runSpawn: a reused/`send`
|
|
1780
|
+
// worker that hits the cap, truncates, or finishes empty must surface
|
|
1781
|
+
// as a failure with an accurate reason, not a silent completed empty.
|
|
1782
|
+
const abnormalError = abnormalEmptyFinishError(result, session.agent || sendAgent);
|
|
1783
|
+
return {
|
|
1784
|
+
tag,
|
|
1785
|
+
sessionId,
|
|
1786
|
+
agent: session.agent || null,
|
|
1787
|
+
provider: session.provider,
|
|
1788
|
+
model: session.model,
|
|
1789
|
+
content: result?.content || '',
|
|
1790
|
+
...(abnormalError ? { error: abnormalError } : {}),
|
|
1791
|
+
};
|
|
1792
|
+
};
|
|
1675
1793
|
const result = await mgr.askSession(sessionId, prompt, args.context || null, null, session.cwd || defaultCwd, null, {
|
|
1676
1794
|
notifyFn: workerNotifyFn(sessionId, notifyContext || {}),
|
|
1677
1795
|
...(job ? {
|
|
@@ -1683,11 +1801,18 @@ export function createStandaloneAgent({ cfgMod, reg, mgr, dataDir, cwd: defaultC
|
|
|
1683
1801
|
// post-result save can't strand the task in `running`. Idempotent.
|
|
1684
1802
|
if (job?.taskId) {
|
|
1685
1803
|
try {
|
|
1686
|
-
reconcileBackgroundTask(job.taskId,
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1804
|
+
reconcileBackgroundTask(job.taskId, value.error
|
|
1805
|
+
? {
|
|
1806
|
+
status: 'failed',
|
|
1807
|
+
result: value,
|
|
1808
|
+
error: value.error,
|
|
1809
|
+
terminalReason: 'agent-empty-final',
|
|
1810
|
+
}
|
|
1811
|
+
: {
|
|
1812
|
+
status: 'completed',
|
|
1813
|
+
result: value,
|
|
1814
|
+
terminalReason: 'agent-terminal-result',
|
|
1815
|
+
});
|
|
1691
1816
|
} catch {}
|
|
1692
1817
|
}
|
|
1693
1818
|
},
|
|
@@ -1695,9 +1820,27 @@ export function createStandaloneAgent({ cfgMod, reg, mgr, dataDir, cwd: defaultC
|
|
|
1695
1820
|
});
|
|
1696
1821
|
// Early preview no longer suppresses the canonical body notification;
|
|
1697
1822
|
// notifyTaskCompletion fires once with output via resolve/reconcile.
|
|
1698
|
-
|
|
1823
|
+
const finalValue = completionValue(result);
|
|
1824
|
+
if (finalValue.error) {
|
|
1825
|
+
finalStatus = 'error';
|
|
1826
|
+
if (job) job._terminalResultValue = finalValue;
|
|
1827
|
+
throw new Error(finalValue.error);
|
|
1828
|
+
}
|
|
1829
|
+
return finalValue;
|
|
1699
1830
|
} catch (error) {
|
|
1700
1831
|
finalStatus = 'error';
|
|
1832
|
+
// Part C (send path mirror): a mid-stream stall throws with no terminal
|
|
1833
|
+
// result — reconcile to `failed` so the owner is notified rather than the
|
|
1834
|
+
// task hanging in `running`. Idempotent (see runSpawn note).
|
|
1835
|
+
if (job?.taskId && job._terminalResultValue === undefined) {
|
|
1836
|
+
try {
|
|
1837
|
+
reconcileBackgroundTask(job.taskId, {
|
|
1838
|
+
status: 'failed',
|
|
1839
|
+
error: presentErrorText(error, { surface: 'agent' }),
|
|
1840
|
+
terminalReason: 'agent-stream-stalled',
|
|
1841
|
+
});
|
|
1842
|
+
} catch {}
|
|
1843
|
+
}
|
|
1701
1844
|
throw error;
|
|
1702
1845
|
} finally {
|
|
1703
1846
|
watchdog?.stop?.();
|
|
@@ -1713,6 +1856,9 @@ export function createStandaloneAgent({ cfgMod, reg, mgr, dataDir, cwd: defaultC
|
|
|
1713
1856
|
reconcileBackgroundTask(job.taskId, {
|
|
1714
1857
|
status: finalStatus === 'error' ? 'failed' : 'completed',
|
|
1715
1858
|
result: job._terminalResultValue,
|
|
1859
|
+
...(finalStatus === 'error' && job._terminalResultValue?.error
|
|
1860
|
+
? { error: job._terminalResultValue.error }
|
|
1861
|
+
: {}),
|
|
1716
1862
|
terminalReason: 'agent-finally-reconcile',
|
|
1717
1863
|
});
|
|
1718
1864
|
} catch {}
|
|
@@ -1737,14 +1883,14 @@ export function createStandaloneAgent({ cfgMod, reg, mgr, dataDir, cwd: defaultC
|
|
|
1737
1883
|
...extras,
|
|
1738
1884
|
tag: tagForSession(prepared.sessionId),
|
|
1739
1885
|
sessionId: prepared.sessionId,
|
|
1740
|
-
|
|
1886
|
+
agent: prepared.session.agent || null,
|
|
1741
1887
|
queueDepth,
|
|
1742
1888
|
});
|
|
1743
1889
|
}
|
|
1744
1890
|
const job = startJob('send', {
|
|
1745
1891
|
tag: tagForSession(prepared.sessionId),
|
|
1746
1892
|
sessionId: prepared.sessionId,
|
|
1747
|
-
|
|
1893
|
+
agent: prepared.session.agent || null,
|
|
1748
1894
|
provider: prepared.session.provider || null,
|
|
1749
1895
|
model: prepared.session.model || null,
|
|
1750
1896
|
preset: prepared.session.presetName || null,
|
|
@@ -1770,7 +1916,7 @@ export function createStandaloneAgent({ cfgMod, reg, mgr, dataDir, cwd: defaultC
|
|
|
1770
1916
|
}
|
|
1771
1917
|
const sessionId = resolveTag(target, scopedContext, { scanSessions: wantsSessionScan(args) });
|
|
1772
1918
|
if (!sessionId) {
|
|
1773
|
-
if (!target.startsWith('sess_') &&
|
|
1919
|
+
if (!target.startsWith('sess_') && tagAgents.has(target)) {
|
|
1774
1920
|
forgetTag(target);
|
|
1775
1921
|
if (task?.taskId) cancelBackgroundTask(task.taskId, 'cancelled by agent close');
|
|
1776
1922
|
return { closed: true, forgotten: true, tag: target, sessionId: null, task_id: task?.taskId || null };
|
|
@@ -1829,7 +1975,7 @@ export function createStandaloneAgent({ cfgMod, reg, mgr, dataDir, cwd: defaultC
|
|
|
1829
1975
|
const value = clean(tag);
|
|
1830
1976
|
if (!value || value.startsWith('sess_')) return false;
|
|
1831
1977
|
if (resolveTag(value, context, { excludeTerminalTraces: true })) return false; // live -> reuse, not trace
|
|
1832
|
-
if (
|
|
1978
|
+
if (tagAgents.has(value)) return true;
|
|
1833
1979
|
return readWorkerRows(context).some((row) => clean(row.tag) === value);
|
|
1834
1980
|
}
|
|
1835
1981
|
|