@fitchmultz/pi-subagents 0.0.0-stage → 0.44.4
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/CHANGELOG.md +1645 -0
- package/LICENSE +21 -0
- package/README.md +1529 -2
- package/agents/context-builder.md +97 -0
- package/agents/debugger.md +51 -0
- package/agents/delegate.md +13 -0
- package/agents/fixer.md +37 -0
- package/agents/oracle.md +71 -0
- package/agents/planner.md +72 -0
- package/agents/researcher.md +51 -0
- package/agents/reviewer-claude.md +69 -0
- package/agents/reviewer-gpt.md +68 -0
- package/agents/reviewer-ponytail.md +82 -0
- package/agents/reviewer-security.md +81 -0
- package/agents/reviewer.md +69 -0
- package/agents/scout.md +66 -0
- package/agents/ui-designer.md +51 -0
- package/agents/watcher.md +26 -0
- package/agents/worker.md +46 -0
- package/agents/writer.md +36 -0
- package/dist/agents/agent-management.js +923 -0
- package/dist/agents/agent-scope.js +7 -0
- package/dist/agents/agent-selection.js +20 -0
- package/dist/agents/agent-serializer.js +100 -0
- package/dist/agents/agents.js +752 -0
- package/dist/agents/chain-serializer.js +367 -0
- package/dist/agents/frontmatter.js +21 -0
- package/dist/agents/identity.js +29 -0
- package/dist/agents/skills.js +570 -0
- package/dist/extension/build-info.js +1 -0
- package/dist/extension/compact-tools.js +112 -0
- package/dist/extension/config.js +22 -0
- package/dist/extension/control-notices.js +29 -0
- package/dist/extension/doctor.js +143 -0
- package/dist/extension/fanout-child.js +326 -0
- package/dist/extension/index.js +577 -0
- package/dist/extension/schemas.js +416 -0
- package/dist/extension/tool-input.js +20 -0
- package/dist/extension/tool-result.js +14 -0
- package/dist/history/canonical-result.js +81 -0
- package/dist/history/database.js +72 -0
- package/dist/history/index.js +194 -0
- package/dist/history/ingest.js +447 -0
- package/dist/history/queries.js +350 -0
- package/dist/history/store.js +216 -0
- package/dist/history/types.js +4 -0
- package/dist/history/worker.js +461 -0
- package/dist/intercom/intercom-bridge.js +112 -0
- package/dist/intercom/live-intercom.js +94 -0
- package/dist/intercom/result-intercom.js +373 -0
- package/dist/pi-intercom/agent-dir.js +10 -0
- package/dist/pi-intercom/broker/broker.js +426 -0
- package/dist/pi-intercom/broker/client.js +492 -0
- package/dist/pi-intercom/broker/framing.js +65 -0
- package/dist/pi-intercom/broker/paths.js +42 -0
- package/dist/pi-intercom/broker/pid.js +66 -0
- package/dist/pi-intercom/broker/spawn.js +221 -0
- package/dist/pi-intercom/config.js +102 -0
- package/dist/pi-intercom/index.js +2942 -0
- package/dist/pi-intercom/reply-tracker.js +182 -0
- package/dist/pi-intercom/session-targets.js +128 -0
- package/dist/pi-intercom/subagent-live-events.js +132 -0
- package/dist/pi-intercom/topics.js +157 -0
- package/dist/pi-intercom/types.js +163 -0
- package/dist/pi-intercom/ui/compose.js +269 -0
- package/dist/pi-intercom/ui/inline-message.js +80 -0
- package/dist/pi-intercom/ui/session-list.js +124 -0
- package/dist/runs/background/async-control.js +95 -0
- package/dist/runs/background/async-execution.js +676 -0
- package/dist/runs/background/async-job-tracker.js +383 -0
- package/dist/runs/background/async-result-file.js +58 -0
- package/dist/runs/background/async-resume.js +305 -0
- package/dist/runs/background/async-status.js +442 -0
- package/dist/runs/background/completion-dedupe.js +69 -0
- package/dist/runs/background/completion-delivery.js +245 -0
- package/dist/runs/background/notify.js +71 -0
- package/dist/runs/background/parallel-groups.js +55 -0
- package/dist/runs/background/result-watcher.js +426 -0
- package/dist/runs/background/run-id-resolver.js +64 -0
- package/dist/runs/background/run-status.js +382 -0
- package/dist/runs/background/stale-run-reconciler.js +335 -0
- package/dist/runs/background/subagent-runner-launcher.js +55 -0
- package/dist/runs/background/subagent-runner.js +1872 -0
- package/dist/runs/background/top-level-async.js +5 -0
- package/dist/runs/foreground/chain-clarify.js +1152 -0
- package/dist/runs/foreground/clarify-invocation.js +72 -0
- package/dist/runs/foreground/execution-input.js +423 -0
- package/dist/runs/foreground/foreground-control.js +817 -0
- package/dist/runs/foreground/question-control.js +77 -0
- package/dist/runs/foreground/run-async-path.js +205 -0
- package/dist/runs/foreground/subagent-executor.js +442 -0
- package/dist/runs/foreground/subagent-params.js +165 -0
- package/dist/runs/foreground/wait-run.js +139 -0
- package/dist/runs/shared/acceptance-contract.js +253 -0
- package/dist/runs/shared/acceptance-evaluation.js +256 -0
- package/dist/runs/shared/acceptance-finalization.js +280 -0
- package/dist/runs/shared/acceptance-reports.js +147 -0
- package/dist/runs/shared/acceptance.js +4 -0
- package/dist/runs/shared/chain-outputs.js +110 -0
- package/dist/runs/shared/child-attempt.js +693 -0
- package/dist/runs/shared/child-execution-cwd.js +96 -0
- package/dist/runs/shared/child-observations.js +114 -0
- package/dist/runs/shared/claude-code.js +199 -0
- package/dist/runs/shared/completion-guard.js +31 -0
- package/dist/runs/shared/dynamic-fanout.js +274 -0
- package/dist/runs/shared/history-index.js +72 -0
- package/dist/runs/shared/mcp-direct-tool-allowlist.js +334 -0
- package/dist/runs/shared/model-fallback.js +185 -0
- package/dist/runs/shared/mutating-tool-guard.js +170 -0
- package/dist/runs/shared/native-finalization.js +101 -0
- package/dist/runs/shared/native-usage.js +153 -0
- package/dist/runs/shared/nested-events.js +700 -0
- package/dist/runs/shared/nested-path.js +48 -0
- package/dist/runs/shared/nested-render.js +108 -0
- package/dist/runs/shared/parallel-utils.js +61 -0
- package/dist/runs/shared/parent-receipts.js +68 -0
- package/dist/runs/shared/parent-usage.js +121 -0
- package/dist/runs/shared/pi-args.js +310 -0
- package/dist/runs/shared/pi-spawn.js +52 -0
- package/dist/runs/shared/run-history-queries.js +45 -0
- package/dist/runs/shared/run-history.js +103 -0
- package/dist/runs/shared/run-id-ambiguity.js +4 -0
- package/dist/runs/shared/run-records.js +679 -0
- package/dist/runs/shared/session-cwd-preload.js +33 -0
- package/dist/runs/shared/single-output.js +222 -0
- package/dist/runs/shared/streaming-text.js +15 -0
- package/dist/runs/shared/structured-output.js +55 -0
- package/dist/runs/shared/subagent-control.js +179 -0
- package/dist/runs/shared/subagent-prompt-runtime.js +197 -0
- package/dist/runs/shared/subagent-tool-loop-guard.js +66 -0
- package/dist/runs/shared/supervisor-questions.js +320 -0
- package/dist/runs/shared/workflow-graph.js +214 -0
- package/dist/runs/shared/workflow-policy.js +84 -0
- package/dist/runs/shared/worktree.js +525 -0
- package/dist/shared/agent-context-policy.js +126 -0
- package/dist/shared/artifacts.js +96 -0
- package/dist/shared/atomic-json.js +13 -0
- package/dist/shared/cooperative.js +20 -0
- package/dist/shared/execution-cwd.js +14 -0
- package/dist/shared/file-coalescer.js +21 -0
- package/dist/shared/fork-context.js +49 -0
- package/dist/shared/formatters.js +85 -0
- package/dist/shared/journal-reader.js +407 -0
- package/dist/shared/lazy-tools.js +32 -0
- package/dist/shared/model-info.js +68 -0
- package/dist/shared/native-session.js +15 -0
- package/dist/shared/native-tui.js +18 -0
- package/dist/shared/native-typebox.js +20 -0
- package/dist/shared/post-exit-stdio-guard.js +197 -0
- package/dist/shared/prompt-sections.js +8 -0
- package/dist/shared/session-entries.js +52 -0
- package/dist/shared/session-identity.js +14 -0
- package/dist/shared/settings.js +254 -0
- package/dist/shared/status-format.js +111 -0
- package/dist/shared/temp-root.js +114 -0
- package/dist/shared/types.js +207 -0
- package/dist/shared/ui-mode.js +3 -0
- package/dist/shared/utils.js +334 -0
- package/dist/slash/prompt-template-bridge.js +298 -0
- package/dist/slash/slash-bridge.js +116 -0
- package/dist/slash/slash-commands.js +607 -0
- package/dist/slash/slash-live-state.js +246 -0
- package/dist/tui/action-hints.js +75 -0
- package/dist/tui/agent-history.js +197 -0
- package/dist/tui/agent-view.js +1656 -0
- package/dist/tui/render.js +1515 -0
- package/docs/intercom.md +579 -0
- package/docs/performance.md +121 -0
- package/package.json +101 -4
- package/prompts/gather-context-and-clarify.md +14 -0
- package/prompts/parallel-cleanup.md +62 -0
- package/prompts/parallel-context-build.md +56 -0
- package/prompts/parallel-handoff-plan.md +62 -0
- package/prompts/parallel-research.md +51 -0
- package/prompts/parallel-review.md +59 -0
- package/prompts/review-loop.md +42 -0
- package/scripts/build.mjs +134 -0
- package/scripts/check-compat.mjs +48 -0
- package/scripts/compat-host.mjs +19 -0
- package/scripts/linux-smoke.sh +51 -0
- package/scripts/local-install-smoke.mjs +66 -0
- package/scripts/native-package-smoke.mjs +82 -0
- package/scripts/package-smoke.mjs +205 -0
- package/scripts/prepare.mjs +67 -0
- package/scripts/real-pi-smoke.mjs +415 -0
- package/scripts/run-tests.mjs +149 -0
- package/skills/pi-intercom/SKILL.md +113 -0
- package/skills/pi-intercom/evals/evals.json +29 -0
- package/skills/pi-intercom/evals/trigger-evals.json +47 -0
- package/skills/pi-intercom/references/peer-sessions.md +69 -0
- package/skills/pi-subagents/SKILL.md +76 -0
- package/skills/pi-subagents/evals/evals.json +29 -0
- package/skills/pi-subagents/evals/trigger-evals.json +42 -0
- package/skills/pi-subagents/references/full-orchestration-guide.md +863 -0
- package/src/agents/agent-management.ts +813 -0
- package/src/agents/agent-scope.ts +7 -0
- package/src/agents/agent-selection.ts +23 -0
- package/src/agents/agent-serializer.ts +99 -0
- package/src/agents/agents.ts +916 -0
- package/src/agents/chain-serializer.ts +329 -0
- package/src/agents/frontmatter.ts +21 -0
- package/src/agents/identity.ts +30 -0
- package/src/agents/skills.ts +637 -0
- package/src/extension/build-info.ts +2 -0
- package/src/extension/compact-tools.ts +114 -0
- package/src/extension/config.ts +23 -0
- package/src/extension/control-notices.ts +47 -0
- package/src/extension/doctor.ts +171 -0
- package/src/extension/fanout-child.ts +312 -0
- package/src/extension/index.ts +649 -0
- package/src/extension/schemas.ts +444 -0
- package/src/extension/tool-input.ts +19 -0
- package/src/extension/tool-result.ts +16 -0
- package/src/history/canonical-result.ts +62 -0
- package/src/history/database.ts +59 -0
- package/src/history/index.ts +152 -0
- package/src/history/ingest.ts +332 -0
- package/src/history/queries.ts +241 -0
- package/src/history/store.ts +203 -0
- package/src/history/types.ts +166 -0
- package/src/history/worker.ts +317 -0
- package/src/intercom/intercom-bridge.ts +123 -0
- package/src/intercom/live-intercom.ts +104 -0
- package/src/intercom/result-intercom.ts +429 -0
- package/src/pi-intercom/agent-dir.ts +9 -0
- package/src/pi-intercom/broker/broker.ts +463 -0
- package/src/pi-intercom/broker/client.ts +558 -0
- package/src/pi-intercom/broker/framing.ts +77 -0
- package/src/pi-intercom/broker/paths.ts +47 -0
- package/src/pi-intercom/broker/pid.ts +59 -0
- package/src/pi-intercom/broker/spawn.ts +248 -0
- package/src/pi-intercom/config.ts +142 -0
- package/src/pi-intercom/index.ts +2929 -0
- package/src/pi-intercom/reply-tracker.ts +211 -0
- package/src/pi-intercom/session-targets.ts +172 -0
- package/src/pi-intercom/subagent-live-events.ts +133 -0
- package/src/pi-intercom/topics.ts +125 -0
- package/src/pi-intercom/types.ts +271 -0
- package/src/pi-intercom/ui/compose.ts +289 -0
- package/src/pi-intercom/ui/inline-message.ts +92 -0
- package/src/pi-intercom/ui/session-list.ts +139 -0
- package/src/runs/background/async-control.ts +89 -0
- package/src/runs/background/async-execution.ts +850 -0
- package/src/runs/background/async-job-tracker.ts +385 -0
- package/src/runs/background/async-result-file.ts +62 -0
- package/src/runs/background/async-resume.ts +333 -0
- package/src/runs/background/async-status.ts +481 -0
- package/src/runs/background/completion-dedupe.ts +79 -0
- package/src/runs/background/completion-delivery.ts +195 -0
- package/src/runs/background/notify.ts +123 -0
- package/src/runs/background/parallel-groups.ts +53 -0
- package/src/runs/background/result-watcher.ts +423 -0
- package/src/runs/background/run-id-resolver.ts +69 -0
- package/src/runs/background/run-status.ts +384 -0
- package/src/runs/background/stale-run-reconciler.ts +369 -0
- package/src/runs/background/subagent-runner-launcher.ts +56 -0
- package/src/runs/background/subagent-runner.ts +2053 -0
- package/src/runs/background/top-level-async.ts +13 -0
- package/src/runs/foreground/chain-clarify.ts +1340 -0
- package/src/runs/foreground/clarify-invocation.ts +77 -0
- package/src/runs/foreground/execution-input.ts +467 -0
- package/src/runs/foreground/foreground-control.ts +821 -0
- package/src/runs/foreground/question-control.ts +70 -0
- package/src/runs/foreground/run-async-path.ts +243 -0
- package/src/runs/foreground/subagent-executor.ts +501 -0
- package/src/runs/foreground/subagent-params.ts +320 -0
- package/src/runs/foreground/wait-run.ts +121 -0
- package/src/runs/shared/acceptance-contract.ts +257 -0
- package/src/runs/shared/acceptance-evaluation.ts +275 -0
- package/src/runs/shared/acceptance-finalization.ts +353 -0
- package/src/runs/shared/acceptance-reports.ts +152 -0
- package/src/runs/shared/acceptance.ts +33 -0
- package/src/runs/shared/chain-outputs.ts +114 -0
- package/src/runs/shared/child-attempt.ts +623 -0
- package/src/runs/shared/child-execution-cwd.ts +91 -0
- package/src/runs/shared/child-observations.ts +99 -0
- package/src/runs/shared/claude-code.ts +243 -0
- package/src/runs/shared/completion-guard.ts +35 -0
- package/src/runs/shared/dynamic-fanout.ts +295 -0
- package/src/runs/shared/history-index.ts +69 -0
- package/src/runs/shared/mcp-direct-tool-allowlist.ts +376 -0
- package/src/runs/shared/model-fallback.ts +219 -0
- package/src/runs/shared/mutating-tool-guard.ts +194 -0
- package/src/runs/shared/native-finalization.ts +128 -0
- package/src/runs/shared/native-usage.ts +113 -0
- package/src/runs/shared/nested-events.ts +752 -0
- package/src/runs/shared/nested-path.ts +52 -0
- package/src/runs/shared/nested-render.ts +119 -0
- package/src/runs/shared/parallel-utils.ts +153 -0
- package/src/runs/shared/parent-receipts.ts +59 -0
- package/src/runs/shared/parent-usage.ts +121 -0
- package/src/runs/shared/pi-args.ts +350 -0
- package/src/runs/shared/pi-spawn.ts +52 -0
- package/src/runs/shared/run-history-queries.ts +44 -0
- package/src/runs/shared/run-history.ts +93 -0
- package/src/runs/shared/run-id-ambiguity.ts +4 -0
- package/src/runs/shared/run-records.ts +607 -0
- package/src/runs/shared/session-cwd-preload.ts +27 -0
- package/src/runs/shared/single-output.ts +267 -0
- package/src/runs/shared/streaming-text.ts +18 -0
- package/src/runs/shared/structured-output.ts +68 -0
- package/src/runs/shared/subagent-control.ts +219 -0
- package/src/runs/shared/subagent-prompt-runtime.ts +201 -0
- package/src/runs/shared/subagent-tool-loop-guard.ts +89 -0
- package/src/runs/shared/supervisor-questions.ts +372 -0
- package/src/runs/shared/workflow-graph.ts +220 -0
- package/src/runs/shared/workflow-policy.ts +125 -0
- package/src/runs/shared/worktree.ts +635 -0
- package/src/shared/agent-context-policy.ts +184 -0
- package/src/shared/artifacts.ts +95 -0
- package/src/shared/atomic-json.ts +16 -0
- package/src/shared/cooperative.ts +21 -0
- package/src/shared/execution-cwd.ts +14 -0
- package/src/shared/file-coalescer.ts +29 -0
- package/src/shared/fork-context.ts +68 -0
- package/src/shared/formatters.ts +84 -0
- package/src/shared/journal-reader.ts +333 -0
- package/src/shared/lazy-tools.ts +36 -0
- package/src/shared/model-info.ts +81 -0
- package/src/shared/native-session.ts +8 -0
- package/src/shared/native-tui.ts +10 -0
- package/src/shared/native-typebox.ts +14 -0
- package/src/shared/post-exit-stdio-guard.ts +204 -0
- package/src/shared/prompt-sections.ts +10 -0
- package/src/shared/session-entries.ts +49 -0
- package/src/shared/session-identity.ts +18 -0
- package/src/shared/settings.ts +384 -0
- package/src/shared/status-format.ts +123 -0
- package/src/shared/temp-root.ts +107 -0
- package/src/shared/types.ts +1259 -0
- package/src/shared/ui-mode.ts +5 -0
- package/src/shared/utils.ts +342 -0
- package/src/slash/prompt-template-bridge.ts +408 -0
- package/src/slash/slash-bridge.ts +178 -0
- package/src/slash/slash-commands.ts +617 -0
- package/src/slash/slash-live-state.ts +292 -0
- package/src/tui/action-hints.ts +69 -0
- package/src/tui/agent-history.ts +210 -0
- package/src/tui/agent-view.ts +1323 -0
- package/src/tui/render.ts +1550 -0
- package/tsconfig.build.json +12 -0
- package/tsconfig.json +19 -0
|
@@ -0,0 +1,426 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import { setImmediate as yieldToInput } from "node:timers/promises";
|
|
4
|
+
import { getRunMetadataDir } from "../shared/supervisor-questions.js";
|
|
5
|
+
import { buildCompletionKey, markSeenWithTtl } from "./completion-dedupe.js";
|
|
6
|
+
import { createFileCoalescer } from "../../shared/file-coalescer.js";
|
|
7
|
+
import { journalStamp } from "../../shared/journal-reader.js";
|
|
8
|
+
import { resolveOrchestratorIntercomTarget } from "../../intercom/intercom-bridge.js";
|
|
9
|
+
import { SUBAGENT_ASYNC_COMPLETE_EVENT, } from "../../shared/types.js";
|
|
10
|
+
import { attachNestedChildrenToResultChildren, buildSubagentResultIntercomPayload, compactNestedResultChildren, deliverSubagentResultIntercomEvent, resolveSubagentResultStatus, } from "../../intercom/result-intercom.js";
|
|
11
|
+
import { projectNestedRegistryForRoot, sanitizeSummary } from "../shared/nested-events.js";
|
|
12
|
+
import { isDurableRun, readAsyncResultFile } from "./async-result-file.js";
|
|
13
|
+
const WATCHER_RESTART_DELAY_MS = 3000;
|
|
14
|
+
const POLL_INTERVAL_MS = 3000;
|
|
15
|
+
function sanitizeNestedResultChildren(value, resultPath, label) {
|
|
16
|
+
if (value === undefined)
|
|
17
|
+
return undefined;
|
|
18
|
+
if (!Array.isArray(value)) {
|
|
19
|
+
console.error(`Ignoring invalid nested children in subagent result file '${resultPath}' at ${label}: expected an array.`);
|
|
20
|
+
return undefined;
|
|
21
|
+
}
|
|
22
|
+
const children = value.map((child) => sanitizeSummary(child)).filter((child) => Boolean(child));
|
|
23
|
+
if (children.length !== value.length) {
|
|
24
|
+
console.error(`Ignoring ${value.length - children.length} invalid nested child record(s) in subagent result file '${resultPath}' at ${label}.`);
|
|
25
|
+
}
|
|
26
|
+
return children.length ? children : undefined;
|
|
27
|
+
}
|
|
28
|
+
function getErrorCode(error) {
|
|
29
|
+
return typeof error === "object" && error !== null && "code" in error
|
|
30
|
+
? error.code
|
|
31
|
+
: undefined;
|
|
32
|
+
}
|
|
33
|
+
function isNotFoundError(error) {
|
|
34
|
+
if (getErrorCode(error) === "ENOENT")
|
|
35
|
+
return true;
|
|
36
|
+
const cause = error instanceof Error ? error.cause : undefined;
|
|
37
|
+
return getErrorCode(cause) === "ENOENT";
|
|
38
|
+
}
|
|
39
|
+
function shouldFallBackToPolling(error) {
|
|
40
|
+
const code = getErrorCode(error);
|
|
41
|
+
return code === "EMFILE" || code === "ENOSPC";
|
|
42
|
+
}
|
|
43
|
+
export function createResultWatcher(pi, state, resultsDir, deps = {}) {
|
|
44
|
+
let periodicScanTimer = null;
|
|
45
|
+
const processingCompletionKeys = new Set();
|
|
46
|
+
const inFlight = new Set();
|
|
47
|
+
let startTail = Promise.resolve(), generation = 0, preserveBeforeGeneration = 0;
|
|
48
|
+
let joiningGeneration;
|
|
49
|
+
const foreignResults = new Map();
|
|
50
|
+
const pendingResultFiles = () => {
|
|
51
|
+
const files = fs.existsSync(resultsDir) ? fs.readdirSync(resultsDir).filter((name) => name.endsWith(".json")) : [];
|
|
52
|
+
const notified = new Map(files.map((file, index) => [file, index]));
|
|
53
|
+
for (const run of state.ownedRuns?.values() ?? []) {
|
|
54
|
+
if (run.source !== "async" || run.accounting?.state !== "incomplete" && run.delivery?.entryId)
|
|
55
|
+
continue;
|
|
56
|
+
const file = path.join(getRunMetadataDir(run.runId), "result.json");
|
|
57
|
+
if (!fs.existsSync(file))
|
|
58
|
+
continue;
|
|
59
|
+
// Disposable hints cannot veto canonical recovery, including corrupt hints.
|
|
60
|
+
const notification = notified.get(`${run.runId}.json`);
|
|
61
|
+
if (notification === undefined)
|
|
62
|
+
files.push(file);
|
|
63
|
+
else
|
|
64
|
+
files[notification] = file;
|
|
65
|
+
}
|
|
66
|
+
const present = new Set(files.map((file) => path.isAbsolute(file) ? file : path.join(resultsDir, file)));
|
|
67
|
+
for (const file of foreignResults.keys())
|
|
68
|
+
if (!present.has(file))
|
|
69
|
+
foreignResults.delete(file);
|
|
70
|
+
return files.filter((file) => {
|
|
71
|
+
const resultPath = path.isAbsolute(file) ? file : path.join(resultsDir, file);
|
|
72
|
+
try {
|
|
73
|
+
return !isForeignUnchanged(resultPath);
|
|
74
|
+
}
|
|
75
|
+
catch (error) {
|
|
76
|
+
if (isNotFoundError(error)) {
|
|
77
|
+
foreignResults.delete(resultPath);
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
return true;
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
};
|
|
84
|
+
const isForeignUnchanged = (resultPath) => {
|
|
85
|
+
const cached = foreignResults.get(resultPath);
|
|
86
|
+
if (!cached || cached.sessionId !== state.currentSessionId || cached.ownerSessionId !== state.ownedRuns?.get(cached.runId)?.ownerSessionId)
|
|
87
|
+
return false;
|
|
88
|
+
return cached.stamp === journalStamp(fs.statSync(resultPath, { bigint: true }))
|
|
89
|
+
&& (!cached.canonicalPath || cached.canonicalStamp === journalStamp(fs.statSync(cached.canonicalPath, { bigint: true })));
|
|
90
|
+
};
|
|
91
|
+
const handleResult = async (file) => {
|
|
92
|
+
const startedGeneration = generation, ownerSessionId = state.currentSessionId;
|
|
93
|
+
let claimedCompletionKey;
|
|
94
|
+
let completionEmitted = false;
|
|
95
|
+
const durableFile = path.isAbsolute(file);
|
|
96
|
+
const resultPath = durableFile ? file : path.join(resultsDir, file);
|
|
97
|
+
if (!fs.existsSync(resultPath))
|
|
98
|
+
return;
|
|
99
|
+
try {
|
|
100
|
+
if (isForeignUnchanged(resultPath))
|
|
101
|
+
return;
|
|
102
|
+
const stamp = journalStamp(fs.statSync(resultPath, { bigint: true }));
|
|
103
|
+
const notification = readAsyncResultFile(resultPath);
|
|
104
|
+
const runId = notification.runId ?? notification.id ?? path.basename(file, ".json");
|
|
105
|
+
const run = state.ownedRuns?.get(runId);
|
|
106
|
+
const canonicalPath = isDurableRun(notification) && !durableFile ? path.join(getRunMetadataDir(runId), "result.json") : undefined;
|
|
107
|
+
const canonicalStamp = canonicalPath ? journalStamp(fs.statSync(canonicalPath, { bigint: true })) : undefined;
|
|
108
|
+
const data = canonicalPath ? readAsyncResultFile(canonicalPath) : notification;
|
|
109
|
+
if ((data.runId ?? data.id ?? runId) !== runId)
|
|
110
|
+
throw new Error(`Result identity does not match notification '${runId}'.`);
|
|
111
|
+
if (durableFile && resultPath !== path.join(getRunMetadataDir(runId), "result.json"))
|
|
112
|
+
throw new Error(`Canonical result identity does not match path '${resultPath}'.`);
|
|
113
|
+
if (data.sessionId ? data.sessionId !== state.currentSessionId && run?.ownerSessionId !== state.currentSessionId : !run) {
|
|
114
|
+
if (stamp)
|
|
115
|
+
foreignResults.set(resultPath, { stamp, sessionId: state.currentSessionId, runId, ownerSessionId: run?.ownerSessionId, canonicalPath, canonicalStamp });
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
foreignResults.delete(resultPath);
|
|
119
|
+
const consumeNotification = () => {
|
|
120
|
+
// A legacy hint can be the only saved result. Native queue admission
|
|
121
|
+
// is not publication; retain it until the verified parent receipt exists.
|
|
122
|
+
if (!durableFile && !canonicalPath && deps.isCompletionPublished && !deps.isCompletionPublished(runId, completionKey))
|
|
123
|
+
return;
|
|
124
|
+
const hint = durableFile ? path.join(resultsDir, `${runId}.json`) : resultPath;
|
|
125
|
+
if (fs.existsSync(hint))
|
|
126
|
+
fs.unlinkSync(hint);
|
|
127
|
+
};
|
|
128
|
+
data.completionId ??= `legacy:${runId}:${data.timestamp ?? "unknown"}`;
|
|
129
|
+
const completionKey = buildCompletionKey({ ...data, id: runId }, "result");
|
|
130
|
+
if (deps.reconcileDelivery ? deps.reconcileDelivery(runId, completionKey) : state.isRunResultConsumed?.(runId)) {
|
|
131
|
+
consumeNotification();
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
if (state.waitingRuns?.has(runId)) {
|
|
135
|
+
pi.events.emit(SUBAGENT_ASYNC_COMPLETE_EVENT, { ...data, runId, suppressNotification: true, intercomResultDelivered: false });
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
const hasExplicitNestedChildren = data.nestedChildren !== undefined;
|
|
139
|
+
let nestedChildren = compactNestedResultChildren(sanitizeNestedResultChildren(data.nestedChildren, resultPath, "nestedChildren"));
|
|
140
|
+
if (!nestedChildren?.length && !hasExplicitNestedChildren) {
|
|
141
|
+
try {
|
|
142
|
+
nestedChildren = compactNestedResultChildren(projectNestedRegistryForRoot(runId)?.children);
|
|
143
|
+
}
|
|
144
|
+
catch (error) {
|
|
145
|
+
console.error(`Failed to enrich subagent result file '${resultPath}' with nested registry children; will retry later:`, error);
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
const hasResultChildren = Array.isArray(data.results) && data.results.length > 0;
|
|
150
|
+
const resultChildren = hasResultChildren
|
|
151
|
+
? data.results
|
|
152
|
+
: [{
|
|
153
|
+
agent: data.agent,
|
|
154
|
+
output: data.summary,
|
|
155
|
+
success: data.success,
|
|
156
|
+
}];
|
|
157
|
+
const normalizedChildren = attachNestedChildrenToResultChildren(runId, resultChildren.map((result = {}, index) => {
|
|
158
|
+
const baseOutput = result.output ?? result.finalOutput ?? data.summary;
|
|
159
|
+
const hasRealOutput = typeof baseOutput === "string" && baseOutput.trim().length > 0;
|
|
160
|
+
const output = hasRealOutput ? baseOutput : "(no output)";
|
|
161
|
+
const summary = result.success === false && result.error
|
|
162
|
+
? `${result.error}${hasRealOutput ? `\n\nOutput:\n${baseOutput}` : ""}`
|
|
163
|
+
: output;
|
|
164
|
+
const sessionPath = result.sessionFile ?? (resultChildren.length === 1 ? data.sessionFile : undefined);
|
|
165
|
+
const childNestedChildren = sanitizeNestedResultChildren(result.children, resultPath, `results[${index}].children`);
|
|
166
|
+
return {
|
|
167
|
+
agent: result.agent ?? data.agent ?? `step-${index + 1}`,
|
|
168
|
+
status: resolveSubagentResultStatus({
|
|
169
|
+
success: result.success,
|
|
170
|
+
exitCode: result.exitCode ?? undefined,
|
|
171
|
+
acceptance: result.acceptance,
|
|
172
|
+
interrupted: result.interrupted,
|
|
173
|
+
state: result.interrupted || typeof result.success !== "boolean" ? data.state : undefined,
|
|
174
|
+
}),
|
|
175
|
+
summary,
|
|
176
|
+
index,
|
|
177
|
+
artifactPath: result.artifactPaths?.outputPath,
|
|
178
|
+
metadataPath: result.artifactPaths?.metadataPath,
|
|
179
|
+
...(typeof sessionPath === "string" && fs.existsSync(sessionPath) ? { sessionPath } : {}),
|
|
180
|
+
...(result.intercomTarget ? { intercomTarget: result.intercomTarget } : {}),
|
|
181
|
+
...(childNestedChildren ? { children: childNestedChildren } : {}),
|
|
182
|
+
};
|
|
183
|
+
}), nestedChildren);
|
|
184
|
+
if (processingCompletionKeys.has(completionKey))
|
|
185
|
+
return;
|
|
186
|
+
// Owned queue/receipt reconciliation above remains authoritative after TTL expiry.
|
|
187
|
+
if (markSeenWithTtl(state.completionSeen, completionKey, Date.now(), 10 * 60 * 1000)) {
|
|
188
|
+
consumeNotification();
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
processingCompletionKeys.add(completionKey);
|
|
192
|
+
claimedCompletionKey = completionKey;
|
|
193
|
+
// Saved ownership is stable; the owner's live intercom identity can change on restart.
|
|
194
|
+
const intercomTarget = resolveOrchestratorIntercomTarget(pi.events, data.intercomTarget?.trim() ?? "");
|
|
195
|
+
let intercomResultDelivered = false;
|
|
196
|
+
if (intercomTarget) {
|
|
197
|
+
const mode = data.mode === "single" || data.mode === "parallel" || data.mode === "chain"
|
|
198
|
+
? data.mode
|
|
199
|
+
: resultChildren.length > 1 ? "chain" : "single";
|
|
200
|
+
const savedResultPath = path.join(getRunMetadataDir(runId), "result.json");
|
|
201
|
+
const payload = buildSubagentResultIntercomPayload({
|
|
202
|
+
to: intercomTarget,
|
|
203
|
+
runId,
|
|
204
|
+
completionId: data.completionId,
|
|
205
|
+
mode,
|
|
206
|
+
source: "async",
|
|
207
|
+
...(fs.existsSync(savedResultPath) ? { resultPath: savedResultPath } : {}),
|
|
208
|
+
status: resolveSubagentResultStatus({ state: data.terminalState }),
|
|
209
|
+
error: data.workflowGraph?.nodes.find((node) => node.error)?.error,
|
|
210
|
+
children: normalizedChildren,
|
|
211
|
+
asyncId: data.id,
|
|
212
|
+
asyncDir: data.asyncDir,
|
|
213
|
+
});
|
|
214
|
+
intercomResultDelivered = await deliverSubagentResultIntercomEvent(pi.events, payload);
|
|
215
|
+
}
|
|
216
|
+
if (startedGeneration !== generation && startedGeneration !== joiningGeneration || ownerSessionId !== state.currentSessionId) {
|
|
217
|
+
state.completionSeen.delete(completionKey);
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
// Shutdown closes native turn ingress. Retain failed deliveries for the
|
|
221
|
+
// next startup, while still recording acknowledgements already accepted.
|
|
222
|
+
if (!intercomResultDelivered && startedGeneration < preserveBeforeGeneration)
|
|
223
|
+
return;
|
|
224
|
+
const { terminalState: _terminalState, ...eventData } = data;
|
|
225
|
+
pi.events.emit(SUBAGENT_ASYNC_COMPLETE_EVENT, {
|
|
226
|
+
...eventData,
|
|
227
|
+
agent: data.agent ?? normalizedChildren.map((child) => child.agent).join(", "),
|
|
228
|
+
summary: data.summary?.trim() ? data.summary : normalizedChildren.map((child) => child.summary).join("\n\n"),
|
|
229
|
+
runId,
|
|
230
|
+
completionKey,
|
|
231
|
+
intercomResultDelivered,
|
|
232
|
+
...(nestedChildren?.length ? { nestedChildren } : {}),
|
|
233
|
+
...(Array.isArray(data.results) ? {
|
|
234
|
+
results: hasResultChildren
|
|
235
|
+
? normalizedChildren.map((child, index) => ({
|
|
236
|
+
...data.results[index],
|
|
237
|
+
agent: child.agent,
|
|
238
|
+
status: child.status,
|
|
239
|
+
summary: child.summary,
|
|
240
|
+
index: child.index,
|
|
241
|
+
artifactPath: child.artifactPath,
|
|
242
|
+
sessionPath: child.sessionPath,
|
|
243
|
+
children: child.children,
|
|
244
|
+
}))
|
|
245
|
+
: [],
|
|
246
|
+
} : {}),
|
|
247
|
+
});
|
|
248
|
+
completionEmitted = true;
|
|
249
|
+
consumeNotification();
|
|
250
|
+
}
|
|
251
|
+
catch (error) {
|
|
252
|
+
if (isNotFoundError(error))
|
|
253
|
+
return;
|
|
254
|
+
console.error(`Failed to process subagent result file '${resultPath}':`, error);
|
|
255
|
+
}
|
|
256
|
+
finally {
|
|
257
|
+
if (claimedCompletionKey) {
|
|
258
|
+
processingCompletionKeys.delete(claimedCompletionKey);
|
|
259
|
+
if (!completionEmitted)
|
|
260
|
+
state.completionSeen.delete(claimedCompletionKey);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
};
|
|
264
|
+
const scheduled = new Set();
|
|
265
|
+
let drainGeneration;
|
|
266
|
+
const scheduleResult = (file) => {
|
|
267
|
+
scheduled.add(file);
|
|
268
|
+
if (drainGeneration === generation)
|
|
269
|
+
return;
|
|
270
|
+
drainGeneration = generation;
|
|
271
|
+
const scheduledGeneration = generation;
|
|
272
|
+
startTail = startTail.then(async () => {
|
|
273
|
+
try {
|
|
274
|
+
while (scheduled.size) {
|
|
275
|
+
await yieldToInput();
|
|
276
|
+
if (scheduledGeneration !== generation)
|
|
277
|
+
return;
|
|
278
|
+
const files = [...scheduled].slice(0, 4);
|
|
279
|
+
for (const file of files)
|
|
280
|
+
scheduled.delete(file);
|
|
281
|
+
// Only synchronous starts share publication proof. Async delivery
|
|
282
|
+
// resumes outside this scope; yield between batches for native input.
|
|
283
|
+
const start = () => {
|
|
284
|
+
for (const file of files) {
|
|
285
|
+
if (scheduledGeneration !== generation)
|
|
286
|
+
return;
|
|
287
|
+
const tail = handleResult(file);
|
|
288
|
+
inFlight.add(tail);
|
|
289
|
+
void tail.finally(() => inFlight.delete(tail));
|
|
290
|
+
}
|
|
291
|
+
};
|
|
292
|
+
if (deps.withReceiptBatch)
|
|
293
|
+
deps.withReceiptBatch(start);
|
|
294
|
+
else
|
|
295
|
+
start();
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
finally {
|
|
299
|
+
if (drainGeneration === scheduledGeneration)
|
|
300
|
+
drainGeneration = undefined;
|
|
301
|
+
}
|
|
302
|
+
});
|
|
303
|
+
};
|
|
304
|
+
state.resultFileCoalescer = createFileCoalescer(scheduleResult, 50);
|
|
305
|
+
const primeExistingResults = () => {
|
|
306
|
+
try {
|
|
307
|
+
// Admit one recovery scan synchronously; separate zero-delay file timers
|
|
308
|
+
// can split the same scan into additional verification batches.
|
|
309
|
+
pendingResultFiles().forEach(scheduleResult);
|
|
310
|
+
}
|
|
311
|
+
catch (error) {
|
|
312
|
+
if (isNotFoundError(error))
|
|
313
|
+
return;
|
|
314
|
+
console.error(`Failed to scan subagent result directory '${resultsDir}':`, error);
|
|
315
|
+
}
|
|
316
|
+
};
|
|
317
|
+
const ensurePeriodicScan = () => {
|
|
318
|
+
if (periodicScanTimer)
|
|
319
|
+
return;
|
|
320
|
+
periodicScanTimer = setInterval(primeExistingResults, POLL_INTERVAL_MS);
|
|
321
|
+
periodicScanTimer.unref?.();
|
|
322
|
+
};
|
|
323
|
+
const clearPeriodicScan = () => {
|
|
324
|
+
if (!periodicScanTimer)
|
|
325
|
+
return;
|
|
326
|
+
clearInterval(periodicScanTimer);
|
|
327
|
+
periodicScanTimer = null;
|
|
328
|
+
};
|
|
329
|
+
const startPollingFallback = (reason) => {
|
|
330
|
+
state.watcher?.close();
|
|
331
|
+
state.watcher = null;
|
|
332
|
+
clearPeriodicScan();
|
|
333
|
+
if (state.watcherRestartTimer)
|
|
334
|
+
return;
|
|
335
|
+
console.error(`Subagent result watcher for '${resultsDir}' fell back to polling because native fs.watch is unavailable (${getErrorCode(reason) ?? "unknown error"}).`);
|
|
336
|
+
primeExistingResults();
|
|
337
|
+
state.watcherRestartTimer = setInterval(primeExistingResults, POLL_INTERVAL_MS);
|
|
338
|
+
state.watcherRestartTimer.unref?.();
|
|
339
|
+
};
|
|
340
|
+
const scheduleRestart = () => {
|
|
341
|
+
if (state.watcherRestartTimer)
|
|
342
|
+
return;
|
|
343
|
+
state.watcherRestartTimer = setTimeout(() => {
|
|
344
|
+
state.watcherRestartTimer = null;
|
|
345
|
+
try {
|
|
346
|
+
fs.mkdirSync(resultsDir, { recursive: true });
|
|
347
|
+
startResultWatcher();
|
|
348
|
+
}
|
|
349
|
+
catch (error) {
|
|
350
|
+
if (shouldFallBackToPolling(error)) {
|
|
351
|
+
startPollingFallback(error);
|
|
352
|
+
return;
|
|
353
|
+
}
|
|
354
|
+
console.error(`Failed to restart subagent result watcher for '${resultsDir}':`, error);
|
|
355
|
+
scheduleRestart();
|
|
356
|
+
}
|
|
357
|
+
}, WATCHER_RESTART_DELAY_MS);
|
|
358
|
+
state.watcherRestartTimer.unref?.();
|
|
359
|
+
};
|
|
360
|
+
const startResultWatcher = () => {
|
|
361
|
+
if (state.watcher) {
|
|
362
|
+
ensurePeriodicScan();
|
|
363
|
+
return;
|
|
364
|
+
}
|
|
365
|
+
if (state.watcherRestartTimer) {
|
|
366
|
+
clearTimeout(state.watcherRestartTimer);
|
|
367
|
+
clearInterval(state.watcherRestartTimer);
|
|
368
|
+
state.watcherRestartTimer = null;
|
|
369
|
+
}
|
|
370
|
+
try {
|
|
371
|
+
state.watcher = fs.watch(resultsDir, (ev, file) => {
|
|
372
|
+
if (ev !== "rename" || !file)
|
|
373
|
+
return;
|
|
374
|
+
const fileName = file.toString();
|
|
375
|
+
if (!fileName.endsWith(".json"))
|
|
376
|
+
return;
|
|
377
|
+
if (!fs.existsSync(path.join(resultsDir, fileName)))
|
|
378
|
+
return;
|
|
379
|
+
state.resultFileCoalescer.schedule(fileName);
|
|
380
|
+
});
|
|
381
|
+
state.watcher.on("error", (error) => {
|
|
382
|
+
if (shouldFallBackToPolling(error)) {
|
|
383
|
+
startPollingFallback(error);
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
386
|
+
console.error(`Subagent result watcher failed for '${resultsDir}':`, error);
|
|
387
|
+
state.watcher?.close();
|
|
388
|
+
state.watcher = null;
|
|
389
|
+
scheduleRestart();
|
|
390
|
+
});
|
|
391
|
+
state.watcher.unref?.();
|
|
392
|
+
ensurePeriodicScan();
|
|
393
|
+
}
|
|
394
|
+
catch (error) {
|
|
395
|
+
if (shouldFallBackToPolling(error)) {
|
|
396
|
+
startPollingFallback(error);
|
|
397
|
+
return;
|
|
398
|
+
}
|
|
399
|
+
console.error(`Failed to start subagent result watcher for '${resultsDir}':`, error);
|
|
400
|
+
state.watcher = null;
|
|
401
|
+
scheduleRestart();
|
|
402
|
+
}
|
|
403
|
+
};
|
|
404
|
+
const stopResultWatcher = (options = {}) => {
|
|
405
|
+
joiningGeneration = options.joinInFlight ? generation : undefined;
|
|
406
|
+
generation++;
|
|
407
|
+
if (options.preservePending)
|
|
408
|
+
preserveBeforeGeneration = generation;
|
|
409
|
+
state.watcher?.close();
|
|
410
|
+
state.watcher = null;
|
|
411
|
+
clearPeriodicScan();
|
|
412
|
+
if (state.watcherRestartTimer) {
|
|
413
|
+
clearTimeout(state.watcherRestartTimer);
|
|
414
|
+
clearInterval(state.watcherRestartTimer);
|
|
415
|
+
}
|
|
416
|
+
state.watcherRestartTimer = null;
|
|
417
|
+
state.resultFileCoalescer.clear();
|
|
418
|
+
scheduled.clear();
|
|
419
|
+
foreignResults.clear();
|
|
420
|
+
};
|
|
421
|
+
const joinInFlight = async () => {
|
|
422
|
+
await Promise.all([...inFlight]);
|
|
423
|
+
joiningGeneration = undefined;
|
|
424
|
+
};
|
|
425
|
+
return { startResultWatcher, primeExistingResults, stopResultWatcher, joinInFlight };
|
|
426
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { ASYNC_DIR, RESULTS_DIR } from "../../shared/types.js";
|
|
2
|
+
import { formatRunIdAmbiguity } from "../shared/run-id-ambiguity.js";
|
|
3
|
+
import { exactAsyncRunLocation, findAsyncRunPrefixMatches } from "./async-resume.js";
|
|
4
|
+
import { assertSafeNestedId, findNestedRunMatchesById, findNestedRouteForRootId } from "../shared/nested-events.js";
|
|
5
|
+
function nestedScopeFromState(state) {
|
|
6
|
+
if (!state)
|
|
7
|
+
return undefined;
|
|
8
|
+
const routes = [];
|
|
9
|
+
const seen = new Set();
|
|
10
|
+
const add = (route) => {
|
|
11
|
+
if (!route)
|
|
12
|
+
return;
|
|
13
|
+
const key = `${route.rootRunId}:${route.eventSink}:${route.controlInbox}`;
|
|
14
|
+
if (seen.has(key))
|
|
15
|
+
return;
|
|
16
|
+
seen.add(key);
|
|
17
|
+
routes.push(route);
|
|
18
|
+
};
|
|
19
|
+
for (const run of state.ownedRuns?.values() ?? [])
|
|
20
|
+
add(findNestedRouteForRootId(run.runId));
|
|
21
|
+
for (const job of state.asyncJobs.values())
|
|
22
|
+
add(job.nestedRoute);
|
|
23
|
+
return { routes };
|
|
24
|
+
}
|
|
25
|
+
function asyncPrefixMatches(prefix, asyncDirRoot, resultsDir) {
|
|
26
|
+
return findAsyncRunPrefixMatches(prefix, asyncDirRoot, resultsDir);
|
|
27
|
+
}
|
|
28
|
+
export function resolveSubagentRunId(id, deps = {}) {
|
|
29
|
+
assertSafeNestedId("id", id);
|
|
30
|
+
const asyncDirRoot = deps.asyncDirRoot ?? ASYNC_DIR;
|
|
31
|
+
const resultsDir = deps.resultsDir ?? RESULTS_DIR;
|
|
32
|
+
const nestedScope = deps.nested ?? nestedScopeFromState(deps.state);
|
|
33
|
+
// In a session scope, storage discovery must not hide an authorized descendant route.
|
|
34
|
+
const preferDirectAsync = (runId) => !nestedScope || deps.state?.ownedRuns?.has(runId) || deps.state?.asyncJobs.has(runId);
|
|
35
|
+
const exactAsync = exactAsyncRunLocation(id, asyncDirRoot, resultsDir);
|
|
36
|
+
if ((exactAsync.asyncDir || exactAsync.resultPath) && preferDirectAsync(id))
|
|
37
|
+
return { kind: "async", id, location: exactAsync };
|
|
38
|
+
const exactNested = findNestedRunMatchesById(id, nestedScope ? { scope: nestedScope } : {});
|
|
39
|
+
if (exactNested.length > 1)
|
|
40
|
+
throw new Error(`Nested run id '${id}' is ambiguous across authorized registries. Provide the full id after stale registries are cleaned up.`);
|
|
41
|
+
if (exactNested[0])
|
|
42
|
+
return { kind: "nested", id, match: exactNested[0] };
|
|
43
|
+
if (exactAsync.asyncDir || exactAsync.resultPath)
|
|
44
|
+
return { kind: "async", id, location: exactAsync };
|
|
45
|
+
const asyncMatches = asyncPrefixMatches(id, asyncDirRoot, resultsDir);
|
|
46
|
+
const nestedMatches = findNestedRunMatchesById(id, nestedScope ? { prefix: true, scope: nestedScope } : { prefix: true });
|
|
47
|
+
const matches = [];
|
|
48
|
+
for (const match of asyncMatches) {
|
|
49
|
+
if (!preferDirectAsync(match.id) && nestedMatches.some((nested) => nested.run.id === match.id))
|
|
50
|
+
continue;
|
|
51
|
+
matches.push({ kind: "async", id: match.id, location: match.location });
|
|
52
|
+
}
|
|
53
|
+
for (const match of nestedMatches) {
|
|
54
|
+
if (preferDirectAsync(match.run.id) && asyncMatches.some((candidate) => candidate.id === match.run.id))
|
|
55
|
+
continue;
|
|
56
|
+
matches.push({ kind: "nested", id: match.run.id, match });
|
|
57
|
+
}
|
|
58
|
+
const unique = new Map(matches.map((match) => [match.kind === "nested" ? `nested:${match.match.rootRunId}:${match.id}` : `async:${match.id}`, match]));
|
|
59
|
+
const values = [...unique.values()];
|
|
60
|
+
if (values.length > 1) {
|
|
61
|
+
throw new Error(formatRunIdAmbiguity("subagent", id, values.map((match) => `${match.kind}:${match.id}`)));
|
|
62
|
+
}
|
|
63
|
+
return values[0];
|
|
64
|
+
}
|