mixdog 0.9.1 → 0.9.3
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 +9 -1
- 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/anthropic-maxtokens-test.mjs +119 -0
- package/scripts/background-task-meta-smoke.mjs +1 -1
- package/scripts/bench-run.mjs +262 -0
- package/scripts/build-tui.mjs +13 -1
- package/scripts/compact-smoke.mjs +12 -0
- package/scripts/compact-trigger-migration-smoke.mjs +67 -1
- package/scripts/explore-bench.mjs +124 -0
- package/scripts/hook-bus-test.mjs +191 -0
- 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/path-suffix-test.mjs +57 -0
- package/scripts/provider-stream-stall-test.mjs +276 -0
- package/scripts/provider-toolcall-test.mjs +599 -1
- package/scripts/recall-bench.mjs +207 -0
- package/scripts/routing-corpus.mjs +281 -0
- package/scripts/session-bench.mjs +1526 -0
- package/scripts/session-diag.mjs +595 -0
- package/scripts/task-bench.mjs +207 -0
- package/scripts/tool-failures.mjs +6 -6
- package/scripts/tool-smoke.mjs +310 -67
- package/scripts/toolcall-args-test.mjs +81 -0
- package/src/agents/debugger/AGENT.md +4 -4
- package/src/agents/heavy-worker/AGENT.md +20 -9
- package/src/agents/reviewer/AGENT.md +4 -4
- package/src/agents/worker/AGENT.md +17 -9
- 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/rules-builder.cjs +32 -54
- package/src/mixdog-session-runtime.mjs +1040 -2036
- 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 +17 -7
- package/src/rules/agent/00-common.md +7 -5
- package/src/rules/agent/30-explorer.md +8 -12
- package/src/rules/lead/01-general.md +3 -1
- package/src/rules/lead/lead-tool.md +13 -2
- package/src/rules/shared/01-tool.md +23 -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 +182 -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 +100 -18
- 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-max-tokens.mjs +93 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +313 -84
- package/src/runtime/agent/orchestrator/providers/anthropic.mjs +104 -38
- package/src/runtime/agent/orchestrator/providers/api-usage.mjs +28 -33
- 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/lib/usage-primitives.mjs +32 -0
- package/src/runtime/agent/orchestrator/providers/model-catalog.mjs +18 -8
- package/src/runtime/agent/orchestrator/providers/oauth-usage.mjs +54 -20
- package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +227 -31
- package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +83 -6
- package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +229 -115
- package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +213 -33
- package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +18 -0
- package/src/runtime/agent/orchestrator/providers/opencode-go-usage.mjs +49 -17
- package/src/runtime/agent/orchestrator/providers/registry.mjs +2 -1
- package/src/runtime/agent/orchestrator/providers/retry-classifier.mjs +22 -17
- 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/compact-debug.mjs +28 -0
- package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +262 -0
- package/src/runtime/agent/orchestrator/session/loop/context-overflow.mjs +38 -0
- package/src/runtime/agent/orchestrator/session/loop/env.mjs +14 -0
- package/src/runtime/agent/orchestrator/session/loop/hidden-agents.mjs +21 -0
- package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +49 -0
- package/src/runtime/agent/orchestrator/session/loop/steering.mjs +63 -0
- package/src/runtime/agent/orchestrator/session/loop/stored-tool-args.mjs +100 -0
- package/src/runtime/agent/orchestrator/session/loop/tool-classify.mjs +52 -0
- package/src/runtime/agent/orchestrator/session/loop/tool-helpers.mjs +218 -0
- package/src/runtime/agent/orchestrator/session/loop/transcript-repair.mjs +101 -0
- package/src/runtime/agent/orchestrator/session/loop/usage.mjs +35 -0
- package/src/runtime/agent/orchestrator/session/loop.mjs +513 -1000
- package/src/runtime/agent/orchestrator/session/manager/context-meta.mjs +227 -0
- package/src/runtime/agent/orchestrator/session/manager/pending-messages.mjs +235 -0
- package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +137 -0
- package/src/runtime/agent/orchestrator/session/manager/rules-cache.mjs +155 -0
- package/src/runtime/agent/orchestrator/session/manager/tool-resolution.mjs +303 -0
- package/src/runtime/agent/orchestrator/session/manager.mjs +232 -1152
- 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/builtin/arg-guard.mjs +194 -24
- package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.test.mjs +143 -0
- package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +34 -18
- package/src/runtime/agent/orchestrator/tools/builtin/external-tool-adapters.mjs +241 -0
- package/src/runtime/agent/orchestrator/tools/builtin/external-tool-adapters.test.mjs +162 -0
- package/src/runtime/agent/orchestrator/tools/builtin/fuzzy-match.mjs +1 -1
- 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-diagnostics.mjs +42 -2
- 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 +14 -5
- package/src/runtime/agent/orchestrator/tools/builtin/read-single-tool.mjs +11 -4
- package/src/runtime/agent/orchestrator/tools/builtin/read-tool.mjs +10 -17
- package/src/runtime/agent/orchestrator/tools/builtin/search-tool.mjs +23 -2
- package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +50 -39
- 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 +70 -1
- package/src/runtime/agent/orchestrator/tools/code-graph/build.mjs +303 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/constants.mjs +43 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/disk-cache.mjs +382 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +497 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/graph-binary.mjs +295 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/graph-model.mjs +158 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/lang-predicates.mjs +128 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/memory-cache.mjs +66 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/project-root.mjs +44 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/search.mjs +1192 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/source-access.mjs +81 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/span.mjs +19 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/symbol-index.mjs +280 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/text-mask.mjs +347 -0
- package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +5 -5
- package/src/runtime/agent/orchestrator/tools/code-graph.mjs +39 -4189
- package/src/runtime/agent/orchestrator/tools/patch.mjs +119 -5
- package/src/runtime/agent/orchestrator/tools/progress-message.mjs +1 -2
- package/src/runtime/agent/orchestrator/tools/shell-command.mjs +1 -2
- package/src/runtime/agent/orchestrator/tools/shell-snapshot.mjs +2 -4
- package/src/runtime/channels/backends/discord.mjs +99 -9
- package/src/runtime/channels/backends/telegram.mjs +501 -0
- package/src/runtime/channels/index.mjs +437 -1439
- package/src/runtime/channels/lib/boot-profile.mjs +23 -0
- package/src/runtime/channels/lib/config.mjs +54 -2
- package/src/runtime/channels/lib/crash-log.mjs +106 -0
- package/src/runtime/channels/lib/format.mjs +4 -2
- package/src/runtime/channels/lib/index-drop-trace.mjs +72 -0
- package/src/runtime/channels/lib/output-forwarder.mjs +88 -67
- package/src/runtime/channels/lib/runtime-paths.mjs +29 -0
- package/src/runtime/channels/lib/scheduler.mjs +1 -1
- package/src/runtime/channels/lib/telegram-format.mjs +280 -0
- package/src/runtime/channels/lib/tool-format.mjs +1 -1
- package/src/runtime/channels/lib/transcript-discovery.mjs +19 -1
- package/src/runtime/channels/lib/webhook.mjs +59 -31
- package/src/runtime/channels/lib/whisper-language.mjs +42 -0
- package/src/runtime/channels/tool-defs.mjs +1 -1
- package/src/runtime/memory/index.mjs +465 -345
- package/src/runtime/memory/lib/agent-ipc.mjs +2 -2
- package/src/runtime/memory/lib/core-memory-store.mjs +352 -2
- package/src/runtime/memory/lib/cycle-signatures.mjs +34 -0
- package/src/runtime/memory/lib/http-wire.mjs +57 -0
- package/src/runtime/memory/lib/memory-cycle1.mjs +1 -1
- package/src/runtime/memory/lib/memory-cycle2.mjs +65 -9
- package/src/runtime/memory/lib/memory-cycle3.mjs +1 -1
- package/src/runtime/memory/lib/memory-recall-scope-filter.mjs +24 -0
- package/src/runtime/memory/lib/memory-retrievers.mjs +8 -0
- package/src/runtime/memory/lib/memory.mjs +121 -4
- package/src/runtime/memory/lib/pg/adapter.mjs +139 -15
- package/src/runtime/memory/lib/promotion-fingerprint.mjs +50 -0
- package/src/runtime/memory/lib/recall-format.mjs +183 -0
- package/src/runtime/memory/lib/session-ingest.mjs +107 -0
- package/src/runtime/memory/lib/trace-store.mjs +69 -22
- package/src/runtime/memory/tool-defs.mjs +10 -7
- package/src/runtime/shared/abort-controller.mjs +1 -1
- package/src/runtime/shared/background-tasks.mjs +2 -3
- package/src/runtime/shared/buffered-appender.mjs +149 -0
- 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/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/task-notification-envelope.mjs +98 -0
- package/src/runtime/shared/task-notification-envelope.test.mjs +107 -0
- package/src/runtime/shared/tool-execution-contract.mjs +2 -2
- package/src/runtime/shared/tool-surface.mjs +98 -13
- package/src/runtime/shared/transcript-writer.mjs +156 -0
- package/src/runtime/shared/update-checker.mjs +214 -0
- package/src/session-runtime/config-helpers.mjs +209 -0
- package/src/session-runtime/effort.mjs +128 -0
- package/src/session-runtime/fs-utils.mjs +10 -0
- package/src/session-runtime/model-capabilities.mjs +130 -0
- package/src/session-runtime/output-styles.mjs +124 -0
- package/src/session-runtime/plugin-mcp.mjs +114 -0
- package/src/session-runtime/session-text.mjs +100 -0
- package/src/session-runtime/statusline-route.mjs +35 -0
- package/src/session-runtime/tool-catalog.mjs +720 -0
- package/src/session-runtime/workflow.mjs +358 -0
- package/src/standalone/agent-tool.mjs +302 -117
- package/src/standalone/channel-admin.mjs +133 -40
- package/src/standalone/channel-worker.mjs +10 -292
- package/src/standalone/explore-tool.mjs +12 -5
- package/src/standalone/hook-bus.mjs +165 -8
- package/src/standalone/memory-runtime-proxy.mjs +3 -1
- package/src/standalone/opencode-go-login.mjs +121 -0
- package/src/standalone/provider-admin.mjs +25 -3
- package/src/standalone/usage-dashboard.mjs +1 -1
- package/src/tui/App.jsx +2883 -778
- 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 +355 -22
- 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 +183 -101
- package/src/tui/components/TurnDone.jsx +4 -4
- package/src/tui/components/UsagePanel.jsx +1 -1
- package/src/tui/components/tool-output-format.mjs +161 -23
- package/src/tui/components/tool-output-format.test.mjs +87 -0
- package/src/tui/display-width.mjs +69 -0
- package/src/tui/display-width.test.mjs +35 -0
- package/src/tui/dist/index.mjs +6731 -2333
- package/src/tui/engine/agent-envelope.mjs +296 -0
- package/src/tui/engine/boot-profile.mjs +21 -0
- package/src/tui/engine/labels.mjs +67 -0
- package/src/tui/engine/notice-text.mjs +112 -0
- package/src/tui/engine/queue-helpers.mjs +161 -0
- package/src/tui/engine/session-stats.mjs +46 -0
- package/src/tui/engine/tool-call-fields.mjs +23 -0
- package/src/tui/engine/tool-result-text.mjs +126 -0
- package/src/tui/engine.mjs +569 -948
- package/src/tui/index.jsx +117 -7
- package/src/tui/input-editing.mjs +58 -8
- package/src/tui/input-editing.selection.test.mjs +75 -0
- package/src/tui/keyboard-protocol.mjs +42 -0
- package/src/tui/lib/voice-recorder.mjs +469 -0
- package/src/tui/markdown/format-token.mjs +128 -76
- package/src/tui/markdown/format-token.test.mjs +61 -19
- 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 +36 -9
- package/src/tui/paste-fix.test.mjs +119 -0
- 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 -657
- 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 +80 -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 +75 -27
- 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 +46 -12
- package/src/workflows/sequential/WORKFLOW.md +51 -0
- package/src/workflows/solo/WORKFLOW.md +12 -1
- package/vendor/ink/build/display-width.js +62 -0
- package/vendor/ink/build/ink.js +100 -12
- 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/output-styles/extreme-simple.md +0 -20
- package/src/rules/lead/04-workflow.md +0 -51
- package/src/workflows/default/workflow.json +0 -13
- package/src/workflows/solo/workflow.json +0 -7
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
taskIdFromArgs,
|
|
15
15
|
} from '../runtime/shared/background-tasks.mjs';
|
|
16
16
|
import { modelVisibleToolCompletionMessage } from '../runtime/shared/tool-execution-contract.mjs';
|
|
17
|
-
import { presentErrorText } from '../runtime/shared/err-text.mjs';
|
|
17
|
+
import { presentErrorText, errorLine } from '../runtime/shared/err-text.mjs';
|
|
18
18
|
import { updateJsonAtomicSync } from '../runtime/shared/atomic-file.mjs';
|
|
19
19
|
import {
|
|
20
20
|
normalizeAgentPermission,
|
|
@@ -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;
|
|
@@ -90,7 +135,11 @@ function envTimeoutMs(name, fallback) {
|
|
|
90
135
|
// Grace window during which a terminated/idle worker row is kept around so the
|
|
91
136
|
// same terminal can re-use (or cleanly re-spawn) the same tag. Cached as a
|
|
92
137
|
// constant like the other timeouts; override with MIXDOG_AGENT_TERMINAL_REAP_MS.
|
|
93
|
-
|
|
138
|
+
// 5m (was 1h): aligned with the BP4 message-tail cache TTL — past 5m the
|
|
139
|
+
// session's prompt cache is cold anyway, so same-tag reuse pays a full prefix
|
|
140
|
+
// rewrite; a fresh respawn (send dead-tag fallback) starts smaller and cheaper.
|
|
141
|
+
// Trace replay (2026-07): agent tail cost 1h 1.77M -> 5m 1.11M (-37%).
|
|
142
|
+
const TERMINAL_REAP_MS = envTimeoutMs('MIXDOG_AGENT_TERMINAL_REAP_MS', 5 * 60_000);
|
|
94
143
|
// Independent hard cap for the spawn *prep* phase (ensureProvider /
|
|
95
144
|
// prepareAgentSession / catalog+rules load). Kept separate from the
|
|
96
145
|
// first-response watchdog so prep cannot hang a whole fanout before the model
|
|
@@ -126,21 +175,21 @@ function normalizeAgentName(value) {
|
|
|
126
175
|
return id;
|
|
127
176
|
}
|
|
128
177
|
|
|
129
|
-
function readAgentFrontmatterPermission(
|
|
130
|
-
const
|
|
131
|
-
if (!
|
|
132
|
-
const cacheKey = `${dataDir || ''}\u0000${
|
|
178
|
+
function readAgentFrontmatterPermission(agent, dataDir) {
|
|
179
|
+
const cleanAgent = clean(agent);
|
|
180
|
+
if (!cleanAgent) return null;
|
|
181
|
+
const cacheKey = `${dataDir || ''}\u0000${cleanAgent}`;
|
|
133
182
|
const cached = _frontmatterPermCache.get(cacheKey);
|
|
134
183
|
if (cached && Date.now() - cached.atMs < FRONTMATTER_PERM_CACHE_TTL_MS) {
|
|
135
184
|
return cached.value;
|
|
136
185
|
}
|
|
137
186
|
const candidates = [];
|
|
138
187
|
if (dataDir) {
|
|
139
|
-
candidates.push(join(dataDir, 'agents',
|
|
140
|
-
candidates.push(join(dataDir, 'agents', `${
|
|
188
|
+
candidates.push(join(dataDir, 'agents', cleanAgent, 'AGENT.md'));
|
|
189
|
+
candidates.push(join(dataDir, 'agents', `${cleanAgent}.md`));
|
|
141
190
|
}
|
|
142
|
-
candidates.push(join(STANDALONE_SOURCE_ROOT, 'agents',
|
|
143
|
-
candidates.push(join(STANDALONE_SOURCE_ROOT, 'agents', `${
|
|
191
|
+
candidates.push(join(STANDALONE_SOURCE_ROOT, 'agents', cleanAgent, 'AGENT.md'));
|
|
192
|
+
candidates.push(join(STANDALONE_SOURCE_ROOT, 'agents', `${cleanAgent}.md`));
|
|
144
193
|
let resolved = null;
|
|
145
194
|
for (const file of candidates) {
|
|
146
195
|
if (!existsSync(file)) continue;
|
|
@@ -248,8 +297,8 @@ function synthesizePreset(config, key) {
|
|
|
248
297
|
};
|
|
249
298
|
}
|
|
250
299
|
|
|
251
|
-
function normalizeAgentRoute(routeLike) {
|
|
252
|
-
const provider = clean(routeLike?.provider);
|
|
300
|
+
function normalizeAgentRoute(routeLike, fallbackProvider = '') {
|
|
301
|
+
const provider = clean(routeLike?.provider) || clean(fallbackProvider);
|
|
253
302
|
const model = clean(routeLike?.model);
|
|
254
303
|
if (!provider || !model) return null;
|
|
255
304
|
return {
|
|
@@ -260,8 +309,8 @@ function normalizeAgentRoute(routeLike) {
|
|
|
260
309
|
};
|
|
261
310
|
}
|
|
262
311
|
|
|
263
|
-
function agentPresetName(
|
|
264
|
-
return `AGENT ${String(
|
|
312
|
+
function agentPresetName(agent) {
|
|
313
|
+
return `AGENT ${String(agent || '').toUpperCase()}`;
|
|
265
314
|
}
|
|
266
315
|
|
|
267
316
|
async function resolvePrompt(args, cwd) {
|
|
@@ -334,7 +383,7 @@ function renderResult(value) {
|
|
|
334
383
|
for (const worker of workers) {
|
|
335
384
|
const tokens = worker.windowTokens ? ` ctx=${worker.windowTokens}${worker.windowCap ? `/${worker.windowCap}` : ''}` : '';
|
|
336
385
|
const terminal = worker.clientHostPid ? ` term=${worker.clientHostPid}` : '';
|
|
337
|
-
const base = `- ${worker.tag} ${worker.
|
|
386
|
+
const base = `- ${worker.tag} ${worker.agent || 'agent'} ${worker.status || 'idle'}/${worker.worker_stage || worker.stage || 'idle'} ${worker.provider}/${worker.model}${terminal}${tokens}`;
|
|
338
387
|
lines.push(appendAgentProgressKv(base, worker));
|
|
339
388
|
}
|
|
340
389
|
const jobs = Array.isArray(value.jobs) ? value.jobs : [];
|
|
@@ -342,7 +391,7 @@ function renderResult(value) {
|
|
|
342
391
|
for (const job of jobs) {
|
|
343
392
|
const target = job.tag || job.sessionId || '-';
|
|
344
393
|
const terminal = job.clientHostPid ? ` term=${job.clientHostPid}` : '';
|
|
345
|
-
const base = `- ${job.task_id} ${job.type} ${job.status} target=${target}${terminal}${job.error ? ` error=${
|
|
394
|
+
const base = `- ${job.task_id} ${job.type} ${job.status} target=${target}${terminal}${job.error ? ` error=${job.error}` : ''}`;
|
|
346
395
|
lines.push(appendAgentProgressKv(base, job));
|
|
347
396
|
}
|
|
348
397
|
if (workers.length === 0 && jobs.length === 0) lines.push('(no agents or tasks)');
|
|
@@ -355,7 +404,7 @@ function renderResult(value) {
|
|
|
355
404
|
if (value.type) lines.push(`type: ${value.type}`);
|
|
356
405
|
if (value.reused) lines.push('reused: true');
|
|
357
406
|
if (value.tag || value.sessionId) lines.push(`target: ${value.tag || '-'} ${value.sessionId || ''}`.trim());
|
|
358
|
-
if (value.
|
|
407
|
+
if (value.agent) lines.push(`agent: ${value.agent}`);
|
|
359
408
|
if (value.provider && value.model) lines.push(`model: ${value.provider}/${value.model}`);
|
|
360
409
|
if (value.effort) lines.push(`effort: ${value.effort}`);
|
|
361
410
|
if (value.fast === true || value.fast === false) lines.push(`fast: ${value.fast ? 'on' : 'off'}`);
|
|
@@ -377,7 +426,7 @@ function renderResult(value) {
|
|
|
377
426
|
const elapsed = elapsedFromStamps(value.startedAt, value.finishedAt, value.status);
|
|
378
427
|
if (elapsed) lines.push(`elapsed: ${elapsed}`);
|
|
379
428
|
}
|
|
380
|
-
if (value.error) lines.push(`error: ${
|
|
429
|
+
if (value.error) lines.push(`error: ${value.error}`);
|
|
381
430
|
if (value.status === 'running') lines.push('notification: completion will be delivered to the owner session; use read/status only for manual recovery.');
|
|
382
431
|
if (value.result !== undefined) {
|
|
383
432
|
const result = value.result;
|
|
@@ -393,7 +442,7 @@ function renderResult(value) {
|
|
|
393
442
|
'agent message queued',
|
|
394
443
|
value.reused ? 'reused: true' : null,
|
|
395
444
|
`target: ${value.tag || '-'} ${value.sessionId || ''}`.trim(),
|
|
396
|
-
value.
|
|
445
|
+
value.agent ? `agent: ${value.agent}` : null,
|
|
397
446
|
`queueDepth: ${value.queueDepth ?? 1}`,
|
|
398
447
|
].filter(Boolean).join('\n');
|
|
399
448
|
}
|
|
@@ -412,7 +461,7 @@ function renderResult(value) {
|
|
|
412
461
|
const header = [
|
|
413
462
|
value.respawned ? 'agent respawned' : 'agent result',
|
|
414
463
|
value.tag ? `tag=${value.tag}` : null,
|
|
415
|
-
value.
|
|
464
|
+
value.agent ? `agent=${value.agent}` : null,
|
|
416
465
|
value.provider && value.model ? `${value.provider}/${value.model}` : null,
|
|
417
466
|
].filter(Boolean).join(' ');
|
|
418
467
|
return `${header}\n${stripFinalAnswerWrapper(value.content)}`;
|
|
@@ -421,9 +470,15 @@ function renderResult(value) {
|
|
|
421
470
|
return JSON.stringify(value, null, 2);
|
|
422
471
|
}
|
|
423
472
|
|
|
424
|
-
export function createStandaloneAgent({ cfgMod, reg, mgr, dataDir, cwd: defaultCwd }) {
|
|
473
|
+
export function createStandaloneAgent({ cfgMod, reg, mgr, dataDir, cwd: defaultCwd, onSubagentEvent }) {
|
|
474
|
+
// Optional bridge to the standard hook bus for SubagentStart / SubagentStop.
|
|
475
|
+
// Best-effort: a hook error must never affect worker spawn/finish.
|
|
476
|
+
function emitSubagentEvent(phase, agent, extra = {}) {
|
|
477
|
+
if (typeof onSubagentEvent !== 'function') return;
|
|
478
|
+
try { onSubagentEvent(phase, { agent_type: agent || null, ...extra }); } catch { /* best-effort */ }
|
|
479
|
+
}
|
|
425
480
|
const tags = new Map();
|
|
426
|
-
const
|
|
481
|
+
const tagAgents = new Map();
|
|
427
482
|
const tagCwds = new Map();
|
|
428
483
|
const reapTimers = new Map();
|
|
429
484
|
const workerIndexMutators = [];
|
|
@@ -463,7 +518,7 @@ export function createStandaloneAgent({ cfgMod, reg, mgr, dataDir, cwd: defaultC
|
|
|
463
518
|
.map((row) => ({
|
|
464
519
|
tag: clean(row.tag),
|
|
465
520
|
sessionId: clean(row.sessionId),
|
|
466
|
-
|
|
521
|
+
agent: clean(row.agent) || null,
|
|
467
522
|
provider: clean(row.provider) || null,
|
|
468
523
|
model: clean(row.model) || null,
|
|
469
524
|
preset: clean(row.preset) || null,
|
|
@@ -562,7 +617,7 @@ export function createStandaloneAgent({ cfgMod, reg, mgr, dataDir, cwd: defaultC
|
|
|
562
617
|
if (!key) return;
|
|
563
618
|
const prev = byKey.get(key) || {};
|
|
564
619
|
const merged = { ...prev, ...normalized };
|
|
565
|
-
for (const field of ['
|
|
620
|
+
for (const field of ['agent', 'provider', 'model', 'preset', 'effort', 'fast', 'clientHostPid', 'cwd', 'task_id', 'permission', 'toolPermission']) {
|
|
566
621
|
if ((merged[field] === null || merged[field] === '') && prev[field] != null && prev[field] !== '') {
|
|
567
622
|
merged[field] = prev[field];
|
|
568
623
|
}
|
|
@@ -610,7 +665,7 @@ export function createStandaloneAgent({ cfgMod, reg, mgr, dataDir, cwd: defaultC
|
|
|
610
665
|
return {
|
|
611
666
|
tag,
|
|
612
667
|
sessionId,
|
|
613
|
-
|
|
668
|
+
agent: clean(extra.agent) || clean(session?.agent) || null,
|
|
614
669
|
provider: clean(extra.provider) || clean(session?.provider) || null,
|
|
615
670
|
model: clean(extra.model) || clean(session?.model) || null,
|
|
616
671
|
preset: clean(extra.preset) || clean(session?.presetName) || null,
|
|
@@ -637,7 +692,7 @@ export function createStandaloneAgent({ cfgMod, reg, mgr, dataDir, cwd: defaultC
|
|
|
637
692
|
return {
|
|
638
693
|
id: row.sessionId,
|
|
639
694
|
agentTag: row.tag,
|
|
640
|
-
|
|
695
|
+
agent: row.agent || null,
|
|
641
696
|
provider: row.provider || null,
|
|
642
697
|
model: row.model || null,
|
|
643
698
|
presetName: row.preset || null,
|
|
@@ -661,7 +716,7 @@ export function createStandaloneAgent({ cfgMod, reg, mgr, dataDir, cwd: defaultC
|
|
|
661
716
|
const normalized = normalizeWorkerRows({ workers: [row] })[0];
|
|
662
717
|
if (!normalized) return false;
|
|
663
718
|
tags.set(normalized.tag, normalized.sessionId);
|
|
664
|
-
if (normalized.
|
|
719
|
+
if (normalized.agent) tagAgents.set(normalized.tag, normalized.agent);
|
|
665
720
|
if (normalized.cwd) tagCwds.set(normalized.tag, normalized.cwd);
|
|
666
721
|
if (defer) {
|
|
667
722
|
return queueWorkerIndexMutation((byKey) => applyWorkerRowUpsert(byKey, normalized));
|
|
@@ -698,7 +753,7 @@ export function createStandaloneAgent({ cfgMod, reg, mgr, dataDir, cwd: defaultC
|
|
|
698
753
|
for (const row of rows) {
|
|
699
754
|
if (!row.tag || !row.sessionId) continue;
|
|
700
755
|
tags.set(row.tag, row.sessionId);
|
|
701
|
-
if (row.
|
|
756
|
+
if (row.agent) tagAgents.set(row.tag, row.agent);
|
|
702
757
|
if (row.cwd) tagCwds.set(row.tag, row.cwd);
|
|
703
758
|
}
|
|
704
759
|
return rows;
|
|
@@ -790,14 +845,14 @@ export function createStandaloneAgent({ cfgMod, reg, mgr, dataDir, cwd: defaultC
|
|
|
790
845
|
return rows;
|
|
791
846
|
}
|
|
792
847
|
|
|
793
|
-
function nextTag(
|
|
848
|
+
function nextTag(agent, context = {}) {
|
|
794
849
|
refreshTagsFromSessions({ context });
|
|
795
|
-
// Auto tags are
|
|
796
|
-
// ("worker3", "heavy-worker7", or "agent1" when the
|
|
797
|
-
// index is the max existing `^
|
|
798
|
-
// hyphenated
|
|
850
|
+
// Auto tags are agent + a per-agent local index with NO hyphen
|
|
851
|
+
// ("worker3", "heavy-worker7", or "agent1" when the agent is unset). The
|
|
852
|
+
// index is the max existing `^agent(\d+)$` + 1, escaping the agent so a
|
|
853
|
+
// hyphenated agent ("heavy-worker") is matched literally. Keep incrementing
|
|
799
854
|
// on any live collision.
|
|
800
|
-
const base = clean(
|
|
855
|
+
const base = clean(agent) || 'agent';
|
|
801
856
|
const escaped = base.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
802
857
|
const re = new RegExp(`^${escaped}(\\d+)$`);
|
|
803
858
|
let maxN = 0;
|
|
@@ -827,7 +882,7 @@ export function createStandaloneAgent({ cfgMod, reg, mgr, dataDir, cwd: defaultC
|
|
|
827
882
|
if (!tag || tags.has(tag)) continue;
|
|
828
883
|
if (!sessionMatchesContext(session, context)) continue;
|
|
829
884
|
tags.set(tag, session.id);
|
|
830
|
-
if (session.
|
|
885
|
+
if (session.agent) tagAgents.set(tag, session.agent);
|
|
831
886
|
if (session.cwd) tagCwds.set(tag, session.cwd);
|
|
832
887
|
upsertWorkerSessionDeferred(session, tag);
|
|
833
888
|
}
|
|
@@ -836,7 +891,7 @@ export function createStandaloneAgent({ cfgMod, reg, mgr, dataDir, cwd: defaultC
|
|
|
836
891
|
function bindTag(tag, session, extra = {}) {
|
|
837
892
|
if (!tag || !session?.id) return;
|
|
838
893
|
tags.set(tag, session.id);
|
|
839
|
-
if (session.
|
|
894
|
+
if (session.agent) tagAgents.set(tag, session.agent);
|
|
840
895
|
if (session.cwd) tagCwds.set(tag, session.cwd);
|
|
841
896
|
upsertWorkerSessionDeferred(session, tag, extra);
|
|
842
897
|
}
|
|
@@ -845,7 +900,7 @@ export function createStandaloneAgent({ cfgMod, reg, mgr, dataDir, cwd: defaultC
|
|
|
845
900
|
if (!tag) return;
|
|
846
901
|
const sessionId = tags.get(tag) || '';
|
|
847
902
|
tags.delete(tag);
|
|
848
|
-
|
|
903
|
+
tagAgents.delete(tag);
|
|
849
904
|
tagCwds.delete(tag);
|
|
850
905
|
removeWorkerRow({ tag, sessionId });
|
|
851
906
|
}
|
|
@@ -1034,10 +1089,14 @@ export function createStandaloneAgent({ cfgMod, reg, mgr, dataDir, cwd: defaultC
|
|
|
1034
1089
|
};
|
|
1035
1090
|
}
|
|
1036
1091
|
|
|
1037
|
-
const agentName = normalizeAgentName(args.agent
|
|
1092
|
+
const agentName = normalizeAgentName(args.agent);
|
|
1093
|
+
const configuredDefault = clean(config?.defaultProvider);
|
|
1094
|
+
const fallbackProvider = configuredDefault && isKnownProvider(configuredDefault)
|
|
1095
|
+
? configuredDefault
|
|
1096
|
+
: DEFAULT_PROVIDER;
|
|
1038
1097
|
const agentRoute = !clean(args.preset)
|
|
1039
|
-
? (normalizeAgentRoute(config?.agents?.[agentName])
|
|
1040
|
-
|| (agentName === 'maintainer' ? normalizeAgentRoute(config?.agents?.maintenance) : null))
|
|
1098
|
+
? (normalizeAgentRoute(config?.agents?.[agentName], fallbackProvider)
|
|
1099
|
+
|| (agentName === 'maintainer' ? normalizeAgentRoute(config?.agents?.maintenance, fallbackProvider) : null))
|
|
1041
1100
|
: null;
|
|
1042
1101
|
if (agentRoute) {
|
|
1043
1102
|
return {
|
|
@@ -1073,11 +1132,11 @@ export function createStandaloneAgent({ cfgMod, reg, mgr, dataDir, cwd: defaultC
|
|
|
1073
1132
|
const stage = session.stage || (status === 'idle' || status === 'error' || status === 'closed'
|
|
1074
1133
|
? status
|
|
1075
1134
|
: (runtime?.stage || status));
|
|
1076
|
-
const progress = sessionProgressExtras(sessionId, session.
|
|
1135
|
+
const progress = sessionProgressExtras(sessionId, session.agent || null, now);
|
|
1077
1136
|
rows.push({
|
|
1078
1137
|
tag,
|
|
1079
1138
|
sessionId,
|
|
1080
|
-
|
|
1139
|
+
agent: session.agent || null,
|
|
1081
1140
|
provider: session.provider,
|
|
1082
1141
|
model: session.model,
|
|
1083
1142
|
preset: session.presetName || null,
|
|
@@ -1133,7 +1192,7 @@ export function createStandaloneAgent({ cfgMod, reg, mgr, dataDir, cwd: defaultC
|
|
|
1133
1192
|
if (!session) return null;
|
|
1134
1193
|
const runtime = mgr.getSessionRuntime?.(sessionId);
|
|
1135
1194
|
const status = session.closed === true ? 'closed' : (session.status || 'idle');
|
|
1136
|
-
const progress = sessionProgressExtras(sessionId, session.
|
|
1195
|
+
const progress = sessionProgressExtras(sessionId, session.agent || null);
|
|
1137
1196
|
return {
|
|
1138
1197
|
workerStatus: status,
|
|
1139
1198
|
stage: progress.worker_stage || runtime?.stage || status,
|
|
@@ -1151,7 +1210,7 @@ export function createStandaloneAgent({ cfgMod, reg, mgr, dataDir, cwd: defaultC
|
|
|
1151
1210
|
status: task.status,
|
|
1152
1211
|
tag: task.tag || null,
|
|
1153
1212
|
sessionId: task.sessionId || null,
|
|
1154
|
-
|
|
1213
|
+
agent: task.agent || null,
|
|
1155
1214
|
preset: task.preset || null,
|
|
1156
1215
|
provider: task.provider || null,
|
|
1157
1216
|
model: task.model || null,
|
|
@@ -1162,7 +1221,7 @@ export function createStandaloneAgent({ cfgMod, reg, mgr, dataDir, cwd: defaultC
|
|
|
1162
1221
|
finishedAt: task.finishedAt || null,
|
|
1163
1222
|
error: task.error || null,
|
|
1164
1223
|
...jobWorkerSnapshot(task.sessionId),
|
|
1165
|
-
...sessionProgressExtras(task.sessionId, task.
|
|
1224
|
+
...sessionProgressExtras(task.sessionId, task.agent || null, Date.now(), task.status),
|
|
1166
1225
|
}));
|
|
1167
1226
|
return wantedPid ? rows.filter((row) => positiveInt(row.clientHostPid) === wantedPid) : rows;
|
|
1168
1227
|
}
|
|
@@ -1177,7 +1236,7 @@ export function createStandaloneAgent({ cfgMod, reg, mgr, dataDir, cwd: defaultC
|
|
|
1177
1236
|
|
|
1178
1237
|
function renderJob(job, includeResult = false) {
|
|
1179
1238
|
const meta = job.meta || {};
|
|
1180
|
-
let progress = sessionProgressExtras(meta.sessionId, meta.
|
|
1239
|
+
let progress = sessionProgressExtras(meta.sessionId, meta.agent || null, Date.now(), job.status);
|
|
1181
1240
|
// Spawn is deferred: before the worker session exists, sessionProgressExtras
|
|
1182
1241
|
// returns {} and the status card would show only "status: running" with no
|
|
1183
1242
|
// stage/progress. Fill a minimal stage so the caller can tell the job is
|
|
@@ -1196,7 +1255,8 @@ export function createStandaloneAgent({ cfgMod, reg, mgr, dataDir, cwd: defaultC
|
|
|
1196
1255
|
status: job.status,
|
|
1197
1256
|
tag: meta.tag || null,
|
|
1198
1257
|
sessionId: meta.sessionId || null,
|
|
1199
|
-
|
|
1258
|
+
agent: meta.agent || null,
|
|
1259
|
+
...(meta.respawned === true ? { respawned: true, note: 'previous session reaped — fresh session, no prior context; re-supply anchors if needed' } : {}),
|
|
1200
1260
|
preset: meta.preset || null,
|
|
1201
1261
|
provider: meta.provider || null,
|
|
1202
1262
|
model: meta.model || null,
|
|
@@ -1217,7 +1277,7 @@ export function createStandaloneAgent({ cfgMod, reg, mgr, dataDir, cwd: defaultC
|
|
|
1217
1277
|
...(extras || {}),
|
|
1218
1278
|
tag: prepared.tag,
|
|
1219
1279
|
sessionId: prepared.session.id,
|
|
1220
|
-
|
|
1280
|
+
agent: prepared.agent,
|
|
1221
1281
|
preset: presetKey(prepared.preset) || prepared.presetName,
|
|
1222
1282
|
provider: prepared.preset.provider,
|
|
1223
1283
|
model: prepared.preset.model,
|
|
@@ -1228,7 +1288,7 @@ export function createStandaloneAgent({ cfgMod, reg, mgr, dataDir, cwd: defaultC
|
|
|
1228
1288
|
}
|
|
1229
1289
|
|
|
1230
1290
|
function pendingSpawnMeta(args = {}, extras = {}) {
|
|
1231
|
-
const
|
|
1291
|
+
const agent = normalizeAgentName(args.agent);
|
|
1232
1292
|
// Best-effort resolve the default preset so the pending "Spawn …" card can
|
|
1233
1293
|
// already show the model (e.g. "Spawn Heavy Worker (Opus 4.8)") even when
|
|
1234
1294
|
// the caller did not pass an explicit provider/model. Never throw: fall back
|
|
@@ -1242,7 +1302,7 @@ export function createStandaloneAgent({ cfgMod, reg, mgr, dataDir, cwd: defaultC
|
|
|
1242
1302
|
...(extras || {}),
|
|
1243
1303
|
tag: clean(args.tag) || null,
|
|
1244
1304
|
sessionId: null,
|
|
1245
|
-
|
|
1305
|
+
agent: agent || null,
|
|
1246
1306
|
preset: clean(args.preset) || presetKey(resolved) || null,
|
|
1247
1307
|
provider: clean(args.provider) || clean(resolved?.provider) || null,
|
|
1248
1308
|
model: clean(args.model) || clean(resolved?.model) || null,
|
|
@@ -1260,7 +1320,7 @@ export function createStandaloneAgent({ cfgMod, reg, mgr, dataDir, cwd: defaultC
|
|
|
1260
1320
|
...job.input,
|
|
1261
1321
|
tag: next.tag || job.input.tag || null,
|
|
1262
1322
|
sessionId: next.sessionId || job.input.sessionId || null,
|
|
1263
|
-
|
|
1323
|
+
agent: next.agent || job.input.agent || null,
|
|
1264
1324
|
};
|
|
1265
1325
|
}
|
|
1266
1326
|
job.label = next.tag || next.sessionId || job.label;
|
|
@@ -1321,14 +1381,19 @@ export function createStandaloneAgent({ cfgMod, reg, mgr, dataDir, cwd: defaultC
|
|
|
1321
1381
|
const ownerSessionId = clean(notifyContext?.callerSessionId || notifyContext?.sessionId);
|
|
1322
1382
|
const upstream = typeof notifyContext?.notifyFn === 'function' ? notifyContext.notifyFn : null;
|
|
1323
1383
|
const finishedAt = new Date().toISOString();
|
|
1384
|
+
// An abnormal-empty finish carries an `error` — the early preview must NOT
|
|
1385
|
+
// present it as a benign `completed` card, or the Lead sees success before
|
|
1386
|
+
// the later `failed` reconcile lands. Mirror the terminal status/instruction.
|
|
1387
|
+
const earlyStatus = resultValue && resultValue.error ? 'failed' : 'completed';
|
|
1324
1388
|
const snapshot = {
|
|
1325
1389
|
...job,
|
|
1326
|
-
status:
|
|
1390
|
+
status: earlyStatus,
|
|
1327
1391
|
finishedAt,
|
|
1328
1392
|
finishedAtMs: Date.now(),
|
|
1329
1393
|
result: resultValue,
|
|
1330
1394
|
resultType: job.resultType || 'agent_task_result',
|
|
1331
1395
|
meta: sanitizeTaskMeta(job.meta || {}),
|
|
1396
|
+
...(resultValue && resultValue.error ? { error: resultValue.error } : {}),
|
|
1332
1397
|
};
|
|
1333
1398
|
// An early notification is only a header-only *preview*: it fires before
|
|
1334
1399
|
// the worker's session is persisted to signal the running→completed
|
|
@@ -1341,8 +1406,8 @@ export function createStandaloneAgent({ cfgMod, reg, mgr, dataDir, cwd: defaultC
|
|
|
1341
1406
|
type: snapshot.resultType,
|
|
1342
1407
|
execution_surface: 'agent',
|
|
1343
1408
|
execution_id: job.taskId || null,
|
|
1344
|
-
status:
|
|
1345
|
-
instruction: `The async agent task ${job.taskId || ''} has finished (
|
|
1409
|
+
status: earlyStatus,
|
|
1410
|
+
instruction: `The async agent task ${job.taskId || ''} has finished (${earlyStatus}) - review this result in your next step.`,
|
|
1346
1411
|
...(ownerSessionId ? { caller_session_id: ownerSessionId } : {}),
|
|
1347
1412
|
};
|
|
1348
1413
|
let delivered = false;
|
|
@@ -1377,7 +1442,7 @@ export function createStandaloneAgent({ cfgMod, reg, mgr, dataDir, cwd: defaultC
|
|
|
1377
1442
|
surface: 'agent',
|
|
1378
1443
|
operation: type,
|
|
1379
1444
|
label: jobMeta?.tag || jobMeta?.sessionId || type,
|
|
1380
|
-
input: { type, tag: jobMeta?.tag || null, sessionId: jobMeta?.sessionId || null,
|
|
1445
|
+
input: { type, tag: jobMeta?.tag || null, sessionId: jobMeta?.sessionId || null, agent: jobMeta?.agent || null },
|
|
1381
1446
|
context: notifyContext,
|
|
1382
1447
|
meta: jobMeta,
|
|
1383
1448
|
resultType: 'agent_task_result',
|
|
@@ -1406,8 +1471,10 @@ export function createStandaloneAgent({ cfgMod, reg, mgr, dataDir, cwd: defaultC
|
|
|
1406
1471
|
// installs its progress watchdog, so guard prep with an internal env-
|
|
1407
1472
|
// backed cap rather than exposing per-call timeout knobs on the agent
|
|
1408
1473
|
// tool surface.
|
|
1409
|
-
const prepDeadlineMs =
|
|
1474
|
+
const prepDeadlineMs = nonNegativeInt(args.spawnPrepTimeoutMs ?? args.prepTimeoutMs)
|
|
1475
|
+
?? DEFAULT_SPAWN_PREP_TIMEOUT_MS;
|
|
1410
1476
|
let prepared;
|
|
1477
|
+
const prepState = { timedOut: false };
|
|
1411
1478
|
if (prepDeadlineMs > 0) {
|
|
1412
1479
|
let prepTimer = null;
|
|
1413
1480
|
let timedOut = false;
|
|
@@ -1415,13 +1482,14 @@ export function createStandaloneAgent({ cfgMod, reg, mgr, dataDir, cwd: defaultC
|
|
|
1415
1482
|
// prepareSpawn promise may still resolve later with a fully-built
|
|
1416
1483
|
// session/tag/route — attach a cleanup so the late-arriving prepared is
|
|
1417
1484
|
// torn down, otherwise the orphaned tag would collide on re-spawn.
|
|
1418
|
-
const prepPromise = prepareSpawn(args, callerCwd, context);
|
|
1485
|
+
const prepPromise = prepareSpawn(args, callerCwd, context, prepState);
|
|
1419
1486
|
prepPromise.then((late) => {
|
|
1420
1487
|
if (timedOut) closePreparedSpawn(late, 'agent-spawn-prep-timeout');
|
|
1421
1488
|
}, () => {});
|
|
1422
1489
|
const timeout = new Promise((_resolve, reject) => {
|
|
1423
1490
|
prepTimer = setTimeout(() => {
|
|
1424
1491
|
timedOut = true;
|
|
1492
|
+
prepState.timedOut = true;
|
|
1425
1493
|
reject(new Error(`agent spawn prep timed out (${prepDeadlineMs}ms) before model request`));
|
|
1426
1494
|
}, prepDeadlineMs);
|
|
1427
1495
|
prepTimer.unref?.();
|
|
@@ -1432,7 +1500,7 @@ export function createStandaloneAgent({ cfgMod, reg, mgr, dataDir, cwd: defaultC
|
|
|
1432
1500
|
if (prepTimer) clearTimeout(prepTimer);
|
|
1433
1501
|
}
|
|
1434
1502
|
} else {
|
|
1435
|
-
prepared = await prepareSpawn(args, callerCwd, context);
|
|
1503
|
+
prepared = await prepareSpawn(args, callerCwd, context, prepState);
|
|
1436
1504
|
}
|
|
1437
1505
|
mergeJobMeta(job, preparedSpawnMeta(prepared, extras));
|
|
1438
1506
|
upsertWorkerSessionDeferred(prepared.session, prepared.tag, {
|
|
@@ -1483,17 +1551,20 @@ export function createStandaloneAgent({ cfgMod, reg, mgr, dataDir, cwd: defaultC
|
|
|
1483
1551
|
};
|
|
1484
1552
|
}
|
|
1485
1553
|
|
|
1486
|
-
async function prepareSpawn(args, callerCwd = null, context = {}) {
|
|
1554
|
+
async function prepareSpawn(args, callerCwd = null, context = {}, prepState = null) {
|
|
1487
1555
|
refreshTagsFromSessions({ context });
|
|
1488
1556
|
const config = cfgMod.loadConfig();
|
|
1489
|
-
const
|
|
1490
|
-
if (!
|
|
1491
|
-
const agentPermission = readAgentFrontmatterPermission(
|
|
1492
|
-
const
|
|
1557
|
+
const agent = normalizeAgentName(args.agent);
|
|
1558
|
+
if (!agent) throw new Error('agent spawn: agent is required');
|
|
1559
|
+
const agentPermission = readAgentFrontmatterPermission(agent, dataDir);
|
|
1560
|
+
const agentPerm = normalizeAgentPermission(agentPermission) || null;
|
|
1493
1561
|
const { presetName, preset } = resolvePreset(config, args);
|
|
1494
1562
|
await ensureProvider(config, preset.provider);
|
|
1563
|
+
if (prepState?.timedOut) {
|
|
1564
|
+
throw new Error('agent spawn prep timed out before session bind');
|
|
1565
|
+
}
|
|
1495
1566
|
|
|
1496
|
-
const tag = clean(args.tag) || nextTag(
|
|
1567
|
+
const tag = clean(args.tag) || nextTag(agent, context);
|
|
1497
1568
|
// Any resolved same-tag binding in this terminal (live or lingering trace)
|
|
1498
1569
|
// blocks a fresh spawn. execute() routes live reuse before prepareSpawn.
|
|
1499
1570
|
if (resolveTag(tag, context, { scanSessions: wantsSessionScan(args) })) {
|
|
@@ -1502,25 +1573,28 @@ export function createStandaloneAgent({ cfgMod, reg, mgr, dataDir, cwd: defaultC
|
|
|
1502
1573
|
const baseCwd = resolve(callerCwd || defaultCwd || process.cwd());
|
|
1503
1574
|
const workerCwd = clean(args.cwd) ? resolve(baseCwd, args.cwd) : baseCwd;
|
|
1504
1575
|
const prompt = withCwdHeader(await resolvePrompt(args, workerCwd), workerCwd);
|
|
1576
|
+
if (prepState?.timedOut) {
|
|
1577
|
+
throw new Error('agent spawn prep timed out before session bind');
|
|
1578
|
+
}
|
|
1505
1579
|
const runtimeSpec = cfgMod.resolveRuntimeSpec(preset, { lane: 'agent', agentId: tag });
|
|
1506
1580
|
const maxLoopIterations = positiveInt(args.maxLoopIterations) || null;
|
|
1507
|
-
const watchdogPolicy = resolveAgentWatchdogPolicy(
|
|
1581
|
+
const watchdogPolicy = resolveAgentWatchdogPolicy(agent);
|
|
1508
1582
|
const { session, effectiveCwd } = prepareAgentSession({
|
|
1509
|
-
|
|
1583
|
+
agent,
|
|
1510
1584
|
presetName,
|
|
1511
1585
|
preset,
|
|
1512
1586
|
runtimeSpec,
|
|
1513
1587
|
owner: AGENT_OWNER,
|
|
1514
1588
|
cwd: workerCwd,
|
|
1515
1589
|
sourceType: 'cli',
|
|
1516
|
-
sourceName:
|
|
1590
|
+
sourceName: agent,
|
|
1517
1591
|
parentSessionId: clean(context?.callerSessionId || context?.sessionId) || null,
|
|
1518
1592
|
ownerSessionId: clean(context?.callerSessionId || context?.sessionId) || null,
|
|
1519
1593
|
clientHostPid: terminalPidForContext(context) || null,
|
|
1520
1594
|
agentTag: tag,
|
|
1521
1595
|
taskType: clean(args.taskType) || clean(args.typeHint) || undefined,
|
|
1522
1596
|
maxLoopIterations: maxLoopIterations || undefined,
|
|
1523
|
-
permission:
|
|
1597
|
+
permission: agentPerm || undefined,
|
|
1524
1598
|
cacheKeyOverride: args.cacheKey || undefined,
|
|
1525
1599
|
});
|
|
1526
1600
|
// Lead sessions write a gateway-session route when created; agent sessions
|
|
@@ -1528,7 +1602,7 @@ export function createStandaloneAgent({ cfgMod, reg, mgr, dataDir, cwd: defaultC
|
|
|
1528
1602
|
// or the vendored L1/L2 statusline cannot resolve the agent route/model.
|
|
1529
1603
|
writeAgentStatuslineRoute(session.id, preset);
|
|
1530
1604
|
bindTag(tag, session, {
|
|
1531
|
-
|
|
1605
|
+
agent,
|
|
1532
1606
|
preset: presetKey(preset) || presetName,
|
|
1533
1607
|
provider: preset.provider,
|
|
1534
1608
|
model: preset.model,
|
|
@@ -1542,7 +1616,7 @@ export function createStandaloneAgent({ cfgMod, reg, mgr, dataDir, cwd: defaultC
|
|
|
1542
1616
|
args,
|
|
1543
1617
|
tag,
|
|
1544
1618
|
session,
|
|
1545
|
-
|
|
1619
|
+
agent,
|
|
1546
1620
|
preset,
|
|
1547
1621
|
presetName,
|
|
1548
1622
|
workerCwd: effectiveCwd || workerCwd,
|
|
@@ -1553,11 +1627,13 @@ export function createStandaloneAgent({ cfgMod, reg, mgr, dataDir, cwd: defaultC
|
|
|
1553
1627
|
}
|
|
1554
1628
|
|
|
1555
1629
|
async function runSpawn(prepared, notifyContext = null, job = null) {
|
|
1556
|
-
const { args, tag, session,
|
|
1630
|
+
const { args, tag, session, agent, preset, presetName, workerCwd, prompt, watchdogPolicy } = prepared;
|
|
1557
1631
|
const watchdog = startProgressIdleWatchdog(session.id, watchdogPolicy);
|
|
1558
1632
|
let finalStatus = 'idle';
|
|
1633
|
+
// SubagentStart: a worker session is about to run its first turn.
|
|
1634
|
+
emitSubagentEvent('start', agent, { session_id: session.id, tag });
|
|
1559
1635
|
upsertWorkerSessionDeferred(session, tag, {
|
|
1560
|
-
|
|
1636
|
+
agent,
|
|
1561
1637
|
preset: presetKey(preset) || presetName,
|
|
1562
1638
|
provider: preset.provider,
|
|
1563
1639
|
model: preset.model,
|
|
@@ -1567,17 +1643,26 @@ export function createStandaloneAgent({ cfgMod, reg, mgr, dataDir, cwd: defaultC
|
|
|
1567
1643
|
stage: 'running',
|
|
1568
1644
|
});
|
|
1569
1645
|
try {
|
|
1570
|
-
const completionValue = (result) =>
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1646
|
+
const completionValue = (result) => {
|
|
1647
|
+
// Promote an abnormal finish (iteration cap, truncation, or a public
|
|
1648
|
+
// agent's empty terminal turn) to an explicit error, so the Lead
|
|
1649
|
+
// receives it as a failure with an accurate reason instead of a silent
|
|
1650
|
+
// `completed` empty result. Keyed off loop.mjs terminationReason;
|
|
1651
|
+
// hidden agents finishing normally-empty are left untagged (benign).
|
|
1652
|
+
const abnormalError = abnormalEmptyFinishError(result, agent);
|
|
1653
|
+
return {
|
|
1654
|
+
tag,
|
|
1655
|
+
sessionId: session.id,
|
|
1656
|
+
agent,
|
|
1657
|
+
preset: presetKey(preset) || presetName,
|
|
1658
|
+
provider: preset.provider,
|
|
1659
|
+
model: preset.model,
|
|
1660
|
+
effort: preset.effort || null,
|
|
1661
|
+
fast: preset.fast === true,
|
|
1662
|
+
content: result?.content || '',
|
|
1663
|
+
...(abnormalError ? { error: abnormalError } : {}),
|
|
1664
|
+
};
|
|
1665
|
+
};
|
|
1581
1666
|
const result = await mgr.askSession(session.id, prompt, args.context || null, null, workerCwd, null, {
|
|
1582
1667
|
notifyFn: workerNotifyFn(session.id, notifyContext || {}),
|
|
1583
1668
|
...(job ? {
|
|
@@ -1591,11 +1676,21 @@ export function createStandaloneAgent({ cfgMod, reg, mgr, dataDir, cwd: defaultC
|
|
|
1591
1676
|
// reconcile remains a backup for the error/no-terminal-result path.
|
|
1592
1677
|
if (job?.taskId) {
|
|
1593
1678
|
try {
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1679
|
+
// An empty/abnormal finish is a failure, not a completion:
|
|
1680
|
+
// reconcile as `failed` with the accurate error so the Lead
|
|
1681
|
+
// card renders `error: …` instead of a header-only empty card.
|
|
1682
|
+
reconcileBackgroundTask(job.taskId, value.error
|
|
1683
|
+
? {
|
|
1684
|
+
status: 'failed',
|
|
1685
|
+
result: value,
|
|
1686
|
+
error: value.error,
|
|
1687
|
+
terminalReason: 'agent-empty-final',
|
|
1688
|
+
}
|
|
1689
|
+
: {
|
|
1690
|
+
status: 'completed',
|
|
1691
|
+
result: value,
|
|
1692
|
+
terminalReason: 'agent-terminal-result',
|
|
1693
|
+
});
|
|
1599
1694
|
} catch {}
|
|
1600
1695
|
}
|
|
1601
1696
|
},
|
|
@@ -1604,14 +1699,40 @@ export function createStandaloneAgent({ cfgMod, reg, mgr, dataDir, cwd: defaultC
|
|
|
1604
1699
|
// The early preview no longer promises body suppression, so the canonical
|
|
1605
1700
|
// notifyTaskCompletion is left to fire exactly once with output via the
|
|
1606
1701
|
// resolve/reconcile/finally path.
|
|
1607
|
-
|
|
1702
|
+
const finalValue = completionValue(result);
|
|
1703
|
+
// Non-job return path (or job path where the terminal-result reconcile
|
|
1704
|
+
// already ran): if the finish was abnormal-empty, surface it as a thrown
|
|
1705
|
+
// error so finalStatus becomes 'error' and the caller's error path (and
|
|
1706
|
+
// the finally reconcile below, as `failed`) render the accurate reason.
|
|
1707
|
+
if (finalValue.error) {
|
|
1708
|
+
finalStatus = 'error';
|
|
1709
|
+
if (job) job._terminalResultValue = finalValue;
|
|
1710
|
+
throw new Error(finalValue.error);
|
|
1711
|
+
}
|
|
1712
|
+
return finalValue;
|
|
1608
1713
|
} catch (error) {
|
|
1609
1714
|
finalStatus = 'error';
|
|
1715
|
+
// Part C: a mid-stream stall (StreamStalledError / ESTREAMSTALL) throws
|
|
1716
|
+
// here WITHOUT a terminal result, so the finally reconcile below (gated on
|
|
1717
|
+
// _terminalResultValue) would be skipped and only the outer task-reject
|
|
1718
|
+
// path would notify. Belt-and-suspenders: reconcile this job to `failed`
|
|
1719
|
+
// now so the owner (Lead) always gets a failure notification instead of a
|
|
1720
|
+
// task stranded in `running`. Idempotent — completeBackgroundTask no-ops
|
|
1721
|
+
// once terminal, so the outer reject path can't double-notify.
|
|
1722
|
+
if (job?.taskId && job._terminalResultValue === undefined) {
|
|
1723
|
+
try {
|
|
1724
|
+
reconcileBackgroundTask(job.taskId, {
|
|
1725
|
+
status: 'failed',
|
|
1726
|
+
error,
|
|
1727
|
+
terminalReason: 'agent-stream-stalled',
|
|
1728
|
+
});
|
|
1729
|
+
} catch {}
|
|
1730
|
+
}
|
|
1610
1731
|
throw error;
|
|
1611
1732
|
} finally {
|
|
1612
1733
|
watchdog?.stop?.();
|
|
1613
1734
|
upsertWorkerSessionDeferred(session, tag, {
|
|
1614
|
-
|
|
1735
|
+
agent,
|
|
1615
1736
|
preset: presetKey(preset) || presetName,
|
|
1616
1737
|
provider: preset.provider,
|
|
1617
1738
|
model: preset.model,
|
|
@@ -1631,11 +1752,16 @@ export function createStandaloneAgent({ cfgMod, reg, mgr, dataDir, cwd: defaultC
|
|
|
1631
1752
|
reconcileBackgroundTask(job.taskId, {
|
|
1632
1753
|
status: finalStatus === 'error' ? 'failed' : 'completed',
|
|
1633
1754
|
result: job._terminalResultValue,
|
|
1755
|
+
...(finalStatus === 'error' && job._terminalResultValue?.error
|
|
1756
|
+
? { error: job._terminalResultValue.error }
|
|
1757
|
+
: {}),
|
|
1634
1758
|
terminalReason: 'agent-finally-reconcile',
|
|
1635
1759
|
});
|
|
1636
1760
|
} catch {}
|
|
1637
1761
|
}
|
|
1638
1762
|
scheduleReap(session.id);
|
|
1763
|
+
// SubagentStop: worker finished (terminal), regardless of outcome.
|
|
1764
|
+
emitSubagentEvent('stop', agent, { session_id: session.id, tag, status: finalStatus });
|
|
1639
1765
|
}
|
|
1640
1766
|
}
|
|
1641
1767
|
|
|
@@ -1658,20 +1784,27 @@ export function createStandaloneAgent({ cfgMod, reg, mgr, dataDir, cwd: defaultC
|
|
|
1658
1784
|
|
|
1659
1785
|
async function runSend(prepared, notifyContext = null, job = null) {
|
|
1660
1786
|
const { args, session, sessionId, prompt } = prepared;
|
|
1661
|
-
const
|
|
1662
|
-
const watchdog = startProgressIdleWatchdog(sessionId, resolveAgentWatchdogPolicy(
|
|
1787
|
+
const sendAgent = session.agent || normalizeAgentName(args.agent);
|
|
1788
|
+
const watchdog = startProgressIdleWatchdog(sessionId, resolveAgentWatchdogPolicy(sendAgent));
|
|
1663
1789
|
const tag = tagForSession(sessionId);
|
|
1664
1790
|
let finalStatus = 'idle';
|
|
1665
1791
|
upsertWorkerSessionDeferred(session, tag, { status: 'running', stage: 'running' });
|
|
1666
1792
|
try {
|
|
1667
|
-
const completionValue = (result) =>
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1793
|
+
const completionValue = (result) => {
|
|
1794
|
+
// Same abnormal-empty → error promotion as runSpawn: a reused/`send`
|
|
1795
|
+
// worker that hits the cap, truncates, or finishes empty must surface
|
|
1796
|
+
// as a failure with an accurate reason, not a silent completed empty.
|
|
1797
|
+
const abnormalError = abnormalEmptyFinishError(result, session.agent || sendAgent);
|
|
1798
|
+
return {
|
|
1799
|
+
tag,
|
|
1800
|
+
sessionId,
|
|
1801
|
+
agent: session.agent || null,
|
|
1802
|
+
provider: session.provider,
|
|
1803
|
+
model: session.model,
|
|
1804
|
+
content: result?.content || '',
|
|
1805
|
+
...(abnormalError ? { error: abnormalError } : {}),
|
|
1806
|
+
};
|
|
1807
|
+
};
|
|
1675
1808
|
const result = await mgr.askSession(sessionId, prompt, args.context || null, null, session.cwd || defaultCwd, null, {
|
|
1676
1809
|
notifyFn: workerNotifyFn(sessionId, notifyContext || {}),
|
|
1677
1810
|
...(job ? {
|
|
@@ -1683,11 +1816,18 @@ export function createStandaloneAgent({ cfgMod, reg, mgr, dataDir, cwd: defaultC
|
|
|
1683
1816
|
// post-result save can't strand the task in `running`. Idempotent.
|
|
1684
1817
|
if (job?.taskId) {
|
|
1685
1818
|
try {
|
|
1686
|
-
reconcileBackgroundTask(job.taskId,
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1819
|
+
reconcileBackgroundTask(job.taskId, value.error
|
|
1820
|
+
? {
|
|
1821
|
+
status: 'failed',
|
|
1822
|
+
result: value,
|
|
1823
|
+
error: value.error,
|
|
1824
|
+
terminalReason: 'agent-empty-final',
|
|
1825
|
+
}
|
|
1826
|
+
: {
|
|
1827
|
+
status: 'completed',
|
|
1828
|
+
result: value,
|
|
1829
|
+
terminalReason: 'agent-terminal-result',
|
|
1830
|
+
});
|
|
1691
1831
|
} catch {}
|
|
1692
1832
|
}
|
|
1693
1833
|
},
|
|
@@ -1695,9 +1835,27 @@ export function createStandaloneAgent({ cfgMod, reg, mgr, dataDir, cwd: defaultC
|
|
|
1695
1835
|
});
|
|
1696
1836
|
// Early preview no longer suppresses the canonical body notification;
|
|
1697
1837
|
// notifyTaskCompletion fires once with output via resolve/reconcile.
|
|
1698
|
-
|
|
1838
|
+
const finalValue = completionValue(result);
|
|
1839
|
+
if (finalValue.error) {
|
|
1840
|
+
finalStatus = 'error';
|
|
1841
|
+
if (job) job._terminalResultValue = finalValue;
|
|
1842
|
+
throw new Error(finalValue.error);
|
|
1843
|
+
}
|
|
1844
|
+
return finalValue;
|
|
1699
1845
|
} catch (error) {
|
|
1700
1846
|
finalStatus = 'error';
|
|
1847
|
+
// Part C (send path mirror): a mid-stream stall throws with no terminal
|
|
1848
|
+
// result — reconcile to `failed` so the owner is notified rather than the
|
|
1849
|
+
// task hanging in `running`. Idempotent (see runSpawn note).
|
|
1850
|
+
if (job?.taskId && job._terminalResultValue === undefined) {
|
|
1851
|
+
try {
|
|
1852
|
+
reconcileBackgroundTask(job.taskId, {
|
|
1853
|
+
status: 'failed',
|
|
1854
|
+
error,
|
|
1855
|
+
terminalReason: 'agent-stream-stalled',
|
|
1856
|
+
});
|
|
1857
|
+
} catch {}
|
|
1858
|
+
}
|
|
1701
1859
|
throw error;
|
|
1702
1860
|
} finally {
|
|
1703
1861
|
watchdog?.stop?.();
|
|
@@ -1713,6 +1871,9 @@ export function createStandaloneAgent({ cfgMod, reg, mgr, dataDir, cwd: defaultC
|
|
|
1713
1871
|
reconcileBackgroundTask(job.taskId, {
|
|
1714
1872
|
status: finalStatus === 'error' ? 'failed' : 'completed',
|
|
1715
1873
|
result: job._terminalResultValue,
|
|
1874
|
+
...(finalStatus === 'error' && job._terminalResultValue?.error
|
|
1875
|
+
? { error: job._terminalResultValue.error }
|
|
1876
|
+
: {}),
|
|
1716
1877
|
terminalReason: 'agent-finally-reconcile',
|
|
1717
1878
|
});
|
|
1718
1879
|
} catch {}
|
|
@@ -1737,14 +1898,14 @@ export function createStandaloneAgent({ cfgMod, reg, mgr, dataDir, cwd: defaultC
|
|
|
1737
1898
|
...extras,
|
|
1738
1899
|
tag: tagForSession(prepared.sessionId),
|
|
1739
1900
|
sessionId: prepared.sessionId,
|
|
1740
|
-
|
|
1901
|
+
agent: prepared.session.agent || null,
|
|
1741
1902
|
queueDepth,
|
|
1742
1903
|
});
|
|
1743
1904
|
}
|
|
1744
1905
|
const job = startJob('send', {
|
|
1745
1906
|
tag: tagForSession(prepared.sessionId),
|
|
1746
1907
|
sessionId: prepared.sessionId,
|
|
1747
|
-
|
|
1908
|
+
agent: prepared.session.agent || null,
|
|
1748
1909
|
provider: prepared.session.provider || null,
|
|
1749
1910
|
model: prepared.session.model || null,
|
|
1750
1911
|
preset: prepared.session.presetName || null,
|
|
@@ -1770,7 +1931,7 @@ export function createStandaloneAgent({ cfgMod, reg, mgr, dataDir, cwd: defaultC
|
|
|
1770
1931
|
}
|
|
1771
1932
|
const sessionId = resolveTag(target, scopedContext, { scanSessions: wantsSessionScan(args) });
|
|
1772
1933
|
if (!sessionId) {
|
|
1773
|
-
if (!target.startsWith('sess_') &&
|
|
1934
|
+
if (!target.startsWith('sess_') && tagAgents.has(target)) {
|
|
1774
1935
|
forgetTag(target);
|
|
1775
1936
|
if (task?.taskId) cancelBackgroundTask(task.taskId, 'cancelled by agent close');
|
|
1776
1937
|
return { closed: true, forgotten: true, tag: target, sessionId: null, task_id: task?.taskId || null };
|
|
@@ -1829,7 +1990,7 @@ export function createStandaloneAgent({ cfgMod, reg, mgr, dataDir, cwd: defaultC
|
|
|
1829
1990
|
const value = clean(tag);
|
|
1830
1991
|
if (!value || value.startsWith('sess_')) return false;
|
|
1831
1992
|
if (resolveTag(value, context, { excludeTerminalTraces: true })) return false; // live -> reuse, not trace
|
|
1832
|
-
if (
|
|
1993
|
+
if (tagAgents.has(value)) return true;
|
|
1833
1994
|
return readWorkerRows(context).some((row) => clean(row.tag) === value);
|
|
1834
1995
|
}
|
|
1835
1996
|
|
|
@@ -1850,8 +2011,32 @@ export function createStandaloneAgent({ cfgMod, reg, mgr, dataDir, cwd: defaultC
|
|
|
1850
2011
|
if (type === 'cancel') return renderResult(close(args, scopedContext));
|
|
1851
2012
|
if (type === 'close') return renderResult(close(args, scopedContext));
|
|
1852
2013
|
if (type === 'send') {
|
|
1853
|
-
|
|
1854
|
-
|
|
2014
|
+
try {
|
|
2015
|
+
const prepared = await prepareSend(args, scopedContext);
|
|
2016
|
+
return dispatchToExistingSession(prepared, notifyContext);
|
|
2017
|
+
} catch (err) {
|
|
2018
|
+
// Reaped/dead-tag fallback: with the 5m terminal-reap window a
|
|
2019
|
+
// same-scope follow-up often lands after the session is gone.
|
|
2020
|
+
// Instead of bouncing an error back to Lead (who would just
|
|
2021
|
+
// re-issue the same content as a spawn), respawn a FRESH session
|
|
2022
|
+
// under the same tag with the message as its brief. `respawned:
|
|
2023
|
+
// true` in the result tells Lead the worker has no prior session
|
|
2024
|
+
// context — re-supply anchors on the next send if needed.
|
|
2025
|
+
// Only tag-addressed sends fall back; explicit sessionId sends
|
|
2026
|
+
// keep erroring (caller pinned a specific session on purpose).
|
|
2027
|
+
const fallbackTag = clean(args.tag);
|
|
2028
|
+
const isDeadTarget = /not found|is closed/i.test(String(err?.message || ''));
|
|
2029
|
+
if (!fallbackTag || fallbackTag.startsWith('sess_') || !isDeadTarget) throw err;
|
|
2030
|
+
const prompt = clean(args.message || args.prompt);
|
|
2031
|
+
if (!prompt) throw err;
|
|
2032
|
+
// Clear the terminal trace so the fresh spawn isn't rejected by
|
|
2033
|
+
// the lingering-trace guard, then run the normal deferred spawn.
|
|
2034
|
+
try { forgetTag(fallbackTag); } catch {}
|
|
2035
|
+
try { removeWorkerRow({ tag: fallbackTag }); } catch {}
|
|
2036
|
+
const spawnArgs = { ...args, type: 'spawn', tag: fallbackTag, prompt, message: undefined };
|
|
2037
|
+
const job = startDeferredSpawnJob(spawnArgs, callerCwd, context, notifyContext, { respawned: true });
|
|
2038
|
+
return renderResult(renderJob(job, false));
|
|
2039
|
+
}
|
|
1855
2040
|
}
|
|
1856
2041
|
if (type === 'spawn') {
|
|
1857
2042
|
// Explicit-tag spawn priority (auto nextTag always creates a fresh session):
|
|
@@ -1887,7 +2072,7 @@ export function createStandaloneAgent({ cfgMod, reg, mgr, dataDir, cwd: defaultC
|
|
|
1887
2072
|
}
|
|
1888
2073
|
throw new Error(`agent: unknown type "${type}"`);
|
|
1889
2074
|
} catch (err) {
|
|
1890
|
-
return
|
|
2075
|
+
return errorLine(err, { surface: 'agent' });
|
|
1891
2076
|
}
|
|
1892
2077
|
}
|
|
1893
2078
|
|