pi-subagents 0.48.0 → 0.50.0
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 +82 -19
- package/agents/oracle.md +7 -5
- package/agents/researcher.md +1 -1
- package/agents/reviewer.md +2 -2
- package/agents/scout.md +1 -1
- package/agents/worker.md +1 -1
- package/docs/agents.md +3 -0
- package/docs/configuration.md +75 -8
- package/docs/extension-api.md +36 -0
- package/docs/missions.md +5 -3
- package/docs/observability.md +42 -2
- package/docs/tool-reference.md +19 -2
- package/docs/workflows.md +2 -2
- package/package.json +1 -1
- package/skills/pi-subagents/references/constraints-and-recipes.md +3 -2
- package/skills/pi-subagents/references/execution-controls.md +5 -4
- package/skills/pi-subagents/references/management-authoring-rpc.md +2 -2
- package/skills/pi-subagents/references/prompting-and-roles.md +31 -15
- package/src/agents/agent-serializer.ts +2 -0
- package/src/agents/agents.ts +37 -12
- package/src/api/external-runs.ts +174 -84
- package/src/api/preflight.ts +13 -7
- package/src/extension/config.ts +53 -0
- package/src/extension/index.ts +62 -9
- package/src/extension/public-execution.ts +32 -4
- package/src/extension/rpc.ts +5 -1
- package/src/extension/schemas.ts +10 -8
- package/src/extension/tool-description.ts +13 -9
- package/src/inspectors/herdr/actions.ts +13 -8
- package/src/inspectors/herdr/inspector-runner.ts +16 -3
- package/src/inspectors/herdr/project-panes.ts +2 -6
- package/src/inspectors/herdr/shell-command.ts +16 -0
- package/src/intercom/intercom-bridge.ts +5 -4
- package/src/intercom/native-supervisor-channel.ts +19 -42
- package/src/missions/goal-driver.ts +3 -1
- package/src/missions/store.ts +8 -3
- package/src/runs/background/active-async-capacity.ts +82 -25
- package/src/runs/background/active-run-index.ts +71 -1
- package/src/runs/background/async-execution.ts +73 -43
- package/src/runs/background/async-job-tracker.ts +5 -0
- package/src/runs/background/async-resume.ts +14 -6
- package/src/runs/background/async-status-snapshot.ts +277 -0
- package/src/runs/background/async-status.ts +8 -3
- package/src/runs/background/chain-root-attachment.ts +2 -2
- package/src/runs/background/completion-replay.ts +11 -1
- package/src/runs/background/fleet-view.ts +21 -6
- package/src/runs/background/result-files.ts +437 -0
- package/src/runs/background/result-watcher.ts +188 -41
- package/src/runs/background/resume-guidance.ts +27 -7
- package/src/runs/background/retained-children.ts +75 -18
- package/src/runs/background/run-id-resolver.ts +30 -24
- package/src/runs/background/run-status.ts +101 -4
- package/src/runs/background/scheduled-runs.ts +54 -28
- package/src/runs/background/stale-run-reconciler.ts +27 -13
- package/src/runs/background/subagent-runner.ts +298 -33
- package/src/runs/background/subagent-wait.ts +2 -0
- package/src/runs/background/wait-completions.ts +5 -2
- package/src/runs/foreground/async-dismiss-action.ts +2 -1
- package/src/runs/foreground/chain-execution.ts +16 -0
- package/src/runs/foreground/execution.ts +219 -15
- package/src/runs/foreground/prompt-audit.ts +4 -3
- package/src/runs/foreground/subagent-executor.ts +328 -43
- package/src/runs/shared/completion-guard.ts +107 -1
- package/src/runs/shared/external-cli-runner.ts +4 -0
- package/src/runs/shared/llm-intent-arbiter.ts +39 -23
- package/src/runs/shared/model-fallback.ts +16 -2
- package/src/runs/shared/nested-events.ts +66 -62
- package/src/runs/shared/orca-progress-tabs.ts +375 -0
- package/src/runs/shared/parallel-utils.ts +2 -0
- package/src/runs/shared/subagent-control.ts +15 -0
- package/src/runs/shared/subagent-prompt-runtime.ts +1 -9
- package/src/runs/shared/subagent-startup-retry.ts +12 -0
- package/src/runs/shared/tool-timeout.ts +93 -0
- package/src/shared/agent-stream-options.ts +5 -0
- package/src/shared/artifacts.ts +2 -6
- package/src/shared/display-text.ts +50 -0
- package/src/shared/node-executable.ts +21 -0
- package/src/shared/types.ts +51 -5
- package/src/slash/slash-commands.ts +34 -25
- package/src/slash/slash-live-state.ts +3 -0
- package/src/tui/fleet-status.ts +160 -45
- package/src/tui/fleet-transcript.ts +1 -48
- package/src/tui/fleet.ts +128 -16
- package/src/tui/render.ts +122 -44
- package/src/watchdog/permission-arbiter.ts +2 -1
- package/src/watchdog/review.ts +4 -3
- package/src/workflows/chat-progress.ts +10 -2
- package/src/workflows/scripted-workflow.ts +272 -76
|
@@ -5,6 +5,7 @@ import * as path from "node:path";
|
|
|
5
5
|
import { pathToFileURL } from "node:url";
|
|
6
6
|
import type { Message } from "@earendil-works/pi-ai";
|
|
7
7
|
import { writeAtomicJson } from "../../shared/atomic-json.ts";
|
|
8
|
+
import { writeAsyncResultFile, writePendingAsyncResultFile } from "./result-files.ts";
|
|
8
9
|
import { createFileCoalescer } from "../../shared/file-coalescer.ts";
|
|
9
10
|
import { isActiveAsyncState, updateActiveRunIndex } from "./active-run-index.ts";
|
|
10
11
|
import { createChildTranscriptWriter, type ChildTranscriptWriter } from "../../shared/child-transcript.ts";
|
|
@@ -57,6 +58,7 @@ import {
|
|
|
57
58
|
claimControlNotification,
|
|
58
59
|
formatControlIntercomMessage,
|
|
59
60
|
formatControlNoticeMessage,
|
|
61
|
+
shouldEmitOpenToolAttention,
|
|
60
62
|
} from "../shared/subagent-control.ts";
|
|
61
63
|
import {
|
|
62
64
|
type RunnerSubagentStep as SubagentStep,
|
|
@@ -83,6 +85,7 @@ import { nestedSummaryFromAsyncStatus, projectNestedEvents, resolveNestedAsyncDi
|
|
|
83
85
|
import { formatModelAttemptNote, isRetryableModelFailure } from "../shared/model-fallback.ts";
|
|
84
86
|
import {
|
|
85
87
|
SUBAGENT_STARTUP_RETRY_DELAYS_MS,
|
|
88
|
+
formatSubagentExtensionConflictError,
|
|
86
89
|
formatSubagentStartupRetryExhaustedError,
|
|
87
90
|
formatSubagentStartupRetryNote,
|
|
88
91
|
isRetryableSubagentStartupFailure,
|
|
@@ -126,12 +129,14 @@ import { waitForImportedAsyncRoot } from "./chain-root-attachment.ts";
|
|
|
126
129
|
import { appendRunnerStepsToStatus, consumeChainAppendRequests, countPendingChainAppendRequests, statusStepDescription } from "./chain-append.ts";
|
|
127
130
|
import { appendTurnBudgetSystemPrompt, formatTurnBudgetOutput, initialTurnBudgetState, turnBudgetDecision, turnBudgetDeferredNote, turnBudgetDeferredState, turnBudgetExceededMessage, turnBudgetSoftNote, turnBudgetState } from "../shared/turn-budget.ts";
|
|
128
131
|
import { initialToolBudgetState, toolBudgetState } from "../shared/tool-budget.ts";
|
|
132
|
+
import { effectiveToolTimeoutMs, formatToolTimeoutMessage, toolTimeoutCallKey } from "../shared/tool-timeout.ts";
|
|
129
133
|
import { usageBudgetExceededMessage, usageBudgetState } from "../shared/usage-budget.ts";
|
|
130
134
|
import { formatParallelHandoffError, formatParallelHandoffReference, parallelHandoffPath, writeParallelHandoffGroup, writePendingParallelHandoff } from "../shared/parallel-handoff.ts";
|
|
131
135
|
import { resolveWatchdogConfig } from "../../watchdog/settings.ts";
|
|
132
136
|
import { createBoundedByteTail, createBoundedLineReader, formatProtocolOutputLimit, MAX_CHILD_STDERR_BYTES, PI_AGGREGATE_EVENT_PROJECTOR, projectChildLifecycle, type ChildLifecycleAction, type ProtocolOutputLimit } from "../shared/child-protocol.ts";
|
|
133
137
|
import { acquireSessionLease, type SessionLeaseRequest } from "../shared/session-lease.ts";
|
|
134
138
|
import { buildExternalCliPrompt, runExternalCli } from "../shared/external-cli-runner.ts";
|
|
139
|
+
import { createOrcaProgressTab, type OrcaProgressTab } from "../shared/orca-progress-tabs.ts";
|
|
135
140
|
import { decodeSubagentCapabilityCeiling, SUBAGENT_CAPABILITY_CEILING_ENV, type ResolvedSubagentCapabilityCeiling } from "../shared/capability-ceiling.ts";
|
|
136
141
|
import {
|
|
137
142
|
CHILD_WATCHDOG_CONFIG_ENV,
|
|
@@ -143,6 +148,8 @@ import {
|
|
|
143
148
|
type ChildWatchdogStateSnapshot,
|
|
144
149
|
} from "../../watchdog/child-status.ts";
|
|
145
150
|
|
|
151
|
+
const INTERCOM_DETACH_RECEIPT = "Detached for intercom coordination before task completion.";
|
|
152
|
+
|
|
146
153
|
interface SubagentRunConfig {
|
|
147
154
|
id: string;
|
|
148
155
|
steps: RunnerStep[];
|
|
@@ -174,6 +181,8 @@ interface SubagentRunConfig {
|
|
|
174
181
|
nestedSelf?: { parentRunId: string; parentStepIndex?: number; depth: number; path?: Array<{ runId: string; stepIndex?: number; agent?: string }> };
|
|
175
182
|
timeoutMs?: number;
|
|
176
183
|
deadlineAt?: number;
|
|
184
|
+
/** Resolved configured hard per-tool-call timeout (ms); fast tools still have a default when undefined. */
|
|
185
|
+
toolTimeoutMs?: number;
|
|
177
186
|
turnBudget?: ResolvedTurnBudget;
|
|
178
187
|
toolBudget?: ResolvedToolBudget;
|
|
179
188
|
usageBudget?: UsageBudgetConfig;
|
|
@@ -539,6 +548,9 @@ function runPiStreaming(
|
|
|
539
548
|
stopMessage?: string,
|
|
540
549
|
registerTurnBudgetAbort?: (abort: ((message: string, state?: TurnBudgetState) => void) | undefined) => void,
|
|
541
550
|
onWriterProcess?: (writer: { state: "none" | "spawning" } | { state: "running"; pid: number }) => void,
|
|
551
|
+
toolTimeoutMs?: number,
|
|
552
|
+
runDeadlineAt?: number,
|
|
553
|
+
orcaProgressTab?: OrcaProgressTab,
|
|
542
554
|
): Promise<RunPiStreamingResult> {
|
|
543
555
|
return new Promise((resolve) => {
|
|
544
556
|
const startedAt = Date.now();
|
|
@@ -595,6 +607,7 @@ function runPiStreaming(
|
|
|
595
607
|
const writeOutputLine = (line: string) => {
|
|
596
608
|
if (!line.trim()) return;
|
|
597
609
|
outputStream.write(`${line}\n`);
|
|
610
|
+
orcaProgressTab?.append(`${line}\n`);
|
|
598
611
|
};
|
|
599
612
|
|
|
600
613
|
const writeOutputText = (text: string) => {
|
|
@@ -672,8 +685,14 @@ function runPiStreaming(
|
|
|
672
685
|
|
|
673
686
|
onChildEvent?.(event);
|
|
674
687
|
|
|
688
|
+
if (event.type === "tool_execution_end") {
|
|
689
|
+
clearActiveToolTimeout(event);
|
|
690
|
+
return;
|
|
691
|
+
}
|
|
692
|
+
|
|
675
693
|
if (event.type === "tool_execution_start" && event.toolName) {
|
|
676
694
|
toolCount += 1;
|
|
695
|
+
armToolTimeout({ toolCallId: (event as { toolCallId?: unknown }).toolCallId, toolName: event.toolName });
|
|
677
696
|
if (event.toolName === "structured_output") {
|
|
678
697
|
structuredOutputToolInvoked = true;
|
|
679
698
|
structuredOutputMessageStartIndex = messages.length;
|
|
@@ -769,6 +788,7 @@ function runPiStreaming(
|
|
|
769
788
|
stderrTail.push(chunk);
|
|
770
789
|
stderrReader.push(chunk);
|
|
771
790
|
outputStream.write(chunk);
|
|
791
|
+
orcaProgressTab?.append(chunk.toString("utf-8"));
|
|
772
792
|
});
|
|
773
793
|
registerInterrupt?.(() => {
|
|
774
794
|
if (settled || timedOut || stopped) return;
|
|
@@ -779,14 +799,60 @@ function runPiStreaming(
|
|
|
779
799
|
if (!settled && !timedOut && !stopped) trySignalChild(child, "SIGTERM");
|
|
780
800
|
}, 1000).unref?.();
|
|
781
801
|
});
|
|
782
|
-
|
|
802
|
+
const terminateForTimeout = (message: string): void => {
|
|
783
803
|
if (settled || timedOut || stopped) return;
|
|
784
804
|
timedOut = true;
|
|
805
|
+
// runPiStreaming's terminal result derives the timeout error from this
|
|
806
|
+
// message, so retain the tool-specific reason through finalization.
|
|
807
|
+
timeoutMessage = message;
|
|
785
808
|
interrupted = false;
|
|
786
|
-
error =
|
|
809
|
+
error = message;
|
|
787
810
|
if (processTreeController) void processTreeController.terminate();
|
|
788
811
|
else trySignalChild(child, "SIGTERM");
|
|
789
|
-
}
|
|
812
|
+
};
|
|
813
|
+
let toolTimeoutSequence = 0;
|
|
814
|
+
const activeToolTimeouts = new Map<string, { toolName: string; timer: ReturnType<typeof setTimeout> }>();
|
|
815
|
+
const activeToolTimeoutKeysByName = new Map<string, string[]>();
|
|
816
|
+
const removeToolTimeoutKey = (key: string): void => {
|
|
817
|
+
const active = activeToolTimeouts.get(key);
|
|
818
|
+
if (!active) return;
|
|
819
|
+
clearTimeout(active.timer);
|
|
820
|
+
activeToolTimeouts.delete(key);
|
|
821
|
+
const keys = activeToolTimeoutKeysByName.get(active.toolName)?.filter((candidate) => candidate !== key) ?? [];
|
|
822
|
+
if (keys.length > 0) activeToolTimeoutKeysByName.set(active.toolName, keys);
|
|
823
|
+
else activeToolTimeoutKeysByName.delete(active.toolName);
|
|
824
|
+
};
|
|
825
|
+
const clearActiveToolTimeout = (event: { toolCallId?: unknown; toolName?: unknown }): void => {
|
|
826
|
+
const key = typeof event.toolCallId === "string" && event.toolCallId.length > 0
|
|
827
|
+
? `id:${event.toolCallId}`
|
|
828
|
+
: typeof event.toolName === "string"
|
|
829
|
+
? activeToolTimeoutKeysByName.get(event.toolName)?.[0]
|
|
830
|
+
: activeToolTimeouts.size === 1
|
|
831
|
+
? [...activeToolTimeouts.keys()][0]
|
|
832
|
+
: undefined;
|
|
833
|
+
if (key) removeToolTimeoutKey(key);
|
|
834
|
+
};
|
|
835
|
+
const clearAllToolTimeouts = (): void => {
|
|
836
|
+
for (const key of [...activeToolTimeouts.keys()]) removeToolTimeoutKey(key);
|
|
837
|
+
};
|
|
838
|
+
const armToolTimeout = (event: { toolCallId?: unknown; toolName: string }): void => {
|
|
839
|
+
const timeoutForTool = effectiveToolTimeoutMs(event.toolName, toolTimeoutMs);
|
|
840
|
+
if (timeoutForTool === undefined) return;
|
|
841
|
+
const runRemaining = runDeadlineAt === undefined ? undefined : Math.max(0, runDeadlineAt - Date.now());
|
|
842
|
+
if (runRemaining !== undefined && timeoutForTool >= runRemaining) return;
|
|
843
|
+
const key = toolTimeoutCallKey(event, ++toolTimeoutSequence);
|
|
844
|
+
const toolName = event.toolName;
|
|
845
|
+
const timer = setTimeout(() => {
|
|
846
|
+
removeToolTimeoutKey(key);
|
|
847
|
+
terminateForTimeout(formatToolTimeoutMessage(toolName, timeoutForTool));
|
|
848
|
+
}, timeoutForTool);
|
|
849
|
+
timer.unref?.();
|
|
850
|
+
activeToolTimeouts.set(key, { toolName, timer });
|
|
851
|
+
const keys = activeToolTimeoutKeysByName.get(toolName) ?? [];
|
|
852
|
+
keys.push(key);
|
|
853
|
+
activeToolTimeoutKeysByName.set(toolName, keys);
|
|
854
|
+
};
|
|
855
|
+
registerTimeout?.(() => terminateForTimeout(timeoutMessage ?? "Subagent timed out."));
|
|
790
856
|
registerStop?.(() => {
|
|
791
857
|
if (settled || timedOut || stopped) return;
|
|
792
858
|
stopped = true;
|
|
@@ -813,6 +879,7 @@ function runPiStreaming(
|
|
|
813
879
|
turnBudgetHardKillTimer.unref?.();
|
|
814
880
|
});
|
|
815
881
|
const clearDrainTimers = () => {
|
|
882
|
+
clearAllToolTimeouts();
|
|
816
883
|
if (finalDrainTimer) {
|
|
817
884
|
clearTimeout(finalDrainTimer);
|
|
818
885
|
finalDrainTimer = undefined;
|
|
@@ -1102,6 +1169,10 @@ interface SingleStepContext {
|
|
|
1102
1169
|
stopSignal?: AbortSignal;
|
|
1103
1170
|
timeoutMessage?: string;
|
|
1104
1171
|
stopMessage?: string;
|
|
1172
|
+
/** Resolved configured hard per-tool-call timeout (ms); fast tools still have a default when undefined. */
|
|
1173
|
+
toolTimeoutMs?: number;
|
|
1174
|
+
/** Effective step deadline (Date.now() + effective timeout) when a run budget exists. */
|
|
1175
|
+
deadlineAt?: number;
|
|
1105
1176
|
turnBudget?: ResolvedTurnBudget;
|
|
1106
1177
|
childIntercomTarget?: string;
|
|
1107
1178
|
orchestratorIntercomTarget?: string;
|
|
@@ -1113,10 +1184,11 @@ interface SingleStepContext {
|
|
|
1113
1184
|
onWriterProcess?: (writer: { state: "none" | "spawning" } | { state: "running"; pid: number }) => void;
|
|
1114
1185
|
onExternalProcess?: (process: ExternalProcessStatus) => void;
|
|
1115
1186
|
skipAcceptance?: () => boolean;
|
|
1187
|
+
orcaProgressTab?: OrcaProgressTab;
|
|
1116
1188
|
}
|
|
1117
1189
|
|
|
1118
1190
|
/** Run a single pi agent step, returning output and metadata */
|
|
1119
|
-
async function
|
|
1191
|
+
async function runSingleStepInner(
|
|
1120
1192
|
step: SubagentStep,
|
|
1121
1193
|
ctx: SingleStepContext,
|
|
1122
1194
|
): Promise<StepResult & { completionGuardTriggered?: boolean }> {
|
|
@@ -1245,6 +1317,8 @@ async function runSingleStep(
|
|
|
1245
1317
|
timeoutMessage: ctx.timeoutMessage,
|
|
1246
1318
|
stopMessage: ctx.stopMessage,
|
|
1247
1319
|
onProcess: ctx.onExternalProcess,
|
|
1320
|
+
onStdout: (chunk) => ctx.orcaProgressTab?.append(chunk.toString("utf-8")),
|
|
1321
|
+
onStderr: (chunk) => ctx.orcaProgressTab?.append(chunk.toString("utf-8")),
|
|
1248
1322
|
}));
|
|
1249
1323
|
try { fs.writeFileSync(ctx.outputFile, external.output, "utf-8"); } catch { /* Observability output is best-effort. */ }
|
|
1250
1324
|
const resolvedOutput = step.outputPath && external.exitCode === 0
|
|
@@ -1431,6 +1505,9 @@ async function runSingleStep(
|
|
|
1431
1505
|
ctx.stopMessage,
|
|
1432
1506
|
ctx.registerTurnBudgetAbort,
|
|
1433
1507
|
ctx.onWriterProcess,
|
|
1508
|
+
ctx.toolTimeoutMs,
|
|
1509
|
+
ctx.deadlineAt,
|
|
1510
|
+
ctx.orcaProgressTab,
|
|
1434
1511
|
);
|
|
1435
1512
|
if (run.processCloseObservedAt !== undefined) {
|
|
1436
1513
|
writerProcesses.push({
|
|
@@ -1538,15 +1615,21 @@ async function runSingleStep(
|
|
|
1538
1615
|
stopped: run.stopped,
|
|
1539
1616
|
turnBudgetExceeded: run.turnBudgetExceeded,
|
|
1540
1617
|
})) ? formatProcessSignalError(run.processSignal!) : undefined;
|
|
1541
|
-
const error =
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
?
|
|
1548
|
-
|
|
1549
|
-
|
|
1618
|
+
const error = formatSubagentExtensionConflictError(
|
|
1619
|
+
toolAvailabilityError
|
|
1620
|
+
?? completionGuardError
|
|
1621
|
+
?? structuredError
|
|
1622
|
+
?? emptyOutputError
|
|
1623
|
+
?? (hiddenError?.hasError
|
|
1624
|
+
? hiddenError.details
|
|
1625
|
+
? `${hiddenError.errorType} failed (exit ${effectiveExitCode}): ${hiddenError.details}`
|
|
1626
|
+
: `${hiddenError.errorType} failed with exit code ${effectiveExitCode}`
|
|
1627
|
+
: run.error || signalError || (run.exitCode !== 0 && run.stderr.trim() ? run.stderr.trim() : undefined)),
|
|
1628
|
+
{
|
|
1629
|
+
agent: step.agent,
|
|
1630
|
+
ambientExtensionsEnabled: launchResolvedExtensions?.disableAmbientExtensions === false,
|
|
1631
|
+
},
|
|
1632
|
+
);
|
|
1550
1633
|
const attempt: ModelAttempt = omitUndefinedProperties({
|
|
1551
1634
|
model: candidate ?? run.model ?? step.model ?? "default",
|
|
1552
1635
|
success: effectiveExitCode === 0 && !error,
|
|
@@ -1693,15 +1776,16 @@ async function runSingleStep(
|
|
|
1693
1776
|
const acceptanceFailure = effectiveAcceptance ? acceptanceFailureMessage(effectiveAcceptance) : undefined;
|
|
1694
1777
|
const acceptanceCanFailRun = acceptanceFailure && effectiveAcceptance?.explicit && (finalResult?.exitCode ?? 1) === 0 && !finalResult?.interrupted && !timedOutAfterAcceptance && !stoppedAfterAcceptance && !turnBudgetExceeded && !isAgentContractV1(step.agentContract);
|
|
1695
1778
|
const effectiveFinalExitCode = timedOutAfterAcceptance || stoppedAfterAcceptance || turnBudgetExceeded ? 1 : acceptanceCanFailRun ? 1 : finalResult?.exitCode ?? 1;
|
|
1779
|
+
const intercomDetachReceipt = finalResult?.finalOutput === INTERCOM_DETACH_RECEIPT;
|
|
1696
1780
|
const effectiveFinalError = stoppedAfterAcceptance
|
|
1697
1781
|
? ctx.stopMessage ?? "Subagent stopped by user."
|
|
1698
1782
|
: timedOutAfterAcceptance
|
|
1699
|
-
? ctx.timeoutMessage ?? "Subagent timed out."
|
|
1783
|
+
? finalResult?.error ?? ctx.timeoutMessage ?? "Subagent timed out."
|
|
1700
1784
|
: turnBudgetExceeded
|
|
1701
1785
|
? finalResult?.error ?? (turnBudget ? turnBudgetExceededMessage(turnBudget, turnBudget.turnCount) : "Subagent exceeded turn budget.")
|
|
1702
1786
|
: acceptanceCanFailRun
|
|
1703
1787
|
? (finalResult?.error ? `${finalResult.error}\n${acceptanceFailure}` : acceptanceFailure)
|
|
1704
|
-
: finalResult?.error;
|
|
1788
|
+
: finalResult?.error ?? (intercomDetachReceipt ? INTERCOM_DETACH_RECEIPT : undefined);
|
|
1705
1789
|
|
|
1706
1790
|
const artifactErrors = artifactPaths && ctx.artifactConfig?.enabled !== false
|
|
1707
1791
|
? persistStepArtifacts({
|
|
@@ -1781,6 +1865,27 @@ async function runSingleStep(
|
|
|
1781
1865
|
return isAgentContractV1(step.agentContract) ? attachContractProjections(result as unknown as import("../../shared/types.ts").SingleResult) as unknown as typeof result : result;
|
|
1782
1866
|
}
|
|
1783
1867
|
|
|
1868
|
+
async function runSingleStep(
|
|
1869
|
+
step: SubagentStep,
|
|
1870
|
+
ctx: SingleStepContext,
|
|
1871
|
+
): Promise<StepResult & { completionGuardTriggered?: boolean }> {
|
|
1872
|
+
if (step.importAsyncRoot) return runSingleStepInner(step, ctx);
|
|
1873
|
+
const orcaProgressTab = createOrcaProgressTab({
|
|
1874
|
+
cwd: step.cwd ?? ctx.cwd,
|
|
1875
|
+
runId: ctx.id,
|
|
1876
|
+
agent: step.agent,
|
|
1877
|
+
index: ctx.flatIndex,
|
|
1878
|
+
});
|
|
1879
|
+
try {
|
|
1880
|
+
const result = await runSingleStepInner(step, { ...ctx, orcaProgressTab });
|
|
1881
|
+
orcaProgressTab?.finish(result.stopped ? "stopped" : result.exitCode === 0 && !result.error ? "completed" : "failed", result.sessionFile);
|
|
1882
|
+
return result;
|
|
1883
|
+
} catch (error) {
|
|
1884
|
+
orcaProgressTab?.finish("failed");
|
|
1885
|
+
throw error;
|
|
1886
|
+
}
|
|
1887
|
+
}
|
|
1888
|
+
|
|
1784
1889
|
type RunnerStatusStep = NonNullable<AsyncStatus["steps"]>[number] & {
|
|
1785
1890
|
exitCode?: number | null;
|
|
1786
1891
|
description?: string;
|
|
@@ -2005,6 +2110,7 @@ async function runSingleStepWithTimeout(
|
|
|
2005
2110
|
return await runSingleStep(step, {
|
|
2006
2111
|
...ctx,
|
|
2007
2112
|
registerTimeout,
|
|
2113
|
+
deadlineAt: Date.now() + timeoutMs,
|
|
2008
2114
|
timeoutSignal: combinedAbortSignal([ctx.timeoutSignal, timeoutController.signal]),
|
|
2009
2115
|
timeoutMessage,
|
|
2010
2116
|
});
|
|
@@ -2199,7 +2305,7 @@ async function runSubagent(
|
|
|
2199
2305
|
|
|
2200
2306
|
fs.mkdirSync(asyncDir, { recursive: true });
|
|
2201
2307
|
writeAtomicJson(statusPath, statusPayload);
|
|
2202
|
-
updateActiveRunIndex(asyncDir, statusPayload.state);
|
|
2308
|
+
updateActiveRunIndex(asyncDir, statusPayload.state, statusPayload.toolCallId);
|
|
2203
2309
|
let pendingParallelUsageCost: CostSummary = { inputTokens: 0, outputTokens: 0, costUsd: 0 };
|
|
2204
2310
|
const currentUsageTotals = (): CostSummary => {
|
|
2205
2311
|
const cost = results.reduce<CostSummary>((sum, result) => ({
|
|
@@ -2272,13 +2378,70 @@ async function runSubagent(
|
|
|
2272
2378
|
for (const node of graph.nodes) updateNode(node);
|
|
2273
2379
|
statusPayload.workflowGraph = graph;
|
|
2274
2380
|
};
|
|
2381
|
+
let finalResultCommitted = false;
|
|
2275
2382
|
let lastIndexedActiveState = isActiveAsyncState(statusPayload.state);
|
|
2383
|
+
const statusResultState = (): AsyncStatus["state"] | undefined => {
|
|
2384
|
+
if (statusPayload.state === "running" || statusPayload.state === "queued") return undefined;
|
|
2385
|
+
if (statusPayload.state === "paused" && statusPayload.checkpoint?.status === "pending") return undefined;
|
|
2386
|
+
return statusPayload.state;
|
|
2387
|
+
};
|
|
2388
|
+
const statusResultSummary = (state: AsyncStatus["state"]): string => {
|
|
2389
|
+
if (statusPayload.error) return statusPayload.error;
|
|
2390
|
+
if (state === "paused") return "Paused after interrupt. Waiting for explicit next action.";
|
|
2391
|
+
if (state === "stopped") return stopMessage;
|
|
2392
|
+
if (state === "rejected") return statusPayload.checkpoint?.name ? `Checkpoint '${statusPayload.checkpoint.name}' rejected.` : "Subagent rejected.";
|
|
2393
|
+
return state === "complete" ? "Subagent completed." : "Subagent failed.";
|
|
2394
|
+
};
|
|
2395
|
+
const statusResultSuccess = (state: AsyncStatus["state"], step: RunnerStatusStep): boolean | undefined => {
|
|
2396
|
+
if (step.status === "complete" || step.status === "completed") return true;
|
|
2397
|
+
if (step.status === "failed" || step.status === "stopped" || step.status === "rejected") return false;
|
|
2398
|
+
if (state === "complete") return true;
|
|
2399
|
+
if (state === "failed" || state === "stopped" || state === "rejected") return false;
|
|
2400
|
+
return undefined;
|
|
2401
|
+
};
|
|
2402
|
+
const writeRecoverableStatusResult = (): void => {
|
|
2403
|
+
const state = statusResultState();
|
|
2404
|
+
if (!state || finalResultCommitted || !config.sessionId) return;
|
|
2405
|
+
const now = statusPayload.endedAt ?? statusPayload.lastUpdate ?? Date.now();
|
|
2406
|
+
const summary = statusResultSummary(state);
|
|
2407
|
+
writePendingAsyncResultFile(resultPath, omitUndefinedProperties({
|
|
2408
|
+
lifecycleArtifactVersion: SUBAGENT_LIFECYCLE_ARTIFACT_VERSION,
|
|
2409
|
+
id,
|
|
2410
|
+
runId: id,
|
|
2411
|
+
agent: statusPayload.steps.length === 1 ? statusPayload.steps[0]!.agent : statusPayload.mode === "parallel" ? `parallel:${statusPayload.steps.map((step) => step.agent).join("+")}` : `chain:${statusPayload.steps.map((step) => step.agent).join("->")}`,
|
|
2412
|
+
mode: statusPayload.mode,
|
|
2413
|
+
success: state === "complete",
|
|
2414
|
+
state,
|
|
2415
|
+
summary,
|
|
2416
|
+
error: state === "failed" || state === "stopped" || state === "rejected" ? summary : undefined,
|
|
2417
|
+
stopped: state === "stopped" ? true : undefined,
|
|
2418
|
+
checkpoint: statusPayload.checkpoint,
|
|
2419
|
+
results: statusPayload.steps.map((step) => omitUndefinedProperties({
|
|
2420
|
+
agent: step.agent,
|
|
2421
|
+
output: step.status === "complete" || step.status === "completed" ? "" : step.error ?? summary,
|
|
2422
|
+
error: step.error,
|
|
2423
|
+
success: statusResultSuccess(state, step),
|
|
2424
|
+
sessionFile: step.sessionFile,
|
|
2425
|
+
model: step.model,
|
|
2426
|
+
attemptedModels: step.attemptedModels,
|
|
2427
|
+
modelAttempts: step.modelAttempts,
|
|
2428
|
+
})),
|
|
2429
|
+
exitCode: state === "complete" || state === "paused" ? 0 : 1,
|
|
2430
|
+
timestamp: now,
|
|
2431
|
+
durationMs: Math.max(0, now - overallStartTime),
|
|
2432
|
+
asyncDir,
|
|
2433
|
+
cwd,
|
|
2434
|
+
sessionId: config.sessionId,
|
|
2435
|
+
sessionFile: statusPayload.sessionFile ?? latestSessionFile,
|
|
2436
|
+
}));
|
|
2437
|
+
};
|
|
2276
2438
|
const writeStatusPayloadNow = (): void => {
|
|
2277
2439
|
refreshWorkflowGraph();
|
|
2440
|
+
writeRecoverableStatusResult();
|
|
2278
2441
|
writeAtomicJson(statusPath, statusPayload);
|
|
2279
2442
|
const activeState = isActiveAsyncState(statusPayload.state);
|
|
2280
|
-
if (activeState
|
|
2281
|
-
updateActiveRunIndex(asyncDir, statusPayload.state);
|
|
2443
|
+
if (activeState !== lastIndexedActiveState) {
|
|
2444
|
+
updateActiveRunIndex(asyncDir, statusPayload.state, statusPayload.toolCallId);
|
|
2282
2445
|
}
|
|
2283
2446
|
lastIndexedActiveState = activeState;
|
|
2284
2447
|
emitNestedSelfEvent(statusPayload.state === "running" || statusPayload.state === "queued" ? "subagent.nested.updated" : "subagent.nested.completed");
|
|
@@ -2523,6 +2686,72 @@ async function runSubagent(
|
|
|
2523
2686
|
const activeLongRunningSteps = new Set<number>();
|
|
2524
2687
|
const mutatingFailureStates = initialStatusSteps.map(() => createMutatingFailureState());
|
|
2525
2688
|
const pendingToolResults: Array<{ tool: string; path?: string; mutates: boolean; startedAt?: number } | undefined> = initialStatusSteps.map(() => undefined);
|
|
2689
|
+
type ActiveToolCall = { key: string; tool: string; args: string; startedAt: number; path?: string; blocksSupervisor: boolean };
|
|
2690
|
+
const activeToolCalls = initialStatusSteps.map(() => new Map<string, ActiveToolCall>());
|
|
2691
|
+
const activeToolKeysByName = initialStatusSteps.map(() => new Map<string, string[]>());
|
|
2692
|
+
const activeToolSequences = initialStatusSteps.map(() => 0);
|
|
2693
|
+
const latestActiveToolCall = (flatIndex: number): ActiveToolCall | undefined => [...(activeToolCalls[flatIndex]?.values() ?? [])].sort((left, right) => right.startedAt - left.startedAt)[0];
|
|
2694
|
+
const refreshStepCurrentTool = (flatIndex: number): void => {
|
|
2695
|
+
const step = statusPayload.steps[flatIndex];
|
|
2696
|
+
if (!step) return;
|
|
2697
|
+
const active = latestActiveToolCall(flatIndex);
|
|
2698
|
+
if (!active) {
|
|
2699
|
+
delete step.currentTool;
|
|
2700
|
+
delete step.currentToolArgs;
|
|
2701
|
+
delete step.currentToolStartedAt;
|
|
2702
|
+
delete step.currentPath;
|
|
2703
|
+
return;
|
|
2704
|
+
}
|
|
2705
|
+
step.currentTool = active.tool;
|
|
2706
|
+
step.currentToolArgs = active.args;
|
|
2707
|
+
step.currentToolStartedAt = active.startedAt;
|
|
2708
|
+
setOptionalProperty(step, "currentPath", active.path);
|
|
2709
|
+
};
|
|
2710
|
+
const recordActiveToolCall = (flatIndex: number, event: { toolCallId?: unknown; toolName: string }, input: { argsPreview: string; currentPath?: string; blocksSupervisor: boolean; now: number }): ActiveToolCall => {
|
|
2711
|
+
const sequence = (activeToolSequences[flatIndex] ?? 0) + 1;
|
|
2712
|
+
activeToolSequences[flatIndex] = sequence;
|
|
2713
|
+
const key = toolTimeoutCallKey(event, sequence);
|
|
2714
|
+
const active: ActiveToolCall = {
|
|
2715
|
+
key,
|
|
2716
|
+
tool: event.toolName,
|
|
2717
|
+
args: input.argsPreview,
|
|
2718
|
+
startedAt: input.now,
|
|
2719
|
+
blocksSupervisor: input.blocksSupervisor,
|
|
2720
|
+
...(input.currentPath !== undefined ? { path: input.currentPath } : {}),
|
|
2721
|
+
};
|
|
2722
|
+
activeToolCalls[flatIndex]?.set(key, active);
|
|
2723
|
+
const keysByName = activeToolKeysByName[flatIndex];
|
|
2724
|
+
const keys = keysByName?.get(active.tool) ?? [];
|
|
2725
|
+
keys.push(key);
|
|
2726
|
+
keysByName?.set(active.tool, keys);
|
|
2727
|
+
refreshStepCurrentTool(flatIndex);
|
|
2728
|
+
return active;
|
|
2729
|
+
};
|
|
2730
|
+
const removeActiveToolCallKey = (flatIndex: number, key: string): ActiveToolCall | undefined => {
|
|
2731
|
+
const calls = activeToolCalls[flatIndex];
|
|
2732
|
+
const active = calls?.get(key);
|
|
2733
|
+
if (!active) return undefined;
|
|
2734
|
+
calls?.delete(key);
|
|
2735
|
+
const keysByName = activeToolKeysByName[flatIndex];
|
|
2736
|
+
const keys = keysByName?.get(active.tool)?.filter((candidate) => candidate !== key) ?? [];
|
|
2737
|
+
if (keys.length > 0) keysByName?.set(active.tool, keys);
|
|
2738
|
+
else keysByName?.delete(active.tool);
|
|
2739
|
+
return active;
|
|
2740
|
+
};
|
|
2741
|
+
const removeActiveToolCall = (flatIndex: number, event: { toolCallId?: unknown; toolName?: unknown }): ActiveToolCall | undefined => {
|
|
2742
|
+
const calls = activeToolCalls[flatIndex];
|
|
2743
|
+
const key = typeof event.toolCallId === "string" && event.toolCallId.length > 0
|
|
2744
|
+
? `id:${event.toolCallId}`
|
|
2745
|
+
: typeof event.toolName === "string"
|
|
2746
|
+
? activeToolKeysByName[flatIndex]?.get(event.toolName)?.[0]
|
|
2747
|
+
: calls?.size === 1
|
|
2748
|
+
? [...calls.keys()][0]
|
|
2749
|
+
: undefined;
|
|
2750
|
+
return key ? removeActiveToolCallKey(flatIndex, key) : undefined;
|
|
2751
|
+
};
|
|
2752
|
+
const openToolAttentionTarget = (flatIndex: number, now: number): ActiveToolCall | undefined => [...(activeToolCalls[flatIndex]?.values() ?? [])]
|
|
2753
|
+
.filter((active) => shouldEmitOpenToolAttention({ config: controlConfig, currentTool: active.tool, currentToolStartedAt: active.startedAt, now }))
|
|
2754
|
+
.sort((left, right) => left.startedAt - right.startedAt)[0];
|
|
2526
2755
|
const supervisorAttentionSteps = new Map<number, ActivityState | undefined>();
|
|
2527
2756
|
const mutatingFailureWindowMs = 5 * 60_000;
|
|
2528
2757
|
const appendControlEvent = (event: ReturnType<typeof buildControlEvent>) => {
|
|
@@ -2563,6 +2792,34 @@ async function runSubagent(
|
|
|
2563
2792
|
currentActivityState = nextRunState;
|
|
2564
2793
|
setOptionalProperty(statusPayload, "activityState", nextRunState);
|
|
2565
2794
|
};
|
|
2795
|
+
const maybeEmitOpenToolAttention = (flatIndex: number, now: number): boolean => {
|
|
2796
|
+
const step = statusPayload.steps[flatIndex];
|
|
2797
|
+
if (!step || step.status !== "running" || step.activityState === "needs_attention") return false;
|
|
2798
|
+
const target = openToolAttentionTarget(flatIndex, now);
|
|
2799
|
+
if (!target) return false;
|
|
2800
|
+
const previous = step.activityState;
|
|
2801
|
+
step.activityState = "needs_attention";
|
|
2802
|
+
statusPayload.activityState = "needs_attention";
|
|
2803
|
+
const toolDurationMs = Math.max(0, now - target.startedAt);
|
|
2804
|
+
appendControlEvent(buildControlEvent(omitUndefinedProperties({
|
|
2805
|
+
type: "needs_attention",
|
|
2806
|
+
from: previous,
|
|
2807
|
+
to: "needs_attention",
|
|
2808
|
+
runId: id,
|
|
2809
|
+
agent: step.agent,
|
|
2810
|
+
index: flatIndex,
|
|
2811
|
+
ts: now,
|
|
2812
|
+
message: `${step.agent} has had tool '${target.tool}' open for ${Math.floor(toolDurationMs / 1000)}s`,
|
|
2813
|
+
reason: "tool_open_threshold",
|
|
2814
|
+
turns: step.turnCount,
|
|
2815
|
+
tokens: step.tokens?.total,
|
|
2816
|
+
toolCount: step.toolCount,
|
|
2817
|
+
currentTool: target.tool,
|
|
2818
|
+
currentToolDurationMs: toolDurationMs,
|
|
2819
|
+
currentPath: target.path,
|
|
2820
|
+
})));
|
|
2821
|
+
return true;
|
|
2822
|
+
};
|
|
2566
2823
|
const maybeEmitActiveLongRunning = (flatIndex: number, now: number): boolean => {
|
|
2567
2824
|
if (!controlConfig.enabled || activeLongRunningSteps.has(flatIndex)) return false;
|
|
2568
2825
|
const step = statusPayload.steps[flatIndex];
|
|
@@ -2823,20 +3080,19 @@ async function runSubagent(
|
|
|
2823
3080
|
if (event.type === "tool_execution_start" && event.toolName) {
|
|
2824
3081
|
const mutates = isMutatingTool(event.toolName, event.args);
|
|
2825
3082
|
const currentPath = resolveCurrentPath(event.toolName, event.args);
|
|
3083
|
+
const argsPreview = extractToolArgsPreview(event.args ?? {});
|
|
3084
|
+
const blocksSupervisor = isBlockingSupervisorTool(event.toolName, event.args);
|
|
2826
3085
|
step.toolCount = (step.toolCount ?? 0) + 1;
|
|
2827
3086
|
const configuredToolBudget = flatSteps[flatIndex]?.toolBudget;
|
|
2828
3087
|
if (configuredToolBudget) {
|
|
2829
3088
|
step.toolBudget = toolBudgetState(configuredToolBudget, step.toolCount);
|
|
2830
3089
|
statusPayload.toolBudget = step.toolBudget;
|
|
2831
3090
|
}
|
|
2832
|
-
|
|
2833
|
-
step.currentToolArgs = extractToolArgsPreview(event.args ?? {});
|
|
2834
|
-
step.currentToolStartedAt = now;
|
|
2835
|
-
setOptionalProperty(step, "currentPath", currentPath);
|
|
3091
|
+
recordActiveToolCall(flatIndex, { toolCallId: (event as { toolCallId?: unknown }).toolCallId, toolName: event.toolName }, { argsPreview, currentPath, blocksSupervisor, now });
|
|
2836
3092
|
pendingToolResults[flatIndex] = omitUndefinedProperties({ tool: event.toolName, path: currentPath, mutates, startedAt: now });
|
|
2837
3093
|
statusPayload.toolCount = (statusPayload.toolCount ?? 0) + 1;
|
|
2838
3094
|
syncTopLevelCurrentTool();
|
|
2839
|
-
if (controlConfig.enabled &&
|
|
3095
|
+
if (controlConfig.enabled && blocksSupervisor && step.activityState !== "needs_attention") {
|
|
2840
3096
|
const previous = step.activityState;
|
|
2841
3097
|
step.activityState = "needs_attention";
|
|
2842
3098
|
supervisorAttentionSteps.set(flatIndex, previous);
|
|
@@ -2861,16 +3117,15 @@ async function runSubagent(
|
|
|
2861
3117
|
})));
|
|
2862
3118
|
}
|
|
2863
3119
|
} else if (event.type === "tool_execution_end") {
|
|
2864
|
-
|
|
3120
|
+
const endedTool = removeActiveToolCall(flatIndex, event);
|
|
3121
|
+
if (endedTool) {
|
|
2865
3122
|
step.recentTools ??= [];
|
|
2866
|
-
step.recentTools.push({ tool:
|
|
3123
|
+
step.recentTools.push({ tool: endedTool.tool, args: endedTool.args, endMs: now });
|
|
2867
3124
|
}
|
|
3125
|
+
refreshStepCurrentTool(flatIndex);
|
|
2868
3126
|
const supervisorPreviousActivity = supervisorAttentionSteps.get(flatIndex);
|
|
2869
|
-
const
|
|
2870
|
-
delete
|
|
2871
|
-
delete step.currentToolArgs;
|
|
2872
|
-
delete step.currentToolStartedAt;
|
|
2873
|
-
delete step.currentPath;
|
|
3127
|
+
const stillBlockingSupervisor = [...(activeToolCalls[flatIndex]?.values() ?? [])].some((active) => active.blocksSupervisor);
|
|
3128
|
+
const clearedSupervisorAttention = endedTool?.blocksSupervisor && !stillBlockingSupervisor ? supervisorAttentionSteps.delete(flatIndex) : false;
|
|
2874
3129
|
if (clearedSupervisorAttention && step.activityState === "needs_attention") {
|
|
2875
3130
|
setOptionalProperty(step, "activityState", supervisorPreviousActivity);
|
|
2876
3131
|
syncAggregateActivityState();
|
|
@@ -2990,6 +3245,8 @@ async function runSubagent(
|
|
|
2990
3245
|
})));
|
|
2991
3246
|
changed = true;
|
|
2992
3247
|
}
|
|
3248
|
+
} else if (maybeEmitOpenToolAttention(index, now)) {
|
|
3249
|
+
changed = true;
|
|
2993
3250
|
} else if (maybeEmitActiveLongRunning(index, now)) {
|
|
2994
3251
|
changed = true;
|
|
2995
3252
|
}
|
|
@@ -3552,6 +3809,7 @@ async function runSubagent(
|
|
|
3552
3809
|
timeoutMessage,
|
|
3553
3810
|
stopMessage,
|
|
3554
3811
|
turnBudget: config.turnBudget,
|
|
3812
|
+
toolTimeoutMs: task.toolTimeoutMs ?? config.toolTimeoutMs,
|
|
3555
3813
|
onAttemptStart: (attempt) => updateStepModel(fi, attempt.model, attempt.thinking),
|
|
3556
3814
|
onChildEvent: (event) => updateStepFromChildEvent(fi, event),
|
|
3557
3815
|
onWriterProcess,
|
|
@@ -3935,6 +4193,7 @@ async function runSubagent(
|
|
|
3935
4193
|
timeoutMessage,
|
|
3936
4194
|
stopMessage,
|
|
3937
4195
|
turnBudget: config.turnBudget,
|
|
4196
|
+
toolTimeoutMs: taskForRun.toolTimeoutMs ?? config.toolTimeoutMs,
|
|
3938
4197
|
onAttemptStart: (attempt) => updateStepModel(fi, attempt.model, attempt.thinking),
|
|
3939
4198
|
onChildEvent: (event) => updateStepFromChildEvent(fi, event),
|
|
3940
4199
|
onWriterProcess,
|
|
@@ -4225,6 +4484,7 @@ async function runSubagent(
|
|
|
4225
4484
|
timeoutMessage,
|
|
4226
4485
|
stopMessage,
|
|
4227
4486
|
turnBudget: config.turnBudget,
|
|
4487
|
+
toolTimeoutMs: seqStep.toolTimeoutMs ?? config.toolTimeoutMs,
|
|
4228
4488
|
onAttemptStart: (attempt) => updateStepModel(flatIndex, attempt.model, attempt.thinking),
|
|
4229
4489
|
onChildEvent: (event) => updateStepFromChildEvent(flatIndex, event),
|
|
4230
4490
|
onWriterProcess,
|
|
@@ -4522,9 +4782,8 @@ async function runSubagent(
|
|
|
4522
4782
|
statusPayload.error = `Step failed: ${failedStep.agent}`;
|
|
4523
4783
|
}
|
|
4524
4784
|
}
|
|
4525
|
-
writeStatusPayload();
|
|
4526
4785
|
try {
|
|
4527
|
-
|
|
4786
|
+
writeAsyncResultFile(resultPath, {
|
|
4528
4787
|
lifecycleArtifactVersion: SUBAGENT_LIFECYCLE_ARTIFACT_VERSION,
|
|
4529
4788
|
id,
|
|
4530
4789
|
agent: agentName,
|
|
@@ -4619,9 +4878,15 @@ async function runSubagent(
|
|
|
4619
4878
|
...(taskIndex !== undefined && { taskIndex }),
|
|
4620
4879
|
...(totalTasks !== undefined && { totalTasks }),
|
|
4621
4880
|
});
|
|
4881
|
+
finalResultCommitted = true;
|
|
4622
4882
|
} catch (err) {
|
|
4623
|
-
|
|
4883
|
+
const message = `Failed to write result file ${resultPath}: ${err instanceof Error ? err.message : String(err)}`;
|
|
4884
|
+
console.error(message, err);
|
|
4885
|
+
statusPayload.state = "failed";
|
|
4886
|
+
statusPayload.error = message;
|
|
4887
|
+
statusPayload.lastUpdate = Date.now();
|
|
4624
4888
|
}
|
|
4889
|
+
writeStatusPayload();
|
|
4625
4890
|
appendJsonl(
|
|
4626
4891
|
eventsPath,
|
|
4627
4892
|
JSON.stringify({
|
|
@@ -267,6 +267,7 @@ function activeRunsForSession(params: SubagentWaitParams, deps: SubagentWaitDeps
|
|
|
267
267
|
resultsDir,
|
|
268
268
|
kill: deps.kill,
|
|
269
269
|
now: deps.now,
|
|
270
|
+
includeNested: false,
|
|
270
271
|
...(params.id ? { runId: params.id } : {}),
|
|
271
272
|
});
|
|
272
273
|
return params.id ? runs.filter((run) => matchesId(run, params.id!)) : runs;
|
|
@@ -286,6 +287,7 @@ function allRunsForSession(params: SubagentWaitParams, deps: SubagentWaitDeps):
|
|
|
286
287
|
resultsDir,
|
|
287
288
|
kill: deps.kill,
|
|
288
289
|
now: deps.now,
|
|
290
|
+
includeNested: false,
|
|
289
291
|
...(params.id ? { runId: params.id } : {}),
|
|
290
292
|
});
|
|
291
293
|
return params.id ? runs.filter((run) => matchesId(run, params.id!)) : runs;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
|
-
import * as path from "node:path";
|
|
3
2
|
import type { ArtifactPaths, SubagentState, WaitCompletion, WaitCompletionChild } from "../../shared/types.ts";
|
|
4
3
|
import type { AsyncRunSummary } from "./async-status.ts";
|
|
5
4
|
import { readCompletionReplay, writeCompletionReplay } from "./completion-replay.ts";
|
|
5
|
+
import { resultFilePath, resultPayloadPathForSessionRun } from "./result-files.ts";
|
|
6
6
|
|
|
7
7
|
function asNonEmptyString(value: unknown): string | undefined {
|
|
8
8
|
return typeof value === "string" && value ? value : undefined;
|
|
@@ -114,7 +114,10 @@ export function collectWaitCompletions(terminal: AsyncRunSummary[], state: Subag
|
|
|
114
114
|
completions.push(recorded.completion);
|
|
115
115
|
continue;
|
|
116
116
|
}
|
|
117
|
-
const
|
|
117
|
+
const publicResultPath = resultFilePath(resultsDir, run.id);
|
|
118
|
+
const resultPath = run.sessionId
|
|
119
|
+
? resultPayloadPathForSessionRun(resultsDir, run.sessionId, run.id) ?? publicResultPath
|
|
120
|
+
: publicResultPath;
|
|
118
121
|
try {
|
|
119
122
|
const raw = JSON.parse(fs.readFileSync(resultPath, "utf-8")) as Record<string, unknown>;
|
|
120
123
|
completions.push(toWaitCompletion(raw, run.id));
|
|
@@ -6,6 +6,7 @@ import { DIRS, type Details, type SubagentState } from "../../shared/types.ts";
|
|
|
6
6
|
import { readStatus } from "../../shared/utils.ts";
|
|
7
7
|
import { updateActiveRunIndex } from "../background/active-run-index.ts";
|
|
8
8
|
import { reconcileAsyncRun } from "../background/stale-run-reconciler.ts";
|
|
9
|
+
import { resultFilePath } from "../background/result-files.ts";
|
|
9
10
|
|
|
10
11
|
export function dismissRecoveredWorkflow(
|
|
11
12
|
state: SubagentState,
|
|
@@ -50,7 +51,7 @@ export function dismissRecoveredWorkflow(
|
|
|
50
51
|
};
|
|
51
52
|
}
|
|
52
53
|
let latestStatus = status;
|
|
53
|
-
const resultPath =
|
|
54
|
+
const resultPath = resultFilePath(DIRS.results, status.runId);
|
|
54
55
|
if (fs.existsSync(resultPath)) {
|
|
55
56
|
const reconciled = reconcileAsyncRun(asyncDir).status;
|
|
56
57
|
if (reconciled && reconciled.state !== "running") {
|