mixdog 0.9.151 → 0.9.153
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/LICENSES/browser-import-NOTICE.txt +11 -0
- package/NOTICE.md +11 -0
- package/README.md +108 -68
- package/package.json +17 -4
- package/scripts/computer-task-cost-report.mjs +144 -0
- package/scripts/run-office-live-tests.mjs +32 -0
- package/scripts/tool-stress.mjs +0 -2
- package/src/agents/front-worker/AGENT.md +13 -0
- package/src/agents/front-worker/agent.json +6 -0
- package/src/agents/heavy-worker/AGENT.md +10 -11
- package/src/agents/heavy-worker/agent.json +1 -1
- package/src/agents/maintainer/AGENT.md +0 -2
- package/src/agents/reviewer/AGENT.md +9 -13
- package/src/agents/reviewer/agent.json +1 -1
- package/src/agents/security/AGENT.md +14 -0
- package/src/agents/security/agent.json +6 -0
- package/src/agents/worker/AGENT.md +8 -8
- package/src/agents/worker/agent.json +1 -1
- package/src/agents/writer/AGENT.md +14 -0
- package/src/agents/writer/agent.json +6 -0
- package/src/defaults/skills/setup/SKILL.md +18 -27
- package/src/headless-exec.mjs +3 -10
- package/src/headless-exec.test.mjs +2 -7
- package/src/rules/agent/00-common.md +4 -7
- package/src/rules/lead/01-general.md +0 -3
- package/src/rules/shared/10-tool-workflow.md +10 -2
- package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch.mjs +183 -26
- package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch.test.mjs +378 -0
- package/src/runtime/agent/orchestrator/agent-runtime/session-builder.mjs +4 -0
- package/src/runtime/agent/orchestrator/cache-break-trace.mjs +109 -0
- package/src/runtime/agent/orchestrator/cache-break-trace.test.mjs +115 -0
- package/src/runtime/agent/orchestrator/config.mjs +13 -68
- package/src/runtime/agent/orchestrator/context/collect-skills.test.mjs +153 -0
- package/src/runtime/agent/orchestrator/context/collect.mjs +49 -45
- package/src/runtime/agent/orchestrator/mcp/client.mjs +242 -15
- package/src/runtime/agent/orchestrator/mcp/features.test.mjs +66 -0
- package/src/runtime/agent/orchestrator/mcp/security.test.mjs +36 -20
- package/src/runtime/agent/orchestrator/providers/admission-scheduler-metrics.test.mjs +47 -0
- package/src/runtime/agent/orchestrator/providers/admission-scheduler.mjs +16 -3
- package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +54 -33
- package/src/runtime/agent/orchestrator/providers/anthropic.mjs +1 -0
- package/src/runtime/agent/orchestrator/providers/antigravity-oauth.mjs +7 -0
- package/src/runtime/agent/orchestrator/providers/gemini-schema.mjs +7 -0
- package/src/runtime/agent/orchestrator/providers/gemini-stream.mjs +3 -8
- package/src/runtime/agent/orchestrator/providers/gemini.mjs +7 -0
- package/src/runtime/agent/orchestrator/providers/lib/anthropic-request-utils.mjs +19 -3
- package/src/runtime/agent/orchestrator/providers/lib/provider-replay.mjs +50 -0
- package/src/runtime/agent/orchestrator/providers/lib/sse-framing.mjs +93 -0
- package/src/runtime/agent/orchestrator/providers/media-normalization.mjs +30 -9
- package/src/runtime/agent/orchestrator/providers/media-parity.test.mjs +59 -0
- package/src/runtime/agent/orchestrator/providers/model-list-sanitize.mjs +3 -3
- package/src/runtime/agent/orchestrator/providers/openai-codex-identity.test.mjs +71 -0
- package/src/runtime/agent/orchestrator/providers/openai-codex-metadata.mjs +18 -26
- package/src/runtime/agent/orchestrator/providers/openai-compat-presets.mjs +8 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +7 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-wire.mjs +25 -2
- package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +35 -4
- package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +47 -2
- package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +103 -12
- package/src/runtime/agent/orchestrator/providers/openai-responses-payload.mjs +13 -0
- package/src/runtime/agent/orchestrator/providers/openai-turn-state.mjs +68 -0
- package/src/runtime/agent/orchestrator/providers/openai-ws-delta-parity.test.mjs +122 -6
- package/src/runtime/agent/orchestrator/providers/openai-ws-delta.mjs +36 -11
- package/src/runtime/agent/orchestrator/providers/openai-ws-pool.mjs +144 -93
- package/src/runtime/agent/orchestrator/providers/openai-ws-stream.mjs +18 -13
- package/src/runtime/agent/orchestrator/providers/provider-replay.test.mjs +288 -0
- package/src/runtime/agent/orchestrator/providers/sse-framing.test.mjs +719 -0
- package/src/runtime/agent/orchestrator/providers/stream-json-pool.mjs +399 -39
- package/src/runtime/agent/orchestrator/providers/stream-json-worker.mjs +26 -0
- package/src/runtime/agent/orchestrator/session/agent-loop.mjs +86 -7
- package/src/runtime/agent/orchestrator/session/agent-loop.test.mjs +222 -0
- package/src/runtime/agent/orchestrator/session/browser-snapshot-supersession.mjs +61 -0
- package/src/runtime/agent/orchestrator/session/browser-snapshot-supersession.test.mjs +83 -0
- package/src/runtime/agent/orchestrator/session/cache/read-cache.mjs +13 -0
- package/src/runtime/agent/orchestrator/session/compact/runner.mjs +26 -2
- package/src/runtime/agent/orchestrator/session/compact-policy.test.mjs +112 -0
- package/src/runtime/agent/orchestrator/session/compaction-read-reset.test.mjs +192 -0
- package/src/runtime/agent/orchestrator/session/context-usage-restart.test.mjs +72 -0
- package/src/runtime/agent/orchestrator/session/context-utils.mjs +100 -15
- package/src/runtime/agent/orchestrator/session/eager-dispatch.mjs +17 -1
- package/src/runtime/agent/orchestrator/session/eager-dispatch.test.mjs +74 -0
- package/src/runtime/agent/orchestrator/session/image-strip-recovery.mjs +12 -8
- package/src/runtime/agent/orchestrator/session/image-strip-recovery.test.mjs +20 -1
- package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +110 -2
- package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +3 -16
- package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.test.mjs +28 -0
- package/src/runtime/agent/orchestrator/session/loop/recall-fasttrack.mjs +2 -2
- package/src/runtime/agent/orchestrator/session/loop/steering.mjs +6 -0
- package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +15 -1
- package/src/runtime/agent/orchestrator/session/loop/tool-helpers.mjs +6 -0
- package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +111 -39
- package/src/runtime/agent/orchestrator/session/manager/ask-session.test.mjs +46 -0
- package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +30 -6
- package/src/runtime/agent/orchestrator/session/manager/message-sanitize.mjs +3 -0
- package/src/runtime/agent/orchestrator/session/manager/session-crud.mjs +27 -3
- package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +26 -54
- package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.test.mjs +63 -1
- package/src/runtime/agent/orchestrator/session/manager/status-telemetry.mjs +3 -0
- package/src/runtime/agent/orchestrator/session/manager/tool-resolution.mjs +4 -110
- package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint-journal.mjs +580 -0
- package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint-journal.test.mjs +593 -0
- package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint.mjs +119 -190
- package/src/runtime/agent/orchestrator/session/manager/turn-interruption.mjs +115 -0
- package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +43 -8
- package/src/runtime/agent/orchestrator/session/provider-prefix-guard.mjs +129 -12
- package/src/runtime/agent/orchestrator/session/send-with-recovery.mjs +9 -2
- package/src/runtime/agent/orchestrator/session/store/listing.mjs +8 -1
- package/src/runtime/agent/orchestrator/session/store/save-worker.mjs +15 -1
- package/src/runtime/agent/orchestrator/session/store-live-publication.test.mjs +83 -0
- package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +36 -2
- package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +157 -30
- package/src/runtime/agent/orchestrator/session/store-summary-reader.test.mjs +64 -0
- package/src/runtime/agent/orchestrator/session/store-summary-visibility.mjs +57 -0
- package/src/runtime/agent/orchestrator/session/store-summary-visibility.test.mjs +219 -0
- package/src/runtime/agent/orchestrator/session/store.mjs +26 -2
- package/src/runtime/agent/orchestrator/session/tool-batch.mjs +22 -0
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool-cwd-env.test.mjs +14 -0
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +19 -10
- package/src/runtime/agent/orchestrator/tools/builtin/find-search-budget.test.mjs +30 -14
- package/src/runtime/agent/orchestrator/tools/builtin/git-command-policy.mjs +15 -4
- package/src/runtime/agent/orchestrator/tools/builtin/git-command-tool.mjs +73 -15
- package/src/runtime/agent/orchestrator/tools/builtin/git-command-tool.test.mjs +76 -2
- package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +24 -683
- package/src/runtime/agent/orchestrator/tools/builtin/native-search-client.mjs +64 -72
- package/src/runtime/agent/orchestrator/tools/builtin/native-search-runner.mjs +2 -5
- package/src/runtime/agent/orchestrator/tools/builtin/native-search-transport.mjs +75 -0
- package/src/runtime/agent/orchestrator/tools/builtin/native-search-transport.test.mjs +24 -0
- package/src/runtime/agent/orchestrator/tools/builtin/read-special-files.mjs +6 -6
- package/src/runtime/agent/orchestrator/tools/code-graph/build.mjs +12 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/constants.mjs +4 -1
- package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +24 -5
- package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.test.mjs +44 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/graph-binary.mjs +12 -2
- package/src/runtime/agent/orchestrator/tools/code-graph/graph-model.mjs +35 -21
- package/src/runtime/agent/orchestrator/tools/code-graph/memory-cache.mjs +28 -13
- package/src/runtime/agent/orchestrator/tools/code-graph/memory-cache.test.mjs +54 -0
- package/src/runtime/agent/orchestrator/tools/graph-manifest.json +11 -11
- package/src/runtime/agent/orchestrator/tools/patch-manifest.json +11 -11
- package/src/runtime/agent/orchestrator/tools/progress-message.mjs +12 -0
- package/src/runtime/agent/orchestrator/tools/spawn-manifest.json +11 -11
- package/src/runtime/attachments/pdf-extract.mjs +3 -0
- package/src/runtime/attachments/pdfjs-runtime.mjs +13 -0
- package/src/runtime/bridge-clients.test.mjs +827 -0
- package/src/runtime/browser-bridge/action-schema.mjs +251 -0
- package/src/runtime/browser-bridge/client.mjs +152 -0
- package/src/runtime/browser-bridge/tool-defs.mjs +159 -0
- package/src/runtime/computer-bridge/action-schema.mjs +653 -0
- package/src/runtime/computer-bridge/client.mjs +231 -0
- package/src/runtime/computer-bridge/tool-defs.mjs +33 -0
- package/src/runtime/media/store.mjs +18 -8
- package/src/runtime/media/store.test.mjs +14 -0
- package/src/runtime/memory/index.mjs +1 -1
- package/src/runtime/memory/lib/transcript-ingest.mjs +8 -6
- package/src/runtime/memory/lib/transcript-ingest.test.mjs +52 -0
- package/src/runtime/office/assurance-benchmark.mjs +358 -0
- package/src/runtime/office/assurance.mjs +833 -0
- package/src/runtime/office/benchmark.mjs +496 -0
- package/src/runtime/office/capabilities.mjs +551 -0
- package/src/runtime/office/com-adapter.mjs +283 -0
- package/src/runtime/office/content-model.mjs +183 -0
- package/src/runtime/office/contract-benchmark.mjs +105 -0
- package/src/runtime/office/design-library.mjs +1562 -0
- package/src/runtime/office/design-system.mjs +1966 -0
- package/src/runtime/office/index.mjs +2413 -0
- package/src/runtime/office/journal.mjs +82 -0
- package/src/runtime/office/office-assurance.test.mjs +371 -0
- package/src/runtime/office/office-com-host.ps1 +3781 -0
- package/src/runtime/office/office-com-session-host.ps1 +855 -0
- package/src/runtime/office/office-live-runtime.test.mjs +819 -0
- package/src/runtime/office/office-runtime.test.mjs +2070 -0
- package/src/runtime/office/ooxml-validator.mjs +271 -0
- package/src/runtime/office/pagination.mjs +132 -0
- package/src/runtime/office/pdf-adapter.mjs +591 -0
- package/src/runtime/office/pdf-analysis.mjs +372 -0
- package/src/runtime/office/pdf-render.mjs +167 -0
- package/src/runtime/office/pdf-security.mjs +63 -0
- package/src/runtime/office/polish-benchmark.mjs +190 -0
- package/src/runtime/office/portable-ooxml.mjs +1867 -0
- package/src/runtime/office/quality-live-benchmark.mjs +486 -0
- package/src/runtime/office/quality-pipeline.mjs +136 -0
- package/src/runtime/office/tabular.mjs +302 -0
- package/src/runtime/office/templates/mixdog-executive.pptx +0 -0
- package/src/runtime/office/templates/mixdog-executive.pptx.mixdog.json +190 -0
- package/src/runtime/office/tool-defs.mjs +113 -0
- package/src/runtime/office/visual-diff.mjs +89 -0
- package/src/runtime/office/xlsx-assertions.mjs +172 -0
- package/src/runtime/office/xlsx-contract.mjs +147 -0
- package/src/runtime/shared/agent-route-config.mjs +9 -0
- package/src/runtime/shared/child-spawn-gate.mjs +17 -0
- package/src/runtime/shared/child-spawn-remote.mjs +69 -7
- package/src/runtime/shared/child-spawn-remote.test.mjs +124 -0
- package/src/runtime/shared/pristine-execution-contract.json +2 -1
- package/src/runtime/shared/provider-api-key.mjs +1 -0
- package/src/runtime/shared/session-runtime-health.mjs +91 -0
- package/src/runtime/shared/session-runtime-health.test.mjs +53 -0
- package/src/runtime/shared/skill-document.mjs +86 -0
- package/src/runtime/shared/skill-document.test.mjs +81 -0
- package/src/runtime/shared/tool-execution-contract.mjs +16 -0
- package/src/runtime/shared/tool-surface.mjs +27 -0
- package/src/runtime/shared/turn-snapshot-store.mjs +1 -0
- package/src/runtime/shared/turn-snapshot.mjs +32 -9
- package/src/runtime/shared/user-cwd.mjs +33 -14
- package/src/runtime/shared/user-cwd.test.mjs +43 -0
- package/src/session-runtime/agent-disable.test.mjs +20 -1
- package/src/session-runtime/context-status.mjs +9 -3
- package/src/session-runtime/cwd-plugins.mjs +26 -26
- package/src/session-runtime/cwd-plugins.test.mjs +75 -0
- package/src/session-runtime/cwd-tool-routing.test.mjs +5 -0
- package/src/session-runtime/deferred-tool-delta.mjs +104 -0
- package/src/session-runtime/deferred-tool-delta.test.mjs +40 -0
- package/src/session-runtime/global-extensions.mjs +26 -0
- package/src/session-runtime/global-extensions.test.mjs +28 -0
- package/src/session-runtime/global-settings-session-identity.test.mjs +76 -0
- package/src/session-runtime/goal-runtime.mjs +1141 -0
- package/src/session-runtime/goal-runtime.test.mjs +645 -0
- package/src/session-runtime/lifecycle-api.mjs +58 -11
- package/src/session-runtime/lifecycle-api.test.mjs +183 -0
- package/src/session-runtime/mcp-glue.mjs +49 -47
- package/src/session-runtime/plugin-mcp-standard.test.mjs +177 -0
- package/src/session-runtime/plugin-mcp.mjs +96 -22
- package/src/session-runtime/prewarm.mjs +1 -10
- package/src/session-runtime/provider-request-snapshot.mjs +33 -9
- package/src/session-runtime/provider-request-snapshot.test.mjs +27 -5
- package/src/session-runtime/resource-api-global.test.mjs +109 -0
- package/src/session-runtime/resource-api.mjs +168 -116
- package/src/session-runtime/runtime-core.mjs +208 -29
- package/src/session-runtime/runtime-tunables.mjs +6 -8
- package/src/session-runtime/session-lifecycle.mjs +34 -12
- package/src/session-runtime/session-title.mjs +2 -2
- package/src/session-runtime/session-turn-api.mjs +37 -25
- package/src/session-runtime/skills-api.mjs +79 -25
- package/src/session-runtime/skills-api.test.mjs +99 -0
- package/src/session-runtime/tool-catalog-data.mjs +1 -1
- package/src/session-runtime/tool-catalog-schema.mjs +0 -5
- package/src/session-runtime/tool-catalog.mjs +42 -112
- package/src/session-runtime/tool-defs.mjs +6 -1
- package/src/session-runtime/tool-policy-surface.test.mjs +93 -17
- package/src/session-runtime/tool-surface.mjs +30 -8
- package/src/session-runtime/workflow-agents-api.mjs +8 -25
- package/src/session-runtime/workflow.mjs +8 -6
- package/src/standalone/agent-dispatch-broker.mjs +5 -9
- package/src/standalone/agent-tool/helpers.mjs +2 -2
- package/src/standalone/agent-tool/job-views.mjs +3 -1
- package/src/standalone/agent-tool/notify.mjs +11 -9
- package/src/standalone/agent-tool/notify.test.mjs +37 -0
- package/src/standalone/agent-tool/render.mjs +3 -0
- package/src/standalone/agent-tool/spawn-flow.mjs +61 -16
- package/src/standalone/agent-tool/spawn-flow.test.mjs +41 -0
- package/src/standalone/agent-tool/tool-def.mjs +1 -1
- package/src/standalone/agent-tool/worker-index.mjs +123 -0
- package/src/standalone/agent-tool/worker-index.test.mjs +132 -0
- package/src/standalone/agent-tool/worker-rows.mjs +2 -1
- package/src/standalone/agent-tool.mjs +57 -10
- package/src/standalone/daemon.mjs +113 -15
- package/src/standalone/hook-bus/config.mjs +1 -0
- package/src/standalone/plugin-admin.mjs +17 -0
- package/src/standalone/plugin-admin.test.mjs +7 -0
- package/src/standalone/provider-admin.mjs +1 -0
- package/src/standalone/session-protocol.mjs +5 -0
- package/src/standalone/session-runtime-agent-control-client.mjs +133 -0
- package/src/standalone/session-runtime-agent-control-client.test.mjs +71 -0
- package/src/standalone/session-runtime-dispatch-cancel.test.mjs +139 -0
- package/src/standalone/session-runtime-host-factory.mjs +17 -0
- package/src/standalone/session-runtime-host-factory.test.mjs +31 -0
- package/src/standalone/session-runtime-host-health.test.mjs +5 -0
- package/src/standalone/session-runtime-host.mjs +709 -58
- package/src/standalone/session-runtime-inline-host.mjs +258 -0
- package/src/standalone/session-runtime-inline-host.test.mjs +385 -0
- package/src/standalone/session-runtime-provider-cooldown.mjs +78 -0
- package/src/standalone/session-runtime-provider-cooldown.test.mjs +80 -0
- package/src/standalone/session-runtime-shard-host.test.mjs +642 -0
- package/src/standalone/session-runtime-shard-router.mjs +149 -0
- package/src/standalone/session-runtime-shard-router.test.mjs +101 -0
- package/src/standalone/session-runtime-worker.mjs +302 -26
- package/src/standalone/session-service-agent-tree.test.mjs +984 -0
- package/src/standalone/session-service-goal-recovery.test.mjs +177 -0
- package/src/standalone/session-service.mjs +711 -11
- package/src/tui/app/app-view.jsx +1 -0
- package/src/tui/app/core-memory-picker.mjs +2 -0
- package/src/tui/app/extension-pickers.mjs +1 -1
- package/src/tui/app/maintenance-pickers.mjs +3 -3
- package/src/tui/app/model-options.mjs +2 -0
- package/src/tui/app/model-picker.mjs +14 -4
- package/src/tui/app/panel-handoff.test.mjs +311 -0
- package/src/tui/app/project-picker.mjs +1 -0
- package/src/tui/app/route-pickers.mjs +21 -6
- package/src/tui/app/settings-picker.mjs +26 -3
- package/src/tui/app/slash-commands.mjs +1 -0
- package/src/tui/app/slash-dispatch.mjs +73 -22
- package/src/tui/app/theme-effort-pickers.mjs +7 -2
- package/src/tui/components/Picker.jsx +10 -1
- package/src/tui/dist/index.mjs +187 -45
- package/src/tui/lib/voice-setup.mjs +4 -1
- package/src/tui/session/agent-message-source.test.mjs +37 -0
- package/src/tui/session/completion-card-restore.test.mjs +50 -0
- package/src/tui/session/context-state.mjs +3 -13
- package/src/tui/session/goal-continuation.mjs +141 -0
- package/src/tui/session/goal-continuation.test.mjs +203 -0
- package/src/tui/session/queue-helpers.mjs +5 -2
- package/src/tui/session/session-action-surface.test.mjs +190 -0
- package/src/tui/session/session-api-ext.mjs +48 -8
- package/src/tui/session/session-api.mjs +73 -1
- package/src/tui/session/session-flow.mjs +31 -1
- package/src/tui/session/turn.mjs +88 -15
- package/src/tui/session-local.mjs +39 -3
- package/src/vendor/statusline/src/gateway/route-meta.mjs +31 -0
- package/src/runtime/agent/orchestrator/tools/builtin/fuzzy-match.mjs +0 -301
|
@@ -61,3 +61,44 @@ test('agent job completion uses immutable caller owner and never invokes stale n
|
|
|
61
61
|
assert.match(deliveries.at(-1).text, /owner handoff/);
|
|
62
62
|
cleanupBackgroundTasks({ surface: 'agent', force: true });
|
|
63
63
|
});
|
|
64
|
+
|
|
65
|
+
test('a user-cancelled Agent turn settles the worker row as cancelled, not error', async () => {
|
|
66
|
+
const updates = [];
|
|
67
|
+
const flow = createSpawnFlow({
|
|
68
|
+
mgr: {
|
|
69
|
+
getSession: () => ({ messages: [] }),
|
|
70
|
+
},
|
|
71
|
+
upsertWorkerSessionDeferred(_session, _tag, patch) {
|
|
72
|
+
updates.push(patch);
|
|
73
|
+
},
|
|
74
|
+
emitSubagentEvent() {},
|
|
75
|
+
scheduleReap() {},
|
|
76
|
+
createTurnReviewCollector: () => ({
|
|
77
|
+
onToolResult() {},
|
|
78
|
+
complete() {},
|
|
79
|
+
}),
|
|
80
|
+
sessionSurface: {
|
|
81
|
+
canonical: true,
|
|
82
|
+
canRun: () => true,
|
|
83
|
+
async runTurn() {
|
|
84
|
+
throw new Error('cancelled by agent close');
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
});
|
|
88
|
+
const job = { taskId: 'task-agent-cancelled', status: 'cancelled' };
|
|
89
|
+
|
|
90
|
+
await assert.rejects(flow.runSpawn({
|
|
91
|
+
args: {},
|
|
92
|
+
tag: 'cancelled-worker',
|
|
93
|
+
session: { id: 'sess_cancelled_worker' },
|
|
94
|
+
agent: 'worker',
|
|
95
|
+
preset: { provider: 'test-provider', model: 'test-model' },
|
|
96
|
+
presetName: 'worker-route',
|
|
97
|
+
workerCwd: 'C:\\Project\\tree',
|
|
98
|
+
prompt: 'cancel me',
|
|
99
|
+
watchdogPolicy: null,
|
|
100
|
+
}, { callerSessionId: 'sess_lead' }, job), /cancelled by agent close/);
|
|
101
|
+
|
|
102
|
+
assert.equal(updates.at(-1).status, 'cancelled');
|
|
103
|
+
assert.equal(updates.at(-1).stage, 'cancelled');
|
|
104
|
+
});
|
|
@@ -20,7 +20,7 @@ export const AGENT_TOOL = {
|
|
|
20
20
|
destructiveHint: true,
|
|
21
21
|
idempotentHint: false,
|
|
22
22
|
openWorldHint: true,
|
|
23
|
-
agentHidden:
|
|
23
|
+
agentHidden: false,
|
|
24
24
|
},
|
|
25
25
|
description: 'Run scoped agent work as background tasks. spawn/send return task_id immediately. Reuse one tag for one scope for its whole lifetime; give distinct tags only to independent scopes. Never mint a new tag because a session expired or was lost — same-tag spawn respawns it with the full brief. Wait for completion notifications; use status/read only for manual recovery.',
|
|
26
26
|
inputSchema: {
|
|
@@ -7,6 +7,7 @@ import { readFileSync, statSync } from 'node:fs';
|
|
|
7
7
|
import { resolve } from 'node:path';
|
|
8
8
|
|
|
9
9
|
import { updateJsonAtomicSync } from '../../runtime/shared/atomic-file.mjs';
|
|
10
|
+
import { resolveAgentTerminalReapMs } from '../../session-runtime/config-helpers.mjs';
|
|
10
11
|
import { WORKER_INDEX_FILE } from './tool-def.mjs';
|
|
11
12
|
import { agentTagOf, clean, positiveInt, rowMatchesContext } from './helpers.mjs';
|
|
12
13
|
import {
|
|
@@ -17,8 +18,37 @@ import {
|
|
|
17
18
|
workerRowKey,
|
|
18
19
|
} from './worker-rows.mjs';
|
|
19
20
|
|
|
21
|
+
const ACTIVE_WORKER_STATUS =
|
|
22
|
+
/^(?:connecting|requesting|streaming|tool[-_\s]?running|running|queued|pending|starting|cancelling)$/i;
|
|
23
|
+
const WORKER_POOL_FRESH_MS = 2 * 60 * 1000;
|
|
24
|
+
const exitFlushes = new Set();
|
|
25
|
+
let exitFlushInstalled = false;
|
|
26
|
+
|
|
27
|
+
function registerExitFlush(flush) {
|
|
28
|
+
exitFlushes.add(flush);
|
|
29
|
+
if (exitFlushInstalled) return;
|
|
30
|
+
exitFlushInstalled = true;
|
|
31
|
+
process.on('exit', () => {
|
|
32
|
+
for (const entry of exitFlushes) {
|
|
33
|
+
try { entry(); } catch { /* exit flush is best effort */ }
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function runtimeAlive(pid) {
|
|
39
|
+
const id = Number(pid) || 0;
|
|
40
|
+
if (id <= 0 || id === process.pid) return true;
|
|
41
|
+
try {
|
|
42
|
+
process.kill(id, 0);
|
|
43
|
+
return true;
|
|
44
|
+
} catch (error) {
|
|
45
|
+
return error?.code === 'EPERM';
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
20
49
|
export function createWorkerIndex({ dataDir, cfgMod, mgr, tags, tagAgents, tagCwds }) {
|
|
21
50
|
const pendingMutators = [];
|
|
51
|
+
const activeWorkerKeys = new Set();
|
|
22
52
|
let flushTimer = null;
|
|
23
53
|
// Mtime-keyed parse cache. A single spawn calls refreshTagsFromSessions /
|
|
24
54
|
// resolveTag / nextTag, which each re-read and re-parse this file; across a
|
|
@@ -48,6 +78,7 @@ export function createWorkerIndex({ dataDir, cfgMod, mgr, tags, tagAgents, tagCw
|
|
|
48
78
|
.map((row) => ({
|
|
49
79
|
tag: clean(row.tag),
|
|
50
80
|
sessionId: clean(row.sessionId),
|
|
81
|
+
parentSessionId: clean(row.parentSessionId) || null,
|
|
51
82
|
ownerSessionId: clean(row.ownerSessionId || row.parentSessionId) || null,
|
|
52
83
|
agent: clean(row.agent) || null,
|
|
53
84
|
provider: clean(row.provider) || null,
|
|
@@ -64,6 +95,7 @@ export function createWorkerIndex({ dataDir, cfgMod, mgr, tags, tagAgents, tagCw
|
|
|
64
95
|
turnStartedAt: clean(row.turnStartedAt) || null,
|
|
65
96
|
reapAt: clean(row.reapAt) || null,
|
|
66
97
|
clientHostPid: positiveInt(row.clientHostPid),
|
|
98
|
+
runtimePid: positiveInt(row.runtimePid),
|
|
67
99
|
cwd: clean(row.cwd) || null,
|
|
68
100
|
task_id: clean(row.task_id || row.taskId) || null,
|
|
69
101
|
error: clean(row.error) || null,
|
|
@@ -159,6 +191,47 @@ export function createWorkerIndex({ dataDir, cfgMod, mgr, tags, tagAgents, tagCw
|
|
|
159
191
|
}
|
|
160
192
|
}
|
|
161
193
|
|
|
194
|
+
function workerHeartbeatFresh(sessionId, now) {
|
|
195
|
+
const id = clean(sessionId);
|
|
196
|
+
if (!dataDir || !id) return false;
|
|
197
|
+
try {
|
|
198
|
+
const mtimeMs = statSync(resolve(dataDir, 'sessions', `${id}.hb`)).mtimeMs || 0;
|
|
199
|
+
return mtimeMs > 0 && now - mtimeMs <= WORKER_POOL_FRESH_MS;
|
|
200
|
+
} catch {
|
|
201
|
+
return false;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function staleActiveWorkerRow(row, now) {
|
|
206
|
+
const active = ACTIVE_WORKER_STATUS.test(clean(row?.status))
|
|
207
|
+
|| ACTIVE_WORKER_STATUS.test(clean(row?.stage));
|
|
208
|
+
if (!active) return false;
|
|
209
|
+
if (positiveInt(row.runtimePid) && !runtimeAlive(row.runtimePid)) return true;
|
|
210
|
+
if (workerHeartbeatFresh(row.sessionId, now)) return false;
|
|
211
|
+
const updated = Date.parse(clean(row.updatedAt)) || 0;
|
|
212
|
+
return !(updated > 0 && now - updated <= WORKER_POOL_FRESH_MS);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function terminalReapAt(row, now) {
|
|
216
|
+
let reapMs = null;
|
|
217
|
+
try { reapMs = resolveAgentTerminalReapMs(cfgMod.loadConfig(), row?.provider); }
|
|
218
|
+
catch { reapMs = null; }
|
|
219
|
+
return reapMs == null ? null : new Date(now + reapMs).toISOString();
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function idleWorkerRow(row, now, touch = false) {
|
|
223
|
+
const stamp = new Date(now).toISOString();
|
|
224
|
+
return {
|
|
225
|
+
...row,
|
|
226
|
+
status: 'idle',
|
|
227
|
+
stage: 'idle',
|
|
228
|
+
turnStartedAt: null,
|
|
229
|
+
finishedAt: touch ? stamp : (clean(row.finishedAt) || clean(row.updatedAt) || stamp),
|
|
230
|
+
updatedAt: touch ? stamp : (clean(row.updatedAt) || stamp),
|
|
231
|
+
reapAt: touch ? terminalReapAt(row, now) : (clean(row.reapAt) || terminalReapAt(row, now)),
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
|
|
162
235
|
// Spawn-path writes are batched onto one microtask so a parallel fanout pays
|
|
163
236
|
// a single locked rewrite instead of one per worker.
|
|
164
237
|
function flushWorkerIndexMutations() {
|
|
@@ -197,6 +270,9 @@ export function createWorkerIndex({ dataDir, cfgMod, mgr, tags, tagAgents, tagCw
|
|
|
197
270
|
return {
|
|
198
271
|
tag,
|
|
199
272
|
sessionId,
|
|
273
|
+
parentSessionId: clean(extra.parentSessionId)
|
|
274
|
+
|| clean(session?.parentSessionId)
|
|
275
|
+
|| null,
|
|
200
276
|
ownerSessionId: clean(extra.ownerSessionId || extra.parentSessionId)
|
|
201
277
|
|| clean(session?.ownerSessionId || session?.parentSessionId)
|
|
202
278
|
|| null,
|
|
@@ -219,6 +295,7 @@ export function createWorkerIndex({ dataDir, cfgMod, mgr, tags, tagAgents, tagCw
|
|
|
219
295
|
// the new absolute deadline immediately afterwards via scheduleReap().
|
|
220
296
|
reapAt: clean(extra.reapAt) || null,
|
|
221
297
|
clientHostPid: positiveInt(extra.clientHostPid) || positiveInt(session?.clientHostPid),
|
|
298
|
+
runtimePid: positiveInt(extra.runtimePid) || process.pid,
|
|
222
299
|
cwd: clean(session?.cwd) || clean(extra.cwd) || null,
|
|
223
300
|
task_id: clean(extra.task_id || extra.taskId) || null,
|
|
224
301
|
error: clean(extra.error) || null,
|
|
@@ -234,6 +311,10 @@ export function createWorkerIndex({ dataDir, cfgMod, mgr, tags, tagAgents, tagCw
|
|
|
234
311
|
function upsertWorkerRow(row, { defer = false } = {}) {
|
|
235
312
|
const normalized = normalizeWorkerRows({ workers: [row] })[0];
|
|
236
313
|
if (!normalized) return false;
|
|
314
|
+
const key = workerRowKey(normalized);
|
|
315
|
+
if (ACTIVE_WORKER_STATUS.test(normalized.status)
|
|
316
|
+
|| ACTIVE_WORKER_STATUS.test(normalized.stage)) activeWorkerKeys.add(key);
|
|
317
|
+
else activeWorkerKeys.delete(key);
|
|
237
318
|
const bindTag = !isLeadPoolAgent(normalized.agent);
|
|
238
319
|
if (bindTag) {
|
|
239
320
|
tags.set(normalized.tag, normalized.sessionId);
|
|
@@ -288,6 +369,45 @@ export function createWorkerIndex({ dataDir, cfgMod, mgr, tags, tagAgents, tagCw
|
|
|
288
369
|
return rows;
|
|
289
370
|
}
|
|
290
371
|
|
|
372
|
+
function flushActiveWorkerRows() {
|
|
373
|
+
if (!activeWorkerKeys.size) return;
|
|
374
|
+
const keys = [...activeWorkerKeys];
|
|
375
|
+
activeWorkerKeys.clear();
|
|
376
|
+
const now = Date.now();
|
|
377
|
+
writeWorkerRows((byKey) => {
|
|
378
|
+
for (const key of keys) {
|
|
379
|
+
const current = byKey.get(key);
|
|
380
|
+
if (!current || positiveInt(current.runtimePid) !== process.pid) continue;
|
|
381
|
+
const active = ACTIVE_WORKER_STATUS.test(clean(current.status))
|
|
382
|
+
|| ACTIVE_WORKER_STATUS.test(clean(current.stage));
|
|
383
|
+
if (active) byKey.set(key, idleWorkerRow(current, now, true));
|
|
384
|
+
}
|
|
385
|
+
});
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
function recoverStaleWorkerRows() {
|
|
389
|
+
const now = Date.now();
|
|
390
|
+
const stale = readAllWorkerRows().filter((row) => staleActiveWorkerRow(row, now));
|
|
391
|
+
if (!stale.length) return false;
|
|
392
|
+
writeWorkerRows((byKey) => {
|
|
393
|
+
for (const row of stale) {
|
|
394
|
+
const key = workerRowKey(row);
|
|
395
|
+
const current = byKey.get(key);
|
|
396
|
+
if (!current || !staleActiveWorkerRow(current, now)) continue;
|
|
397
|
+
byKey.set(key, idleWorkerRow(current, now));
|
|
398
|
+
}
|
|
399
|
+
});
|
|
400
|
+
return true;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
function flushWorkerIndexOnExit() {
|
|
404
|
+
flushWorkerIndexMutations();
|
|
405
|
+
flushActiveWorkerRows();
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
recoverStaleWorkerRows();
|
|
409
|
+
registerExitFlush(flushWorkerIndexOnExit);
|
|
410
|
+
|
|
291
411
|
return {
|
|
292
412
|
workerIndexPath,
|
|
293
413
|
invalidateWorkerRowsCache: invalidateCache,
|
|
@@ -304,5 +424,8 @@ export function createWorkerIndex({ dataDir, cfgMod, mgr, tags, tagAgents, tagCw
|
|
|
304
424
|
upsertWorkerSessionDeferred,
|
|
305
425
|
removeWorkerRow,
|
|
306
426
|
refreshTagsFromIndex,
|
|
427
|
+
flushActiveWorkerRows,
|
|
428
|
+
flushWorkerIndexOnExit,
|
|
429
|
+
recoverStaleWorkerRows,
|
|
307
430
|
};
|
|
308
431
|
}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
3
|
+
import { tmpdir } from 'node:os';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
import test from 'node:test';
|
|
6
|
+
|
|
7
|
+
import { listStoredAgentWorkers } from '../../runtime/agent/orchestrator/session/store-summary-reader.mjs';
|
|
8
|
+
import { createWorkerIndex } from './worker-index.mjs';
|
|
9
|
+
|
|
10
|
+
function workerIndex(root) {
|
|
11
|
+
return createWorkerIndex({
|
|
12
|
+
dataDir: root,
|
|
13
|
+
cfgMod: { loadConfig: () => ({}) },
|
|
14
|
+
mgr: { getSessionRuntime: () => null },
|
|
15
|
+
tags: new Map(),
|
|
16
|
+
tagAgents: new Map(),
|
|
17
|
+
tagCwds: new Map(),
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function writeWorkers(root, workers) {
|
|
22
|
+
writeFileSync(join(root, 'agent-workers.json'), JSON.stringify({ workers }));
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
test('worker boot recovery settles a running row whose runtime died', () => {
|
|
26
|
+
const root = mkdtempSync(join(tmpdir(), 'mixdog-worker-recover-'));
|
|
27
|
+
const previous = process.env.MIXDOG_DATA_DIR;
|
|
28
|
+
process.env.MIXDOG_DATA_DIR = root;
|
|
29
|
+
try {
|
|
30
|
+
mkdirSync(join(root, 'sessions'));
|
|
31
|
+
const now = Date.now();
|
|
32
|
+
writeWorkers(root, {
|
|
33
|
+
crashed: {
|
|
34
|
+
tag: 'crashed',
|
|
35
|
+
sessionId: 'crashed',
|
|
36
|
+
ownerSessionId: 'lead-a',
|
|
37
|
+
agent: 'worker',
|
|
38
|
+
status: 'running',
|
|
39
|
+
stage: 'running',
|
|
40
|
+
runtimePid: 0x7fffffff,
|
|
41
|
+
turnStartedAt: new Date(now - 5_000).toISOString(),
|
|
42
|
+
updatedAt: new Date(now - 5_000).toISOString(),
|
|
43
|
+
},
|
|
44
|
+
live: {
|
|
45
|
+
tag: 'live',
|
|
46
|
+
sessionId: 'live',
|
|
47
|
+
ownerSessionId: 'lead-a',
|
|
48
|
+
agent: 'worker',
|
|
49
|
+
status: 'running',
|
|
50
|
+
stage: 'running',
|
|
51
|
+
runtimePid: process.pid,
|
|
52
|
+
turnStartedAt: new Date(now - 5_000).toISOString(),
|
|
53
|
+
updatedAt: new Date(now - 5_000).toISOString(),
|
|
54
|
+
},
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
workerIndex(root);
|
|
58
|
+
|
|
59
|
+
const stored = JSON.parse(readFileSync(join(root, 'agent-workers.json'), 'utf8'));
|
|
60
|
+
assert.equal(stored.workers.crashed.status, 'idle');
|
|
61
|
+
assert.equal(stored.workers.crashed.stage, 'idle');
|
|
62
|
+
assert.equal(stored.workers.crashed.turnStartedAt, null);
|
|
63
|
+
assert.equal(stored.workers.live.status, 'running');
|
|
64
|
+
|
|
65
|
+
const pool = listStoredAgentWorkers();
|
|
66
|
+
assert.equal(pool.find((row) => row.sessionId === 'crashed')?.status, 'idle');
|
|
67
|
+
assert.equal(pool.find((row) => row.sessionId === 'live')?.status, 'running');
|
|
68
|
+
} finally {
|
|
69
|
+
if (previous === undefined) delete process.env.MIXDOG_DATA_DIR;
|
|
70
|
+
else process.env.MIXDOG_DATA_DIR = previous;
|
|
71
|
+
rmSync(root, { recursive: true, force: true });
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
test('cold pool reader does not trust an expired running worker row', () => {
|
|
76
|
+
const root = mkdtempSync(join(tmpdir(), 'mixdog-worker-cold-stale-'));
|
|
77
|
+
const previous = process.env.MIXDOG_DATA_DIR;
|
|
78
|
+
process.env.MIXDOG_DATA_DIR = root;
|
|
79
|
+
try {
|
|
80
|
+
mkdirSync(join(root, 'sessions'));
|
|
81
|
+
const old = new Date(Date.now() - 3 * 60 * 1000).toISOString();
|
|
82
|
+
writeWorkers(root, {
|
|
83
|
+
stale: {
|
|
84
|
+
tag: 'stale',
|
|
85
|
+
sessionId: 'stale',
|
|
86
|
+
ownerSessionId: 'lead-a',
|
|
87
|
+
agent: 'worker',
|
|
88
|
+
status: 'running',
|
|
89
|
+
stage: 'running',
|
|
90
|
+
turnStartedAt: old,
|
|
91
|
+
updatedAt: old,
|
|
92
|
+
},
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
const row = listStoredAgentWorkers().find((entry) => entry.sessionId === 'stale');
|
|
96
|
+
assert.equal(row?.status, 'idle');
|
|
97
|
+
assert.equal(row?.stage, 'idle');
|
|
98
|
+
assert.equal(row?.turnStartedAt, null);
|
|
99
|
+
} finally {
|
|
100
|
+
if (previous === undefined) delete process.env.MIXDOG_DATA_DIR;
|
|
101
|
+
else process.env.MIXDOG_DATA_DIR = previous;
|
|
102
|
+
rmSync(root, { recursive: true, force: true });
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
test('worker exit flush commits queued state before settling active work', () => {
|
|
107
|
+
const root = mkdtempSync(join(tmpdir(), 'mixdog-worker-exit-flush-'));
|
|
108
|
+
try {
|
|
109
|
+
mkdirSync(join(root, 'sessions'));
|
|
110
|
+
const index = workerIndex(root);
|
|
111
|
+
index.upsertWorkerSessionDeferred({
|
|
112
|
+
id: 'exit-worker',
|
|
113
|
+
ownerSessionId: 'lead-a',
|
|
114
|
+
agentTag: 'exit-worker',
|
|
115
|
+
agent: 'worker',
|
|
116
|
+
createdAt: new Date().toISOString(),
|
|
117
|
+
}, 'exit-worker', {
|
|
118
|
+
status: 'running',
|
|
119
|
+
stage: 'running',
|
|
120
|
+
turnStartedAt: new Date().toISOString(),
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
index.flushWorkerIndexOnExit();
|
|
124
|
+
|
|
125
|
+
const stored = JSON.parse(readFileSync(join(root, 'agent-workers.json'), 'utf8'));
|
|
126
|
+
assert.equal(stored.workers['exit-worker'].status, 'idle');
|
|
127
|
+
assert.equal(stored.workers['exit-worker'].stage, 'idle');
|
|
128
|
+
assert.equal(stored.workers['exit-worker'].turnStartedAt, null);
|
|
129
|
+
} finally {
|
|
130
|
+
rmSync(root, { recursive: true, force: true });
|
|
131
|
+
}
|
|
132
|
+
});
|
|
@@ -74,7 +74,7 @@ export function applyWorkerRowUpsert(byKey, normalized) {
|
|
|
74
74
|
if (!key) return;
|
|
75
75
|
const prev = byKey.get(key) || {};
|
|
76
76
|
const merged = { ...prev, ...normalized };
|
|
77
|
-
for (const field of ['ownerSessionId', 'agent', 'provider', 'model', 'preset', 'effort', 'fast', 'clientHostPid', 'cwd', 'task_id', 'permission', 'toolPermission', 'turnStartedAt']) {
|
|
77
|
+
for (const field of ['parentSessionId', 'ownerSessionId', 'agent', 'provider', 'model', 'preset', 'effort', 'fast', 'clientHostPid', 'runtimePid', 'cwd', 'task_id', 'permission', 'toolPermission', 'turnStartedAt']) {
|
|
78
78
|
if ((merged[field] === null || merged[field] === '') && prev[field] != null && prev[field] !== '') {
|
|
79
79
|
merged[field] = prev[field];
|
|
80
80
|
}
|
|
@@ -89,6 +89,7 @@ export function applyWorkerRowUpsert(byKey, normalized) {
|
|
|
89
89
|
export function workerRowToSession(row = {}) {
|
|
90
90
|
return {
|
|
91
91
|
id: row.sessionId,
|
|
92
|
+
parentSessionId: row.parentSessionId || row.ownerSessionId || null,
|
|
92
93
|
ownerSessionId: row.ownerSessionId || null,
|
|
93
94
|
agentTag: row.tag,
|
|
94
95
|
agent: row.agent || null,
|
|
@@ -97,10 +97,13 @@ export function createStandaloneAgent({
|
|
|
97
97
|
mcpScopeId = null,
|
|
98
98
|
onSubagentEvent,
|
|
99
99
|
notifySessionCompletion,
|
|
100
|
+
sessionSurface = null,
|
|
100
101
|
awaitKeychainPrewarm = async () => {},
|
|
101
102
|
isKeychainPrewarmReady = () => true,
|
|
102
103
|
}) {
|
|
103
104
|
const mgr = baseMgr;
|
|
105
|
+
const canUseSessionSurface = (session) => typeof sessionSurface?.runTurn === 'function'
|
|
106
|
+
&& sessionSurface.canRun?.(session) !== false;
|
|
104
107
|
const statusListeners = new Set();
|
|
105
108
|
const notifyStatusChange = () => {
|
|
106
109
|
for (const listener of [...statusListeners]) {
|
|
@@ -118,6 +121,8 @@ export function createStandaloneAgent({
|
|
|
118
121
|
notifyContext?.callerSessionId
|
|
119
122
|
|| notifyContext?.sessionId
|
|
120
123
|
|| notifyContext?.routingSessionId
|
|
124
|
+
|| session?.parentSessionId
|
|
125
|
+
|| notifyContext?.ownerSessionId
|
|
121
126
|
|| session?.ownerSessionId,
|
|
122
127
|
);
|
|
123
128
|
const handle = beginAgentTurnReview(ownerSessionId, session?.id, { tag, agent });
|
|
@@ -230,6 +235,7 @@ export function createStandaloneAgent({
|
|
|
230
235
|
emitSubagentEvent,
|
|
231
236
|
notifyStatusChange,
|
|
232
237
|
notifySessionCompletion,
|
|
238
|
+
sessionSurface,
|
|
233
239
|
scheduleReap,
|
|
234
240
|
cfgMod,
|
|
235
241
|
dataDir,
|
|
@@ -288,11 +294,12 @@ export function createStandaloneAgent({
|
|
|
288
294
|
provider: session.provider,
|
|
289
295
|
model: session.model,
|
|
290
296
|
content: result?.content || '',
|
|
297
|
+
...(sessionSurface?.canonical === true ? { handoffOnly: true } : {}),
|
|
291
298
|
...(abnormalError ? { error: abnormalError } : {}),
|
|
292
299
|
};
|
|
293
300
|
};
|
|
294
301
|
handoffMsgStart = resolveHandoffMessageStartIndex(mgr.getSession(sessionId));
|
|
295
|
-
const
|
|
302
|
+
const turnHooks = {
|
|
296
303
|
notifyFn: workerNotifyFn(sessionId, notifyContext || {}),
|
|
297
304
|
onToolResult: (message) => turnReview.onToolResult(message),
|
|
298
305
|
...(job ? {
|
|
@@ -304,7 +311,24 @@ export function createStandaloneAgent({
|
|
|
304
311
|
reconcileJobTerminalResult(job, value);
|
|
305
312
|
},
|
|
306
313
|
} : {}),
|
|
307
|
-
}
|
|
314
|
+
};
|
|
315
|
+
const result = canUseSessionSurface(session)
|
|
316
|
+
? await sessionSurface.runTurn({
|
|
317
|
+
session,
|
|
318
|
+
prompt,
|
|
319
|
+
context: args.context || null,
|
|
320
|
+
cwd: session.cwd || defaultCwd,
|
|
321
|
+
...turnHooks,
|
|
322
|
+
})
|
|
323
|
+
: await mgr.askSession(
|
|
324
|
+
sessionId,
|
|
325
|
+
prompt,
|
|
326
|
+
args.context || null,
|
|
327
|
+
null,
|
|
328
|
+
session.cwd || defaultCwd,
|
|
329
|
+
null,
|
|
330
|
+
turnHooks,
|
|
331
|
+
);
|
|
308
332
|
// Early preview no longer suppresses the canonical body notification;
|
|
309
333
|
// notifyTaskCompletion fires once with output via resolve/reconcile.
|
|
310
334
|
const finalValue = completionValue(result);
|
|
@@ -359,7 +383,9 @@ export function createStandaloneAgent({
|
|
|
359
383
|
// live session (reuse path). Busy sessions queue the prompt; idle ones run a
|
|
360
384
|
// background send job that continues the existing session (context kept).
|
|
361
385
|
function dispatchToExistingSession(prepared, notifyContext, extras = {}) {
|
|
362
|
-
if (
|
|
386
|
+
if (!canUseSessionSurface(prepared.session)
|
|
387
|
+
&& isSessionBusy(prepared.sessionId)
|
|
388
|
+
&& typeof mgr.enqueuePendingMessage === 'function') {
|
|
363
389
|
const queueDepth = mgr.enqueuePendingMessage(prepared.sessionId, prepared.prompt);
|
|
364
390
|
return renderResult({
|
|
365
391
|
queued: true,
|
|
@@ -383,7 +409,7 @@ export function createStandaloneAgent({
|
|
|
383
409
|
return renderResult({ ...extras, ...renderJob(job, false) });
|
|
384
410
|
}
|
|
385
411
|
|
|
386
|
-
function close(args, context = {}) {
|
|
412
|
+
async function close(args, context = {}) {
|
|
387
413
|
const scopedContext = agentScope(args, context);
|
|
388
414
|
refreshTagsFromSessions({ scanSessions: wantsSessionScan(args), context: scopedContext });
|
|
389
415
|
const taskId = taskIdFromArgs(args);
|
|
@@ -415,6 +441,9 @@ export function createStandaloneAgent({
|
|
|
415
441
|
throw new Error(`agent close: target "${target}" is a Lead session`);
|
|
416
442
|
}
|
|
417
443
|
cancelReap(sessionId);
|
|
444
|
+
const descendantSessionIds = typeof mgr.descendantSessionIds === 'function'
|
|
445
|
+
? mgr.descendantSessionIds(sessionId)
|
|
446
|
+
: [];
|
|
418
447
|
const tag = tagForSession(sessionId);
|
|
419
448
|
clearAgentStatuslineRoute(sessionId);
|
|
420
449
|
// Cancel any running background task bound to this session BEFORE closing
|
|
@@ -430,13 +459,18 @@ export function createStandaloneAgent({
|
|
|
430
459
|
// Close (and stamp cancelStatus) BEFORE dropping the worker row. Removing
|
|
431
460
|
// the row first left the next pool read with only a leftover heartbeat,
|
|
432
461
|
// which republished the session as `running`.
|
|
433
|
-
const ok = mgr.closeSession(sessionId, 'cli-agent-close');
|
|
462
|
+
const ok = await mgr.closeSession(sessionId, 'cli-agent-close');
|
|
434
463
|
if (task?.taskId) cancelBackgroundTask(task.taskId, 'cancelled by agent close');
|
|
435
464
|
// Drop the row only once the cancellation is DURABLY recorded. A close that
|
|
436
465
|
// failed still forgot the row, leaving the next pool read with a fresh
|
|
437
466
|
// heartbeat and no cancelled state — which republished the session as
|
|
438
467
|
// `running`, the exact lingering row this whole path exists to prevent.
|
|
439
468
|
if (ok) forgetTerminalSession(tag, sessionId);
|
|
469
|
+
if (ok) {
|
|
470
|
+
for (const descendantSessionId of descendantSessionIds) {
|
|
471
|
+
removeWorkerRow({ sessionId: descendantSessionId });
|
|
472
|
+
}
|
|
473
|
+
}
|
|
440
474
|
return { closed: ok, tag, sessionId, task_id: task?.taskId || null };
|
|
441
475
|
}
|
|
442
476
|
|
|
@@ -455,7 +489,7 @@ export function createStandaloneAgent({
|
|
|
455
489
|
};
|
|
456
490
|
}
|
|
457
491
|
|
|
458
|
-
function closeAll(reason = 'cli-agent-close-all', scope = {}) {
|
|
492
|
+
async function closeAll(reason = 'cli-agent-close-all', scope = {}) {
|
|
459
493
|
// Scoped teardown (one Lead closing/deleting/switching) must close ONLY
|
|
460
494
|
// that Lead's workers: the worker index and task registry are shared by
|
|
461
495
|
// every Lead in the process, and the old unscoped sweep wiped sibling
|
|
@@ -467,7 +501,7 @@ export function createStandaloneAgent({
|
|
|
467
501
|
const failed = [];
|
|
468
502
|
for (const { tag, session } of agentSessionEntries({ scanSessions: false, context })) {
|
|
469
503
|
try {
|
|
470
|
-
closed.push(close({ sessionId: session.id }, context));
|
|
504
|
+
closed.push(await close({ sessionId: session.id }, context));
|
|
471
505
|
} catch (err) {
|
|
472
506
|
failed.push({ tag, error: presentErrorText(err, { surface: 'agent' }) });
|
|
473
507
|
}
|
|
@@ -538,12 +572,25 @@ export function createStandaloneAgent({
|
|
|
538
572
|
const callerCwd = clean(context.cwd || context.callerCwd);
|
|
539
573
|
const scopedContext = agentScope(args, context);
|
|
540
574
|
const notifyContext = context;
|
|
575
|
+
if (typeof mgr.rehydrateAgentSessions === 'function') {
|
|
576
|
+
await mgr.rehydrateAgentSessions();
|
|
577
|
+
// Rebuild tag ownership from canonical records even if a daemon crash
|
|
578
|
+
// happened before the auxiliary worker row was flushed.
|
|
579
|
+
refreshTagsFromSessions({ scanSessions: true, context: scopedContext });
|
|
580
|
+
}
|
|
541
581
|
if (type === 'list') return renderResult({ workers: list({ scanSessions: wantsSessionScan(args), context: scopedContext }), jobs: listJobs(scopedContext) });
|
|
542
582
|
if (type === 'status') return renderResult(renderJob(getJobOrWorker(args, scopedContext), false));
|
|
543
|
-
if (type === 'read')
|
|
583
|
+
if (type === 'read') {
|
|
584
|
+
const job = getJobOrWorker(args, scopedContext);
|
|
585
|
+
if (job?.taskId == null && typeof mgr.readSessionHandoff === 'function') {
|
|
586
|
+
const handoff = await mgr.readSessionHandoff(job.meta?.sessionId);
|
|
587
|
+
if (handoff) job.result = handoff;
|
|
588
|
+
}
|
|
589
|
+
return renderResult(renderJob(job, true));
|
|
590
|
+
}
|
|
544
591
|
if (type === 'cleanup') return renderResult(cleanup(args, scopedContext));
|
|
545
|
-
if (type === 'cancel') return renderResult(close(args, scopedContext));
|
|
546
|
-
if (type === 'close') return renderResult(close(args, scopedContext));
|
|
592
|
+
if (type === 'cancel') return renderResult(await close(args, scopedContext));
|
|
593
|
+
if (type === 'close') return renderResult(await close(args, scopedContext));
|
|
547
594
|
if (type === 'send') {
|
|
548
595
|
try {
|
|
549
596
|
const prepared = await prepareSend(args, scopedContext);
|