pi-subagents 0.49.0 → 0.51.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 +97 -0
- package/agents/gpt-pro.md +17 -0
- 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/async-retention-discovery-worker.mjs +180 -0
- package/docs/agents.md +37 -2
- package/docs/configuration.md +76 -14
- package/docs/extension-api.md +78 -1
- package/docs/missions.md +1 -1
- package/docs/observability.md +20 -4
- package/docs/tool-reference.md +55 -39
- package/docs/workflows.md +171 -5
- package/package.json +4 -2
- package/skills/pi-subagents/SKILL.md +5 -4
- package/skills/pi-subagents/references/constraints-and-recipes.md +9 -6
- package/skills/pi-subagents/references/execution-controls.md +22 -18
- package/skills/pi-subagents/references/management-authoring-rpc.md +5 -5
- package/skills/pi-subagents/references/prompting-and-roles.md +33 -17
- package/src/agents/agent-management.ts +100 -345
- package/src/agents/agent-serializer.ts +2 -0
- package/src/agents/agents.ts +135 -25
- package/src/api/external-job-provider.ts +185 -0
- package/src/api/external-runs.ts +174 -84
- package/src/api/preflight.ts +42 -11
- package/src/api/shared-types.ts +2 -0
- package/src/extension/config.ts +36 -3
- package/src/extension/doctor.ts +3 -6
- package/src/extension/fanout-child.ts +2 -2
- package/src/extension/index.ts +210 -90
- package/src/extension/public-execution.ts +31 -2
- package/src/extension/rpc.ts +5 -1
- package/src/extension/schemas.ts +14 -36
- package/src/extension/tool-description.ts +37 -24
- package/src/inspectors/herdr/actions.ts +5 -9
- package/src/inspectors/herdr/inspector-runner.ts +2 -1
- package/src/inspectors/herdr/project-panes.ts +4 -8
- package/src/inspectors/herdr/shell-command.ts +16 -0
- package/src/intercom/intercom-bridge.ts +2 -3
- package/src/intercom/native-supervisor-channel.ts +49 -51
- package/src/missions/goal-driver.ts +3 -1
- package/src/missions/lifecycle.ts +6 -1
- package/src/missions/store.ts +4 -9
- package/src/profiles/profiles.ts +3 -1
- package/src/runs/background/active-run-index.ts +94 -1
- package/src/runs/background/async-execution.ts +98 -38
- package/src/runs/background/async-job-tracker.ts +21 -4
- package/src/runs/background/async-resume.ts +30 -17
- package/src/runs/background/async-retention.ts +888 -0
- package/src/runs/background/async-status-snapshot.ts +277 -0
- package/src/runs/background/async-status.ts +47 -56
- package/src/runs/background/chain-append.ts +3 -33
- package/src/runs/background/chain-root-attachment.ts +2 -2
- package/src/runs/background/completion-replay.ts +11 -1
- package/src/runs/background/control-channel.ts +14 -68
- package/src/runs/background/fleet-view.ts +3 -1
- package/src/runs/background/index-segment.ts +59 -0
- package/src/runs/background/notify.ts +3 -1
- package/src/runs/background/result-files.ts +505 -0
- package/src/runs/background/result-watcher.ts +250 -51
- package/src/runs/background/retained-children.ts +79 -20
- package/src/runs/background/run-id-query.ts +7 -0
- package/src/runs/background/run-id-resolver.ts +37 -29
- package/src/runs/background/run-status.ts +34 -20
- package/src/runs/background/scheduled-runs.ts +71 -27
- package/src/runs/background/stale-run-reconciler.ts +33 -16
- package/src/runs/background/steering.ts +11 -1
- package/src/runs/background/subagent-runner.ts +535 -161
- package/src/runs/background/subagent-wait.ts +9 -7
- package/src/runs/background/terminal-run-index.ts +129 -0
- package/src/runs/background/wait-completions.ts +22 -2
- package/src/runs/background/wait-subscriptions.ts +80 -1
- package/src/runs/foreground/async-dismiss-action.ts +2 -1
- package/src/runs/foreground/async-steering-action.ts +21 -14
- package/src/runs/foreground/execution.ts +221 -15
- package/src/runs/foreground/subagent-executor.ts +529 -1519
- package/src/runs/foreground/workflow-foreground-steering.ts +6 -5
- package/src/runs/shared/chain-outputs.ts +1 -3
- package/src/runs/shared/completion-guard.ts +96 -6
- package/src/runs/shared/external-cli-runner.ts +4 -0
- package/src/runs/shared/external-job-bridge.ts +450 -0
- package/src/runs/shared/external-job-runner.ts +286 -0
- package/src/runs/shared/mcp-direct-tool-allowlist.ts +14 -0
- package/src/runs/shared/model-fallback.ts +34 -3
- package/src/runs/shared/nested-events.ts +66 -62
- package/src/runs/shared/orca-progress-tabs.ts +437 -0
- package/src/runs/shared/parallel-handoff.ts +46 -4
- package/src/runs/shared/parallel-utils.ts +6 -15
- package/src/runs/shared/permissions.ts +5 -1
- package/src/runs/shared/pi-args.ts +8 -1
- package/src/runs/shared/subagent-control.ts +41 -4
- package/src/runs/shared/subagent-prompt-runtime.ts +13 -15
- package/src/runs/shared/subagent-startup-retry.ts +12 -0
- package/src/runs/shared/tool-timeout.ts +93 -0
- package/src/runs/shared/workflow-graph.ts +1 -23
- package/src/runs/shared/worktree.ts +12 -1
- package/src/shared/atomic-json.ts +22 -2
- package/src/shared/capacity-resilient-json.ts +102 -0
- package/src/shared/completion-owner.ts +14 -0
- package/src/shared/file-system-retry.ts +49 -1
- package/src/shared/fork-context.ts +42 -0
- package/src/shared/prompt-resources.ts +0 -40
- package/src/shared/settings.ts +3 -27
- package/src/shared/types.ts +88 -27
- package/src/shared/utils.ts +8 -0
- package/src/shared/watch-strategy.ts +10 -0
- package/src/slash/slash-commands.ts +45 -28
- package/src/slash/slash-live-state.ts +3 -0
- package/src/tui/fleet-status.ts +160 -45
- package/src/tui/fleet.ts +186 -28
- package/src/tui/render.ts +41 -10
- package/src/workflows/chat-progress.ts +7 -5
- package/src/workflows/scripted-workflow.ts +424 -125
- package/src/runs/foreground/chain-clarify.ts +0 -1354
- package/src/runs/foreground/chain-execution.ts +0 -1565
|
@@ -5,8 +5,11 @@ 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
|
-
import {
|
|
10
|
+
import { createCapacityResilientJsonWriter } from "../../shared/capacity-resilient-json.ts";
|
|
11
|
+
import { isStorageCapacityError } from "../../shared/file-system-retry.ts";
|
|
12
|
+
import { updateActiveRunIndex } from "./active-run-index.ts";
|
|
10
13
|
import { createChildTranscriptWriter, type ChildTranscriptWriter } from "../../shared/child-transcript.ts";
|
|
11
14
|
import { closeSteerInbox, consumeInterruptRequest, consumeSteerRequests, deliverInterruptRequest, deliverStopRequest, deliverTimeoutRequest, enqueueStepSteer, steerAcksDir, steerCapabilityPath, stepSteerInboxDir, watchAsyncControlInbox, type SteerAck, type SteerCapability, type SteerRequest } from "./control-channel.ts";
|
|
12
15
|
import { appendJsonl as appendRawJsonl, formatOutputArtifactContent, getArtifactPaths, writeArtifact, writeMetadata } from "../../shared/artifacts.ts";
|
|
@@ -16,6 +19,8 @@ import {
|
|
|
16
19
|
type ActivityState,
|
|
17
20
|
type ArtifactConfig,
|
|
18
21
|
type ExternalCliRunnerStatus,
|
|
22
|
+
type ExternalJobRunnerStatus,
|
|
23
|
+
type ExternalJobStatus,
|
|
19
24
|
type ExternalProcessStatus,
|
|
20
25
|
type ArtifactPaths,
|
|
21
26
|
type AsyncParallelGroupStatus,
|
|
@@ -57,11 +62,11 @@ import {
|
|
|
57
62
|
claimControlNotification,
|
|
58
63
|
formatControlIntercomMessage,
|
|
59
64
|
formatControlNoticeMessage,
|
|
65
|
+
shouldEmitOpenToolAttention,
|
|
60
66
|
} from "../shared/subagent-control.ts";
|
|
61
67
|
import {
|
|
62
68
|
type RunnerSubagentStep as SubagentStep,
|
|
63
69
|
type RunnerStep,
|
|
64
|
-
isCheckpointRunnerStep,
|
|
65
70
|
isDynamicRunnerGroup,
|
|
66
71
|
isParallelGroup,
|
|
67
72
|
flattenSteps,
|
|
@@ -83,6 +88,7 @@ import { nestedSummaryFromAsyncStatus, projectNestedEvents, resolveNestedAsyncDi
|
|
|
83
88
|
import { formatModelAttemptNote, isRetryableModelFailure } from "../shared/model-fallback.ts";
|
|
84
89
|
import {
|
|
85
90
|
SUBAGENT_STARTUP_RETRY_DELAYS_MS,
|
|
91
|
+
formatSubagentExtensionConflictError,
|
|
86
92
|
formatSubagentStartupRetryExhaustedError,
|
|
87
93
|
formatSubagentStartupRetryNote,
|
|
88
94
|
isRetryableSubagentStartupFailure,
|
|
@@ -126,12 +132,15 @@ import { waitForImportedAsyncRoot } from "./chain-root-attachment.ts";
|
|
|
126
132
|
import { appendRunnerStepsToStatus, consumeChainAppendRequests, countPendingChainAppendRequests, statusStepDescription } from "./chain-append.ts";
|
|
127
133
|
import { appendTurnBudgetSystemPrompt, formatTurnBudgetOutput, initialTurnBudgetState, turnBudgetDecision, turnBudgetDeferredNote, turnBudgetDeferredState, turnBudgetExceededMessage, turnBudgetSoftNote, turnBudgetState } from "../shared/turn-budget.ts";
|
|
128
134
|
import { initialToolBudgetState, toolBudgetState } from "../shared/tool-budget.ts";
|
|
135
|
+
import { effectiveToolTimeoutMs, formatToolTimeoutMessage, toolTimeoutCallKey } from "../shared/tool-timeout.ts";
|
|
129
136
|
import { usageBudgetExceededMessage, usageBudgetState } from "../shared/usage-budget.ts";
|
|
130
137
|
import { formatParallelHandoffError, formatParallelHandoffReference, parallelHandoffPath, writeParallelHandoffGroup, writePendingParallelHandoff } from "../shared/parallel-handoff.ts";
|
|
131
138
|
import { resolveWatchdogConfig } from "../../watchdog/settings.ts";
|
|
132
139
|
import { createBoundedByteTail, createBoundedLineReader, formatProtocolOutputLimit, MAX_CHILD_STDERR_BYTES, PI_AGGREGATE_EVENT_PROJECTOR, projectChildLifecycle, type ChildLifecycleAction, type ProtocolOutputLimit } from "../shared/child-protocol.ts";
|
|
133
140
|
import { acquireSessionLease, type SessionLeaseRequest } from "../shared/session-lease.ts";
|
|
134
141
|
import { buildExternalCliPrompt, runExternalCli } from "../shared/external-cli-runner.ts";
|
|
142
|
+
import { runExternalJob } from "../shared/external-job-runner.ts";
|
|
143
|
+
import { createOrcaProgressTab, type OrcaProgressTab } from "../shared/orca-progress-tabs.ts";
|
|
135
144
|
import { decodeSubagentCapabilityCeiling, SUBAGENT_CAPABILITY_CEILING_ENV, type ResolvedSubagentCapabilityCeiling } from "../shared/capability-ceiling.ts";
|
|
136
145
|
import {
|
|
137
146
|
CHILD_WATCHDOG_CONFIG_ENV,
|
|
@@ -160,6 +169,7 @@ interface SubagentRunConfig {
|
|
|
160
169
|
sessionDir?: string;
|
|
161
170
|
asyncDir: string;
|
|
162
171
|
sessionId?: string | null;
|
|
172
|
+
completionOwnerId?: string;
|
|
163
173
|
piPackageRoot?: string;
|
|
164
174
|
piArgv1?: string;
|
|
165
175
|
worktreeSetupHook?: string;
|
|
@@ -176,6 +186,8 @@ interface SubagentRunConfig {
|
|
|
176
186
|
nestedSelf?: { parentRunId: string; parentStepIndex?: number; depth: number; path?: Array<{ runId: string; stepIndex?: number; agent?: string }> };
|
|
177
187
|
timeoutMs?: number;
|
|
178
188
|
deadlineAt?: number;
|
|
189
|
+
/** Resolved configured hard per-tool-call timeout (ms); fast tools still have a default when undefined. */
|
|
190
|
+
toolTimeoutMs?: number;
|
|
179
191
|
turnBudget?: ResolvedTurnBudget;
|
|
180
192
|
toolBudget?: ResolvedToolBudget;
|
|
181
193
|
usageBudget?: UsageBudgetConfig;
|
|
@@ -210,6 +222,7 @@ interface StepResult {
|
|
|
210
222
|
savedOutputPath?: string;
|
|
211
223
|
skipped?: boolean;
|
|
212
224
|
interrupted?: boolean;
|
|
225
|
+
detached?: boolean;
|
|
213
226
|
timedOut?: boolean;
|
|
214
227
|
stopped?: boolean;
|
|
215
228
|
processSignal?: string | null;
|
|
@@ -242,8 +255,9 @@ interface StepResult {
|
|
|
242
255
|
watchdog?: import("../../shared/types.ts").ChildWatchdogProgress;
|
|
243
256
|
writerProcesses?: PiWriterProcessInstanceExitV1[];
|
|
244
257
|
writerAttemptCount?: number;
|
|
245
|
-
runner?: ExternalCliRunnerStatus;
|
|
258
|
+
runner?: ExternalCliRunnerStatus | ExternalJobRunnerStatus;
|
|
246
259
|
externalProcess?: ExternalProcessStatus;
|
|
260
|
+
externalJob?: ExternalJobStatus;
|
|
247
261
|
}
|
|
248
262
|
|
|
249
263
|
function persistStepArtifacts(input: {
|
|
@@ -541,6 +555,9 @@ function runPiStreaming(
|
|
|
541
555
|
stopMessage?: string,
|
|
542
556
|
registerTurnBudgetAbort?: (abort: ((message: string, state?: TurnBudgetState) => void) | undefined) => void,
|
|
543
557
|
onWriterProcess?: (writer: { state: "none" | "spawning" } | { state: "running"; pid: number }) => void,
|
|
558
|
+
toolTimeoutMs?: number,
|
|
559
|
+
runDeadlineAt?: number,
|
|
560
|
+
orcaProgressTab?: OrcaProgressTab,
|
|
544
561
|
): Promise<RunPiStreamingResult> {
|
|
545
562
|
return new Promise((resolve) => {
|
|
546
563
|
const startedAt = Date.now();
|
|
@@ -597,6 +614,7 @@ function runPiStreaming(
|
|
|
597
614
|
const writeOutputLine = (line: string) => {
|
|
598
615
|
if (!line.trim()) return;
|
|
599
616
|
outputStream.write(`${line}\n`);
|
|
617
|
+
orcaProgressTab?.append(`${line}\n`);
|
|
600
618
|
};
|
|
601
619
|
|
|
602
620
|
const writeOutputText = (text: string) => {
|
|
@@ -674,8 +692,14 @@ function runPiStreaming(
|
|
|
674
692
|
|
|
675
693
|
onChildEvent?.(event);
|
|
676
694
|
|
|
695
|
+
if (event.type === "tool_execution_end") {
|
|
696
|
+
clearActiveToolTimeout(event);
|
|
697
|
+
return;
|
|
698
|
+
}
|
|
699
|
+
|
|
677
700
|
if (event.type === "tool_execution_start" && event.toolName) {
|
|
678
701
|
toolCount += 1;
|
|
702
|
+
armToolTimeout({ toolCallId: (event as { toolCallId?: unknown }).toolCallId, toolName: event.toolName });
|
|
679
703
|
if (event.toolName === "structured_output") {
|
|
680
704
|
structuredOutputToolInvoked = true;
|
|
681
705
|
structuredOutputMessageStartIndex = messages.length;
|
|
@@ -771,6 +795,7 @@ function runPiStreaming(
|
|
|
771
795
|
stderrTail.push(chunk);
|
|
772
796
|
stderrReader.push(chunk);
|
|
773
797
|
outputStream.write(chunk);
|
|
798
|
+
orcaProgressTab?.append(chunk.toString("utf-8"));
|
|
774
799
|
});
|
|
775
800
|
registerInterrupt?.(() => {
|
|
776
801
|
if (settled || timedOut || stopped) return;
|
|
@@ -781,14 +806,60 @@ function runPiStreaming(
|
|
|
781
806
|
if (!settled && !timedOut && !stopped) trySignalChild(child, "SIGTERM");
|
|
782
807
|
}, 1000).unref?.();
|
|
783
808
|
});
|
|
784
|
-
|
|
809
|
+
const terminateForTimeout = (message: string): void => {
|
|
785
810
|
if (settled || timedOut || stopped) return;
|
|
786
811
|
timedOut = true;
|
|
812
|
+
// runPiStreaming's terminal result derives the timeout error from this
|
|
813
|
+
// message, so retain the tool-specific reason through finalization.
|
|
814
|
+
timeoutMessage = message;
|
|
787
815
|
interrupted = false;
|
|
788
|
-
error =
|
|
816
|
+
error = message;
|
|
789
817
|
if (processTreeController) void processTreeController.terminate();
|
|
790
818
|
else trySignalChild(child, "SIGTERM");
|
|
791
|
-
}
|
|
819
|
+
};
|
|
820
|
+
let toolTimeoutSequence = 0;
|
|
821
|
+
const activeToolTimeouts = new Map<string, { toolName: string; timer: ReturnType<typeof setTimeout> }>();
|
|
822
|
+
const activeToolTimeoutKeysByName = new Map<string, string[]>();
|
|
823
|
+
const removeToolTimeoutKey = (key: string): void => {
|
|
824
|
+
const active = activeToolTimeouts.get(key);
|
|
825
|
+
if (!active) return;
|
|
826
|
+
clearTimeout(active.timer);
|
|
827
|
+
activeToolTimeouts.delete(key);
|
|
828
|
+
const keys = activeToolTimeoutKeysByName.get(active.toolName)?.filter((candidate) => candidate !== key) ?? [];
|
|
829
|
+
if (keys.length > 0) activeToolTimeoutKeysByName.set(active.toolName, keys);
|
|
830
|
+
else activeToolTimeoutKeysByName.delete(active.toolName);
|
|
831
|
+
};
|
|
832
|
+
const clearActiveToolTimeout = (event: { toolCallId?: unknown; toolName?: unknown }): void => {
|
|
833
|
+
const key = typeof event.toolCallId === "string" && event.toolCallId.length > 0
|
|
834
|
+
? `id:${event.toolCallId}`
|
|
835
|
+
: typeof event.toolName === "string"
|
|
836
|
+
? activeToolTimeoutKeysByName.get(event.toolName)?.[0]
|
|
837
|
+
: activeToolTimeouts.size === 1
|
|
838
|
+
? [...activeToolTimeouts.keys()][0]
|
|
839
|
+
: undefined;
|
|
840
|
+
if (key) removeToolTimeoutKey(key);
|
|
841
|
+
};
|
|
842
|
+
const clearAllToolTimeouts = (): void => {
|
|
843
|
+
for (const key of [...activeToolTimeouts.keys()]) removeToolTimeoutKey(key);
|
|
844
|
+
};
|
|
845
|
+
const armToolTimeout = (event: { toolCallId?: unknown; toolName: string }): void => {
|
|
846
|
+
const timeoutForTool = effectiveToolTimeoutMs(event.toolName, toolTimeoutMs);
|
|
847
|
+
if (timeoutForTool === undefined) return;
|
|
848
|
+
const runRemaining = runDeadlineAt === undefined ? undefined : Math.max(0, runDeadlineAt - Date.now());
|
|
849
|
+
if (runRemaining !== undefined && timeoutForTool >= runRemaining) return;
|
|
850
|
+
const key = toolTimeoutCallKey(event, ++toolTimeoutSequence);
|
|
851
|
+
const toolName = event.toolName;
|
|
852
|
+
const timer = setTimeout(() => {
|
|
853
|
+
removeToolTimeoutKey(key);
|
|
854
|
+
terminateForTimeout(formatToolTimeoutMessage(toolName, timeoutForTool));
|
|
855
|
+
}, timeoutForTool);
|
|
856
|
+
timer.unref?.();
|
|
857
|
+
activeToolTimeouts.set(key, { toolName, timer });
|
|
858
|
+
const keys = activeToolTimeoutKeysByName.get(toolName) ?? [];
|
|
859
|
+
keys.push(key);
|
|
860
|
+
activeToolTimeoutKeysByName.set(toolName, keys);
|
|
861
|
+
};
|
|
862
|
+
registerTimeout?.(() => terminateForTimeout(timeoutMessage ?? "Subagent timed out."));
|
|
792
863
|
registerStop?.(() => {
|
|
793
864
|
if (settled || timedOut || stopped) return;
|
|
794
865
|
stopped = true;
|
|
@@ -815,6 +886,7 @@ function runPiStreaming(
|
|
|
815
886
|
turnBudgetHardKillTimer.unref?.();
|
|
816
887
|
});
|
|
817
888
|
const clearDrainTimers = () => {
|
|
889
|
+
clearAllToolTimeouts();
|
|
818
890
|
if (finalDrainTimer) {
|
|
819
891
|
clearTimeout(finalDrainTimer);
|
|
820
892
|
finalDrainTimer = undefined;
|
|
@@ -1044,6 +1116,7 @@ function writeRunLog(
|
|
|
1044
1116
|
shareUrl?: string;
|
|
1045
1117
|
shareError?: string;
|
|
1046
1118
|
},
|
|
1119
|
+
write: (filePath: string, content: string) => void = (filePath, content) => fs.writeFileSync(filePath, content, "utf-8"),
|
|
1047
1120
|
): void {
|
|
1048
1121
|
const lines: string[] = [];
|
|
1049
1122
|
lines.push(`# Subagent run ${input.id}`);
|
|
@@ -1073,7 +1146,7 @@ function writeRunLog(
|
|
|
1073
1146
|
}
|
|
1074
1147
|
lines.push(input.summary.trim() || "(no output)");
|
|
1075
1148
|
lines.push("");
|
|
1076
|
-
|
|
1149
|
+
write(logPath, lines.join("\n"));
|
|
1077
1150
|
}
|
|
1078
1151
|
|
|
1079
1152
|
/** Context for running a single step */
|
|
@@ -1104,6 +1177,10 @@ interface SingleStepContext {
|
|
|
1104
1177
|
stopSignal?: AbortSignal;
|
|
1105
1178
|
timeoutMessage?: string;
|
|
1106
1179
|
stopMessage?: string;
|
|
1180
|
+
/** Resolved configured hard per-tool-call timeout (ms); fast tools still have a default when undefined. */
|
|
1181
|
+
toolTimeoutMs?: number;
|
|
1182
|
+
/** Effective step deadline (Date.now() + effective timeout) when a run budget exists. */
|
|
1183
|
+
deadlineAt?: number;
|
|
1107
1184
|
turnBudget?: ResolvedTurnBudget;
|
|
1108
1185
|
childIntercomTarget?: string;
|
|
1109
1186
|
orchestratorIntercomTarget?: string;
|
|
@@ -1114,11 +1191,13 @@ interface SingleStepContext {
|
|
|
1114
1191
|
onChildEvent?: (event: ChildEvent) => void;
|
|
1115
1192
|
onWriterProcess?: (writer: { state: "none" | "spawning" } | { state: "running"; pid: number }) => void;
|
|
1116
1193
|
onExternalProcess?: (process: ExternalProcessStatus) => void;
|
|
1194
|
+
onExternalJob?: (status: ExternalJobStatus) => void;
|
|
1117
1195
|
skipAcceptance?: () => boolean;
|
|
1196
|
+
orcaProgressTab?: OrcaProgressTab;
|
|
1118
1197
|
}
|
|
1119
1198
|
|
|
1120
1199
|
/** Run a single pi agent step, returning output and metadata */
|
|
1121
|
-
async function
|
|
1200
|
+
async function runSingleStepInner(
|
|
1122
1201
|
step: SubagentStep,
|
|
1123
1202
|
ctx: SingleStepContext,
|
|
1124
1203
|
): Promise<StepResult & { completionGuardTriggered?: boolean }> {
|
|
@@ -1247,6 +1326,8 @@ async function runSingleStep(
|
|
|
1247
1326
|
timeoutMessage: ctx.timeoutMessage,
|
|
1248
1327
|
stopMessage: ctx.stopMessage,
|
|
1249
1328
|
onProcess: ctx.onExternalProcess,
|
|
1329
|
+
onStdout: (chunk) => ctx.orcaProgressTab?.append(chunk.toString("utf-8")),
|
|
1330
|
+
onStderr: (chunk) => ctx.orcaProgressTab?.append(chunk.toString("utf-8")),
|
|
1250
1331
|
}));
|
|
1251
1332
|
try { fs.writeFileSync(ctx.outputFile, external.output, "utf-8"); } catch { /* Observability output is best-effort. */ }
|
|
1252
1333
|
const resolvedOutput = step.outputPath && external.exitCode === 0
|
|
@@ -1288,6 +1369,69 @@ async function runSingleStep(
|
|
|
1288
1369
|
});
|
|
1289
1370
|
}
|
|
1290
1371
|
|
|
1372
|
+
if (step.runner?.type === "external-job") {
|
|
1373
|
+
const runner: ExternalJobRunnerStatus = {
|
|
1374
|
+
type: "external-job",
|
|
1375
|
+
provider: step.runner.provider,
|
|
1376
|
+
options: step.runner.options ?? {},
|
|
1377
|
+
capabilities: { stop: false, steer: false, resume: false, structuredOutput: false, toolEvents: false },
|
|
1378
|
+
};
|
|
1379
|
+
const outputSnapshot = captureSingleOutputSnapshot(step.outputPath);
|
|
1380
|
+
const external = await runExternalJob(omitUndefinedProperties({
|
|
1381
|
+
provider: runner.provider,
|
|
1382
|
+
options: runner.options,
|
|
1383
|
+
cwd: step.cwd ?? ctx.cwd,
|
|
1384
|
+
prompt: buildExternalCliPrompt(step.systemPrompt ?? "", task),
|
|
1385
|
+
asyncDir: path.dirname(ctx.outputFile),
|
|
1386
|
+
stepIndex: ctx.flatIndex,
|
|
1387
|
+
runId: ctx.id,
|
|
1388
|
+
agent: step.agent,
|
|
1389
|
+
sessionId: step.parentSessionId,
|
|
1390
|
+
registerTimeout: ctx.registerTimeout,
|
|
1391
|
+
registerStop: ctx.registerStop,
|
|
1392
|
+
timeoutMessage: ctx.timeoutMessage,
|
|
1393
|
+
stopMessage: ctx.stopMessage,
|
|
1394
|
+
onExternalJob: ctx.onExternalJob,
|
|
1395
|
+
}));
|
|
1396
|
+
try { fs.writeFileSync(ctx.outputFile, external.output, "utf-8"); } catch { /* Observability output is best-effort. */ }
|
|
1397
|
+
const resolvedOutput = step.outputPath && external.exitCode === 0
|
|
1398
|
+
? resolveSingleOutput(step.outputPath, external.output, outputSnapshot)
|
|
1399
|
+
: { fullOutput: external.output };
|
|
1400
|
+
const outputReference = resolvedOutput.savedPath ? formatSavedOutputReference(resolvedOutput.savedPath, resolvedOutput.fullOutput) : undefined;
|
|
1401
|
+
const finalizedOutput = finalizeSingleOutput(omitUndefinedProperties({
|
|
1402
|
+
fullOutput: resolvedOutput.fullOutput,
|
|
1403
|
+
outputPath: step.outputPath,
|
|
1404
|
+
outputMode: step.outputMode,
|
|
1405
|
+
exitCode: external.exitCode,
|
|
1406
|
+
savedPath: resolvedOutput.savedPath,
|
|
1407
|
+
outputReference,
|
|
1408
|
+
saveError: resolvedOutput.saveError,
|
|
1409
|
+
}));
|
|
1410
|
+
const artifactErrors = artifactPaths && ctx.artifactConfig?.enabled !== false
|
|
1411
|
+
? persistStepArtifacts({
|
|
1412
|
+
artifactPaths,
|
|
1413
|
+
artifactConfig: ctx.artifactConfig,
|
|
1414
|
+
output: formatOutputArtifactContent(omitUndefinedProperties({ output: resolvedOutput.fullOutput, error: external.error, metadataPath: ctx.artifactConfig?.includeMetadata === false ? undefined : artifactPaths.metadataPath })),
|
|
1415
|
+
metadata: { runId: ctx.id, agent: step.agent, task: PROMPT_REDACTED, runner, externalJob: external.externalJob, exitCode: external.exitCode, error: external.error, timestamp: Date.now() },
|
|
1416
|
+
})
|
|
1417
|
+
: {};
|
|
1418
|
+
return omitUndefinedProperties({
|
|
1419
|
+
agent: step.agent,
|
|
1420
|
+
context: step.context,
|
|
1421
|
+
output: finalizedOutput.displayOutput,
|
|
1422
|
+
outputState: external.output.trim() ? "present" : "absent",
|
|
1423
|
+
exitCode: external.exitCode,
|
|
1424
|
+
error: external.error,
|
|
1425
|
+
timedOut: external.timedOut,
|
|
1426
|
+
stopped: external.stopped,
|
|
1427
|
+
artifactPaths,
|
|
1428
|
+
outputSaveError: artifactErrors.outputSaveError,
|
|
1429
|
+
metadataSaveError: artifactErrors.metadataSaveError,
|
|
1430
|
+
runner,
|
|
1431
|
+
externalJob: external.externalJob,
|
|
1432
|
+
});
|
|
1433
|
+
}
|
|
1434
|
+
|
|
1291
1435
|
const candidates = step.modelCandidates && step.modelCandidates.length > 0
|
|
1292
1436
|
? step.modelCandidates
|
|
1293
1437
|
: step.model
|
|
@@ -1433,6 +1577,9 @@ async function runSingleStep(
|
|
|
1433
1577
|
ctx.stopMessage,
|
|
1434
1578
|
ctx.registerTurnBudgetAbort,
|
|
1435
1579
|
ctx.onWriterProcess,
|
|
1580
|
+
ctx.toolTimeoutMs,
|
|
1581
|
+
ctx.deadlineAt,
|
|
1582
|
+
ctx.orcaProgressTab,
|
|
1436
1583
|
);
|
|
1437
1584
|
if (run.processCloseObservedAt !== undefined) {
|
|
1438
1585
|
writerProcesses.push({
|
|
@@ -1540,15 +1687,21 @@ async function runSingleStep(
|
|
|
1540
1687
|
stopped: run.stopped,
|
|
1541
1688
|
turnBudgetExceeded: run.turnBudgetExceeded,
|
|
1542
1689
|
})) ? formatProcessSignalError(run.processSignal!) : undefined;
|
|
1543
|
-
const error =
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
?
|
|
1550
|
-
|
|
1551
|
-
|
|
1690
|
+
const error = formatSubagentExtensionConflictError(
|
|
1691
|
+
toolAvailabilityError
|
|
1692
|
+
?? completionGuardError
|
|
1693
|
+
?? structuredError
|
|
1694
|
+
?? emptyOutputError
|
|
1695
|
+
?? (hiddenError?.hasError
|
|
1696
|
+
? hiddenError.details
|
|
1697
|
+
? `${hiddenError.errorType} failed (exit ${effectiveExitCode}): ${hiddenError.details}`
|
|
1698
|
+
: `${hiddenError.errorType} failed with exit code ${effectiveExitCode}`
|
|
1699
|
+
: run.error || signalError || (run.exitCode !== 0 && run.stderr.trim() ? run.stderr.trim() : undefined)),
|
|
1700
|
+
{
|
|
1701
|
+
agent: step.agent,
|
|
1702
|
+
ambientExtensionsEnabled: launchResolvedExtensions?.disableAmbientExtensions === false,
|
|
1703
|
+
},
|
|
1704
|
+
);
|
|
1552
1705
|
const attempt: ModelAttempt = omitUndefinedProperties({
|
|
1553
1706
|
model: candidate ?? run.model ?? step.model ?? "default",
|
|
1554
1707
|
success: effectiveExitCode === 0 && !error,
|
|
@@ -1699,7 +1852,7 @@ async function runSingleStep(
|
|
|
1699
1852
|
const effectiveFinalError = stoppedAfterAcceptance
|
|
1700
1853
|
? ctx.stopMessage ?? "Subagent stopped by user."
|
|
1701
1854
|
: timedOutAfterAcceptance
|
|
1702
|
-
? ctx.timeoutMessage ?? "Subagent timed out."
|
|
1855
|
+
? finalResult?.error ?? ctx.timeoutMessage ?? "Subagent timed out."
|
|
1703
1856
|
: turnBudgetExceeded
|
|
1704
1857
|
? finalResult?.error ?? (turnBudget ? turnBudgetExceededMessage(turnBudget, turnBudget.turnCount) : "Subagent exceeded turn budget.")
|
|
1705
1858
|
: acceptanceCanFailRun
|
|
@@ -1784,20 +1937,51 @@ async function runSingleStep(
|
|
|
1784
1937
|
return isAgentContractV1(step.agentContract) ? attachContractProjections(result as unknown as import("../../shared/types.ts").SingleResult) as unknown as typeof result : result;
|
|
1785
1938
|
}
|
|
1786
1939
|
|
|
1940
|
+
async function runSingleStep(
|
|
1941
|
+
step: SubagentStep,
|
|
1942
|
+
ctx: SingleStepContext,
|
|
1943
|
+
): Promise<StepResult & { completionGuardTriggered?: boolean }> {
|
|
1944
|
+
if (step.importAsyncRoot) return runSingleStepInner(step, ctx);
|
|
1945
|
+
const orcaProgressTab = createOrcaProgressTab({
|
|
1946
|
+
cwd: step.cwd ?? ctx.cwd,
|
|
1947
|
+
runId: ctx.id,
|
|
1948
|
+
agent: step.agent,
|
|
1949
|
+
index: ctx.flatIndex,
|
|
1950
|
+
});
|
|
1951
|
+
try {
|
|
1952
|
+
const result = await runSingleStepInner(step, { ...ctx, orcaProgressTab });
|
|
1953
|
+
orcaProgressTab?.finish(result.stopped ? "stopped" : result.exitCode === 0 && !result.error ? "completed" : "failed", result.sessionFile);
|
|
1954
|
+
return result;
|
|
1955
|
+
} catch (error) {
|
|
1956
|
+
orcaProgressTab?.finish("failed");
|
|
1957
|
+
throw error;
|
|
1958
|
+
}
|
|
1959
|
+
}
|
|
1960
|
+
|
|
1787
1961
|
type RunnerStatusStep = NonNullable<AsyncStatus["steps"]>[number] & {
|
|
1788
1962
|
exitCode?: number | null;
|
|
1789
1963
|
description?: string;
|
|
1790
1964
|
};
|
|
1791
1965
|
|
|
1792
|
-
function externalRunnerStatus(runner: SubagentStep["runner"]): ExternalCliRunnerStatus | undefined {
|
|
1793
|
-
if (runner?.type
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1966
|
+
function externalRunnerStatus(runner: SubagentStep["runner"]): ExternalCliRunnerStatus | ExternalJobRunnerStatus | undefined {
|
|
1967
|
+
if (runner?.type === "external-cli") {
|
|
1968
|
+
return {
|
|
1969
|
+
type: "external-cli",
|
|
1970
|
+
command: runner.command,
|
|
1971
|
+
args: runner.args ?? [],
|
|
1972
|
+
promptDelivery: runner.promptDelivery ?? "stdin",
|
|
1973
|
+
capabilities: { stop: true, steer: false, resume: false, structuredOutput: false, toolEvents: false },
|
|
1974
|
+
};
|
|
1975
|
+
}
|
|
1976
|
+
if (runner?.type === "external-job") {
|
|
1977
|
+
return {
|
|
1978
|
+
type: "external-job",
|
|
1979
|
+
provider: runner.provider,
|
|
1980
|
+
options: runner.options ?? {},
|
|
1981
|
+
capabilities: { stop: false, steer: false, resume: false, structuredOutput: false, toolEvents: false },
|
|
1982
|
+
};
|
|
1983
|
+
}
|
|
1984
|
+
return undefined;
|
|
1801
1985
|
}
|
|
1802
1986
|
|
|
1803
1987
|
function appendCapabilityCeilingAppliedEvent(eventsPath: string, runId: string, stepIndex: number, agent: string, result: StepResult): void {
|
|
@@ -1846,6 +2030,7 @@ function markParallelGroupSetupFailure(input: {
|
|
|
1846
2030
|
asyncDir: string;
|
|
1847
2031
|
runId: string;
|
|
1848
2032
|
stepIndex: number;
|
|
2033
|
+
writeStatus: (status: RunnerStatusPayload) => void;
|
|
1849
2034
|
}): void {
|
|
1850
2035
|
for (let taskIndex = 0; taskIndex < input.group.parallel.length; taskIndex++) {
|
|
1851
2036
|
const flatTaskIndex = input.groupStartFlatIndex + taskIndex;
|
|
@@ -1862,7 +2047,7 @@ function markParallelGroupSetupFailure(input: {
|
|
|
1862
2047
|
input.statusPayload.currentStep = input.groupStartFlatIndex;
|
|
1863
2048
|
input.statusPayload.lastUpdate = input.failedAt;
|
|
1864
2049
|
input.statusPayload.outputFile = path.join(input.asyncDir, `output-${input.groupStartFlatIndex}.log`);
|
|
1865
|
-
|
|
2050
|
+
input.writeStatus(input.statusPayload);
|
|
1866
2051
|
appendJsonl(input.eventsPath, JSON.stringify({
|
|
1867
2052
|
type: "subagent.parallel.completed",
|
|
1868
2053
|
ts: input.failedAt,
|
|
@@ -1882,6 +2067,7 @@ function markParallelGroupRunning(input: {
|
|
|
1882
2067
|
asyncDir: string;
|
|
1883
2068
|
runId: string;
|
|
1884
2069
|
stepIndex: number;
|
|
2070
|
+
writeStatus: (status: RunnerStatusPayload) => void;
|
|
1885
2071
|
}): void {
|
|
1886
2072
|
for (let taskIndex = 0; taskIndex < input.group.parallel.length; taskIndex++) {
|
|
1887
2073
|
const flatTaskIndex = input.groupStartFlatIndex + taskIndex;
|
|
@@ -1899,7 +2085,7 @@ function markParallelGroupRunning(input: {
|
|
|
1899
2085
|
input.statusPayload.lastActivityAt = input.groupStartTime;
|
|
1900
2086
|
input.statusPayload.lastUpdate = input.groupStartTime;
|
|
1901
2087
|
input.statusPayload.outputFile = path.join(input.asyncDir, `output-${input.groupStartFlatIndex}.log`);
|
|
1902
|
-
|
|
2088
|
+
input.writeStatus(input.statusPayload);
|
|
1903
2089
|
appendJsonl(input.eventsPath, JSON.stringify({
|
|
1904
2090
|
type: "subagent.parallel.started",
|
|
1905
2091
|
ts: input.groupStartTime,
|
|
@@ -2008,6 +2194,7 @@ async function runSingleStepWithTimeout(
|
|
|
2008
2194
|
return await runSingleStep(step, {
|
|
2009
2195
|
...ctx,
|
|
2010
2196
|
registerTimeout,
|
|
2197
|
+
deadlineAt: Date.now() + timeoutMs,
|
|
2011
2198
|
timeoutSignal: combinedAbortSignal([ctx.timeoutSignal, timeoutController.signal]),
|
|
2012
2199
|
timeoutMessage,
|
|
2013
2200
|
});
|
|
@@ -2048,9 +2235,6 @@ async function runSubagent(
|
|
|
2048
2235
|
let stopped = false;
|
|
2049
2236
|
let turnBudgetExceeded = false;
|
|
2050
2237
|
let usageBudgetExceeded = false;
|
|
2051
|
-
let checkpointRejected = false;
|
|
2052
|
-
let pendingCheckpointDecision: "approved" | "rejected" | undefined;
|
|
2053
|
-
let wakeCheckpointDecision: (() => void) | undefined;
|
|
2054
2238
|
const timeoutMessage = config.timeoutMs !== undefined ? `Subagent timed out after ${config.timeoutMs}ms.` : undefined;
|
|
2055
2239
|
const stopMessage = "Subagent stopped by user.";
|
|
2056
2240
|
const timeoutAbortController = new AbortController();
|
|
@@ -2065,19 +2249,6 @@ async function runSubagent(
|
|
|
2065
2249
|
let flatStepCount = 0;
|
|
2066
2250
|
for (let stepIndex = 0; stepIndex < steps.length; stepIndex++) {
|
|
2067
2251
|
const step = steps[stepIndex]!;
|
|
2068
|
-
if (isCheckpointRunnerStep(step)) {
|
|
2069
|
-
initialStatusSteps.push(omitUndefinedProperties({
|
|
2070
|
-
agent: `checkpoint:${step.checkpoint}`,
|
|
2071
|
-
phase: step.phase,
|
|
2072
|
-
label: step.label ?? step.checkpoint,
|
|
2073
|
-
status: "pending",
|
|
2074
|
-
checkpoint: { name: step.checkpoint, ...(step.message ? { message: step.message } : {}), status: "pending", stepIndex },
|
|
2075
|
-
recentTools: [],
|
|
2076
|
-
recentOutput: [],
|
|
2077
|
-
}));
|
|
2078
|
-
flatStepCount++;
|
|
2079
|
-
continue;
|
|
2080
|
-
}
|
|
2081
2252
|
if (isParallelGroup(step)) {
|
|
2082
2253
|
parallelGroups.push({ start: flatStepCount, count: step.parallel.length, stepIndex });
|
|
2083
2254
|
for (const task of step.parallel) {
|
|
@@ -2169,8 +2340,9 @@ async function runSubagent(
|
|
|
2169
2340
|
lifecycleArtifactVersion: SUBAGENT_LIFECYCLE_ARTIFACT_VERSION,
|
|
2170
2341
|
runId: id,
|
|
2171
2342
|
...(config.sessionId ? { sessionId: config.sessionId } : {}),
|
|
2343
|
+
...(config.completionOwnerId ? { completionOwnerId: config.completionOwnerId } : {}),
|
|
2172
2344
|
mode: config.resultMode ?? (flatSteps.length > 1 ? "chain" : "single"),
|
|
2173
|
-
...(config.
|
|
2345
|
+
...(config.nestedSelf ? { isNested: true } : {}),
|
|
2174
2346
|
state: "running",
|
|
2175
2347
|
steering: createSteeringStatus(),
|
|
2176
2348
|
lastActivityAt: overallStartTime,
|
|
@@ -2200,9 +2372,37 @@ async function runSubagent(
|
|
|
2200
2372
|
outputFile: path.join(asyncDir, "output-0.log"),
|
|
2201
2373
|
});
|
|
2202
2374
|
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2375
|
+
let lastIndexedStatusState: AsyncStatus["state"] | undefined;
|
|
2376
|
+
const indexPersistence = createCapacityResilientJsonWriter({
|
|
2377
|
+
keepAlive: true,
|
|
2378
|
+
onSuccess: (_filePath, payload) => {
|
|
2379
|
+
lastIndexedStatusState = (payload as { state: AsyncStatus["state"] }).state;
|
|
2380
|
+
},
|
|
2381
|
+
onError: (error, filePath) => console.error(`Failed to update async run index '${filePath}':`, error),
|
|
2382
|
+
});
|
|
2383
|
+
const queueActiveRunIndex = (): void => {
|
|
2384
|
+
const state = statusPayload.state;
|
|
2385
|
+
if (state === lastIndexedStatusState && indexPersistence.pendingCount() === 0) return;
|
|
2386
|
+
indexPersistence.write(asyncDir, { state, toolCallId: statusPayload.toolCallId }, (_filePath, payload) => {
|
|
2387
|
+
const indexPayload = payload as { state: AsyncStatus["state"]; toolCallId?: string };
|
|
2388
|
+
updateActiveRunIndex(asyncDir, indexPayload.state, indexPayload.toolCallId, { retryCapacityErrors: true });
|
|
2389
|
+
});
|
|
2390
|
+
};
|
|
2391
|
+
const runPersistence = createCapacityResilientJsonWriter({
|
|
2392
|
+
keepAlive: true,
|
|
2393
|
+
onSuccess: (filePath) => {
|
|
2394
|
+
if (filePath === statusPath) queueActiveRunIndex();
|
|
2395
|
+
},
|
|
2396
|
+
onError: (error, filePath) => console.error(`Failed to persist async run state '${filePath}':`, error),
|
|
2397
|
+
});
|
|
2398
|
+
try {
|
|
2399
|
+
fs.mkdirSync(asyncDir, { recursive: true });
|
|
2400
|
+
} catch (error) {
|
|
2401
|
+
if (!isStorageCapacityError(error)) throw error;
|
|
2402
|
+
console.error(`Failed to prepare async run storage '${asyncDir}' while storage is full:`, error);
|
|
2403
|
+
}
|
|
2404
|
+
runPersistence.write(statusPath, statusPayload);
|
|
2405
|
+
|
|
2206
2406
|
let pendingParallelUsageCost: CostSummary = { inputTokens: 0, outputTokens: 0, costUsd: 0 };
|
|
2207
2407
|
const currentUsageTotals = (): CostSummary => {
|
|
2208
2408
|
const cost = results.reduce<CostSummary>((sum, result) => ({
|
|
@@ -2257,7 +2457,6 @@ async function runSubagent(
|
|
|
2257
2457
|
node.status = normalize(step.status);
|
|
2258
2458
|
setOptionalProperty(node, "error", step.error);
|
|
2259
2459
|
setOptionalProperty(node, "acceptanceStatus", step.acceptance?.status);
|
|
2260
|
-
setOptionalProperty(node, "checkpoint", step.checkpoint);
|
|
2261
2460
|
}
|
|
2262
2461
|
if (statusPayload.currentStep === node.flatIndex) graph.currentNodeId = node.id;
|
|
2263
2462
|
}
|
|
@@ -2275,15 +2474,65 @@ async function runSubagent(
|
|
|
2275
2474
|
for (const node of graph.nodes) updateNode(node);
|
|
2276
2475
|
statusPayload.workflowGraph = graph;
|
|
2277
2476
|
};
|
|
2278
|
-
let
|
|
2477
|
+
let finalResultCommitted = false;
|
|
2478
|
+
const statusResultState = (): AsyncStatus["state"] | undefined => {
|
|
2479
|
+
if (statusPayload.state === "running" || statusPayload.state === "queued") return undefined;
|
|
2480
|
+
return statusPayload.state;
|
|
2481
|
+
};
|
|
2482
|
+
const statusResultSummary = (state: AsyncStatus["state"]): string => {
|
|
2483
|
+
if (statusPayload.error) return statusPayload.error;
|
|
2484
|
+
if (state === "paused") return "Paused after interrupt. Waiting for explicit next action.";
|
|
2485
|
+
if (state === "stopped") return stopMessage;
|
|
2486
|
+
if (state === "rejected") return "Subagent rejected.";
|
|
2487
|
+
return state === "complete" ? "Subagent completed." : "Subagent failed.";
|
|
2488
|
+
};
|
|
2489
|
+
const statusResultSuccess = (state: AsyncStatus["state"], step: RunnerStatusStep): boolean | undefined => {
|
|
2490
|
+
if (step.status === "complete" || step.status === "completed") return true;
|
|
2491
|
+
if (step.status === "failed" || step.status === "stopped" || step.status === "rejected") return false;
|
|
2492
|
+
if (state === "complete") return true;
|
|
2493
|
+
if (state === "failed" || state === "stopped" || state === "rejected") return false;
|
|
2494
|
+
return undefined;
|
|
2495
|
+
};
|
|
2496
|
+
const writeRecoverableStatusResult = (): void => {
|
|
2497
|
+
const state = statusResultState();
|
|
2498
|
+
if (!state || finalResultCommitted || !config.sessionId) return;
|
|
2499
|
+
const now = statusPayload.endedAt ?? statusPayload.lastUpdate ?? Date.now();
|
|
2500
|
+
const summary = statusResultSummary(state);
|
|
2501
|
+
runPersistence.write(resultPath, omitUndefinedProperties({
|
|
2502
|
+
lifecycleArtifactVersion: SUBAGENT_LIFECYCLE_ARTIFACT_VERSION,
|
|
2503
|
+
id,
|
|
2504
|
+
runId: id,
|
|
2505
|
+
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("->")}`,
|
|
2506
|
+
mode: statusPayload.mode,
|
|
2507
|
+
success: state === "complete",
|
|
2508
|
+
state,
|
|
2509
|
+
summary,
|
|
2510
|
+
error: state === "failed" || state === "stopped" || state === "rejected" ? summary : undefined,
|
|
2511
|
+
stopped: state === "stopped" ? true : undefined,
|
|
2512
|
+
results: statusPayload.steps.map((step) => omitUndefinedProperties({
|
|
2513
|
+
agent: step.agent,
|
|
2514
|
+
output: step.status === "complete" || step.status === "completed" ? "" : step.error ?? summary,
|
|
2515
|
+
error: step.error,
|
|
2516
|
+
success: statusResultSuccess(state, step),
|
|
2517
|
+
sessionFile: step.sessionFile,
|
|
2518
|
+
model: step.model,
|
|
2519
|
+
attemptedModels: step.attemptedModels,
|
|
2520
|
+
modelAttempts: step.modelAttempts,
|
|
2521
|
+
})),
|
|
2522
|
+
exitCode: state === "complete" || state === "paused" ? 0 : 1,
|
|
2523
|
+
timestamp: now,
|
|
2524
|
+
durationMs: Math.max(0, now - overallStartTime),
|
|
2525
|
+
asyncDir,
|
|
2526
|
+
cwd,
|
|
2527
|
+
sessionId: config.sessionId,
|
|
2528
|
+
completionOwnerId: config.completionOwnerId,
|
|
2529
|
+
sessionFile: statusPayload.sessionFile ?? latestSessionFile,
|
|
2530
|
+
}), (filePath, payload) => writePendingAsyncResultFile(filePath, payload as Record<string, unknown>));
|
|
2531
|
+
};
|
|
2279
2532
|
const writeStatusPayloadNow = (): void => {
|
|
2280
2533
|
refreshWorkflowGraph();
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
if (activeState && activeState !== lastIndexedActiveState) {
|
|
2284
|
-
updateActiveRunIndex(asyncDir, statusPayload.state);
|
|
2285
|
-
}
|
|
2286
|
-
lastIndexedActiveState = activeState;
|
|
2534
|
+
writeRecoverableStatusResult();
|
|
2535
|
+
runPersistence.write(statusPath, statusPayload);
|
|
2287
2536
|
emitNestedSelfEvent(statusPayload.state === "running" || statusPayload.state === "queued" ? "subagent.nested.updated" : "subagent.nested.completed");
|
|
2288
2537
|
};
|
|
2289
2538
|
const statusWriteCoalescer = createFileCoalescer(writeStatusPayloadNow, 100);
|
|
@@ -2299,6 +2548,11 @@ async function runSubagent(
|
|
|
2299
2548
|
statusPayload.lastUpdate = Date.now();
|
|
2300
2549
|
writeStatusPayload();
|
|
2301
2550
|
};
|
|
2551
|
+
const updateExternalJob = (index: number, externalJob: ExternalJobStatus): void => {
|
|
2552
|
+
requiredStatusStep(statusPayload, index).externalJob = externalJob;
|
|
2553
|
+
statusPayload.lastUpdate = Date.now();
|
|
2554
|
+
writeStatusPayload();
|
|
2555
|
+
};
|
|
2302
2556
|
const registerStepInterrupt = (flatIndex: number, interrupt: (() => void) | undefined): void => {
|
|
2303
2557
|
if (!interrupt) {
|
|
2304
2558
|
activeChildInterrupts.delete(flatIndex);
|
|
@@ -2526,10 +2780,84 @@ async function runSubagent(
|
|
|
2526
2780
|
const activeLongRunningSteps = new Set<number>();
|
|
2527
2781
|
const mutatingFailureStates = initialStatusSteps.map(() => createMutatingFailureState());
|
|
2528
2782
|
const pendingToolResults: Array<{ tool: string; path?: string; mutates: boolean; startedAt?: number } | undefined> = initialStatusSteps.map(() => undefined);
|
|
2783
|
+
type ActiveToolCall = { key: string; tool: string; args: string; startedAt: number; path?: string; blocksSupervisor: boolean };
|
|
2784
|
+
const activeToolCalls = initialStatusSteps.map(() => new Map<string, ActiveToolCall>());
|
|
2785
|
+
const activeToolKeysByName = initialStatusSteps.map(() => new Map<string, string[]>());
|
|
2786
|
+
const activeToolSequences = initialStatusSteps.map(() => 0);
|
|
2787
|
+
const latestActiveToolCall = (flatIndex: number): ActiveToolCall | undefined => [...(activeToolCalls[flatIndex]?.values() ?? [])].sort((left, right) => right.startedAt - left.startedAt)[0];
|
|
2788
|
+
const refreshStepCurrentTool = (flatIndex: number): void => {
|
|
2789
|
+
const step = statusPayload.steps[flatIndex];
|
|
2790
|
+
if (!step) return;
|
|
2791
|
+
const active = latestActiveToolCall(flatIndex);
|
|
2792
|
+
if (!active) {
|
|
2793
|
+
delete step.currentTool;
|
|
2794
|
+
delete step.currentToolArgs;
|
|
2795
|
+
delete step.currentToolStartedAt;
|
|
2796
|
+
delete step.currentPath;
|
|
2797
|
+
return;
|
|
2798
|
+
}
|
|
2799
|
+
step.currentTool = active.tool;
|
|
2800
|
+
step.currentToolArgs = active.args;
|
|
2801
|
+
step.currentToolStartedAt = active.startedAt;
|
|
2802
|
+
setOptionalProperty(step, "currentPath", active.path);
|
|
2803
|
+
};
|
|
2804
|
+
const recordActiveToolCall = (flatIndex: number, event: { toolCallId?: unknown; toolName: string }, input: { argsPreview: string; currentPath?: string; blocksSupervisor: boolean; now: number }): ActiveToolCall => {
|
|
2805
|
+
const sequence = (activeToolSequences[flatIndex] ?? 0) + 1;
|
|
2806
|
+
activeToolSequences[flatIndex] = sequence;
|
|
2807
|
+
const key = toolTimeoutCallKey(event, sequence);
|
|
2808
|
+
const active: ActiveToolCall = {
|
|
2809
|
+
key,
|
|
2810
|
+
tool: event.toolName,
|
|
2811
|
+
args: input.argsPreview,
|
|
2812
|
+
startedAt: input.now,
|
|
2813
|
+
blocksSupervisor: input.blocksSupervisor,
|
|
2814
|
+
...(input.currentPath !== undefined ? { path: input.currentPath } : {}),
|
|
2815
|
+
};
|
|
2816
|
+
activeToolCalls[flatIndex]?.set(key, active);
|
|
2817
|
+
const keysByName = activeToolKeysByName[flatIndex];
|
|
2818
|
+
const keys = keysByName?.get(active.tool) ?? [];
|
|
2819
|
+
keys.push(key);
|
|
2820
|
+
keysByName?.set(active.tool, keys);
|
|
2821
|
+
refreshStepCurrentTool(flatIndex);
|
|
2822
|
+
return active;
|
|
2823
|
+
};
|
|
2824
|
+
const removeActiveToolCallKey = (flatIndex: number, key: string): ActiveToolCall | undefined => {
|
|
2825
|
+
const calls = activeToolCalls[flatIndex];
|
|
2826
|
+
const active = calls?.get(key);
|
|
2827
|
+
if (!active) return undefined;
|
|
2828
|
+
calls?.delete(key);
|
|
2829
|
+
const keysByName = activeToolKeysByName[flatIndex];
|
|
2830
|
+
const keys = keysByName?.get(active.tool)?.filter((candidate) => candidate !== key) ?? [];
|
|
2831
|
+
if (keys.length > 0) keysByName?.set(active.tool, keys);
|
|
2832
|
+
else keysByName?.delete(active.tool);
|
|
2833
|
+
return active;
|
|
2834
|
+
};
|
|
2835
|
+
const removeActiveToolCall = (flatIndex: number, event: { toolCallId?: unknown; toolName?: unknown }): ActiveToolCall | undefined => {
|
|
2836
|
+
const calls = activeToolCalls[flatIndex];
|
|
2837
|
+
const key = typeof event.toolCallId === "string" && event.toolCallId.length > 0
|
|
2838
|
+
? `id:${event.toolCallId}`
|
|
2839
|
+
: typeof event.toolName === "string"
|
|
2840
|
+
? activeToolKeysByName[flatIndex]?.get(event.toolName)?.[0]
|
|
2841
|
+
: calls?.size === 1
|
|
2842
|
+
? [...calls.keys()][0]
|
|
2843
|
+
: undefined;
|
|
2844
|
+
return key ? removeActiveToolCallKey(flatIndex, key) : undefined;
|
|
2845
|
+
};
|
|
2846
|
+
const openToolAttentionTarget = (flatIndex: number, now: number): ActiveToolCall | undefined => [...(activeToolCalls[flatIndex]?.values() ?? [])]
|
|
2847
|
+
.filter((active) => shouldEmitOpenToolAttention({ config: controlConfig, currentTool: active.tool, currentToolStartedAt: active.startedAt, now }))
|
|
2848
|
+
.sort((left, right) => left.startedAt - right.startedAt)[0];
|
|
2529
2849
|
const supervisorAttentionSteps = new Map<number, ActivityState | undefined>();
|
|
2530
2850
|
const mutatingFailureWindowMs = 5 * 60_000;
|
|
2531
|
-
const appendControlEvent = (
|
|
2851
|
+
const appendControlEvent = (rawEvent: ReturnType<typeof buildControlEvent>) => {
|
|
2532
2852
|
if (!controlConfig.enabled) return;
|
|
2853
|
+
const contextStep = statusPayload.steps[rawEvent.index ?? statusPayload.currentStep ?? 0];
|
|
2854
|
+
const event = {
|
|
2855
|
+
...rawEvent,
|
|
2856
|
+
...(contextStep?.workflowKey ?? statusPayload.workflowKey ? { workflowKey: contextStep?.workflowKey ?? statusPayload.workflowKey } : {}),
|
|
2857
|
+
...(contextStep?.phase ? { phase: contextStep.phase } : {}),
|
|
2858
|
+
...(contextStep?.label ? { label: contextStep.label } : {}),
|
|
2859
|
+
...(contextStep?.description ? { taskPreview: contextStep.description } : {}),
|
|
2860
|
+
};
|
|
2533
2861
|
const childIntercomTarget = config.childIntercomTargets?.[event.index ?? statusPayload.currentStep];
|
|
2534
2862
|
const channels = event.type === "active_long_running"
|
|
2535
2863
|
? controlConfig.notifyChannels.filter((channel) => channel !== "intercom")
|
|
@@ -2566,6 +2894,34 @@ async function runSubagent(
|
|
|
2566
2894
|
currentActivityState = nextRunState;
|
|
2567
2895
|
setOptionalProperty(statusPayload, "activityState", nextRunState);
|
|
2568
2896
|
};
|
|
2897
|
+
const maybeEmitOpenToolAttention = (flatIndex: number, now: number): boolean => {
|
|
2898
|
+
const step = statusPayload.steps[flatIndex];
|
|
2899
|
+
if (!step || step.status !== "running" || step.activityState === "needs_attention") return false;
|
|
2900
|
+
const target = openToolAttentionTarget(flatIndex, now);
|
|
2901
|
+
if (!target) return false;
|
|
2902
|
+
const previous = step.activityState;
|
|
2903
|
+
step.activityState = "needs_attention";
|
|
2904
|
+
statusPayload.activityState = "needs_attention";
|
|
2905
|
+
const toolDurationMs = Math.max(0, now - target.startedAt);
|
|
2906
|
+
appendControlEvent(buildControlEvent(omitUndefinedProperties({
|
|
2907
|
+
type: "needs_attention",
|
|
2908
|
+
from: previous,
|
|
2909
|
+
to: "needs_attention",
|
|
2910
|
+
runId: id,
|
|
2911
|
+
agent: step.agent,
|
|
2912
|
+
index: flatIndex,
|
|
2913
|
+
ts: now,
|
|
2914
|
+
message: `${step.agent} has had tool '${target.tool}' open for ${Math.floor(toolDurationMs / 1000)}s`,
|
|
2915
|
+
reason: "tool_open_threshold",
|
|
2916
|
+
turns: step.turnCount,
|
|
2917
|
+
tokens: step.tokens?.total,
|
|
2918
|
+
toolCount: step.toolCount,
|
|
2919
|
+
currentTool: target.tool,
|
|
2920
|
+
currentToolDurationMs: toolDurationMs,
|
|
2921
|
+
currentPath: target.path,
|
|
2922
|
+
})));
|
|
2923
|
+
return true;
|
|
2924
|
+
};
|
|
2569
2925
|
const maybeEmitActiveLongRunning = (flatIndex: number, now: number): boolean => {
|
|
2570
2926
|
if (!controlConfig.enabled || activeLongRunningSteps.has(flatIndex)) return false;
|
|
2571
2927
|
const step = statusPayload.steps[flatIndex];
|
|
@@ -2826,20 +3182,19 @@ async function runSubagent(
|
|
|
2826
3182
|
if (event.type === "tool_execution_start" && event.toolName) {
|
|
2827
3183
|
const mutates = isMutatingTool(event.toolName, event.args);
|
|
2828
3184
|
const currentPath = resolveCurrentPath(event.toolName, event.args);
|
|
3185
|
+
const argsPreview = extractToolArgsPreview(event.args ?? {});
|
|
3186
|
+
const blocksSupervisor = isBlockingSupervisorTool(event.toolName, event.args);
|
|
2829
3187
|
step.toolCount = (step.toolCount ?? 0) + 1;
|
|
2830
3188
|
const configuredToolBudget = flatSteps[flatIndex]?.toolBudget;
|
|
2831
3189
|
if (configuredToolBudget) {
|
|
2832
3190
|
step.toolBudget = toolBudgetState(configuredToolBudget, step.toolCount);
|
|
2833
3191
|
statusPayload.toolBudget = step.toolBudget;
|
|
2834
3192
|
}
|
|
2835
|
-
|
|
2836
|
-
step.currentToolArgs = extractToolArgsPreview(event.args ?? {});
|
|
2837
|
-
step.currentToolStartedAt = now;
|
|
2838
|
-
setOptionalProperty(step, "currentPath", currentPath);
|
|
3193
|
+
recordActiveToolCall(flatIndex, { toolCallId: (event as { toolCallId?: unknown }).toolCallId, toolName: event.toolName }, { argsPreview, currentPath, blocksSupervisor, now });
|
|
2839
3194
|
pendingToolResults[flatIndex] = omitUndefinedProperties({ tool: event.toolName, path: currentPath, mutates, startedAt: now });
|
|
2840
3195
|
statusPayload.toolCount = (statusPayload.toolCount ?? 0) + 1;
|
|
2841
3196
|
syncTopLevelCurrentTool();
|
|
2842
|
-
if (controlConfig.enabled &&
|
|
3197
|
+
if (controlConfig.enabled && blocksSupervisor && step.activityState !== "needs_attention") {
|
|
2843
3198
|
const previous = step.activityState;
|
|
2844
3199
|
step.activityState = "needs_attention";
|
|
2845
3200
|
supervisorAttentionSteps.set(flatIndex, previous);
|
|
@@ -2864,16 +3219,15 @@ async function runSubagent(
|
|
|
2864
3219
|
})));
|
|
2865
3220
|
}
|
|
2866
3221
|
} else if (event.type === "tool_execution_end") {
|
|
2867
|
-
|
|
3222
|
+
const endedTool = removeActiveToolCall(flatIndex, event);
|
|
3223
|
+
if (endedTool) {
|
|
2868
3224
|
step.recentTools ??= [];
|
|
2869
|
-
step.recentTools.push({ tool:
|
|
3225
|
+
step.recentTools.push({ tool: endedTool.tool, args: endedTool.args, endMs: now });
|
|
2870
3226
|
}
|
|
3227
|
+
refreshStepCurrentTool(flatIndex);
|
|
2871
3228
|
const supervisorPreviousActivity = supervisorAttentionSteps.get(flatIndex);
|
|
2872
|
-
const
|
|
2873
|
-
delete
|
|
2874
|
-
delete step.currentToolArgs;
|
|
2875
|
-
delete step.currentToolStartedAt;
|
|
2876
|
-
delete step.currentPath;
|
|
3229
|
+
const stillBlockingSupervisor = [...(activeToolCalls[flatIndex]?.values() ?? [])].some((active) => active.blocksSupervisor);
|
|
3230
|
+
const clearedSupervisorAttention = endedTool?.blocksSupervisor && !stillBlockingSupervisor ? supervisorAttentionSteps.delete(flatIndex) : false;
|
|
2877
3231
|
if (clearedSupervisorAttention && step.activityState === "needs_attention") {
|
|
2878
3232
|
setOptionalProperty(step, "activityState", supervisorPreviousActivity);
|
|
2879
3233
|
syncAggregateActivityState();
|
|
@@ -2993,6 +3347,8 @@ async function runSubagent(
|
|
|
2993
3347
|
})));
|
|
2994
3348
|
changed = true;
|
|
2995
3349
|
}
|
|
3350
|
+
} else if (maybeEmitOpenToolAttention(index, now)) {
|
|
3351
|
+
changed = true;
|
|
2996
3352
|
} else if (maybeEmitActiveLongRunning(index, now)) {
|
|
2997
3353
|
changed = true;
|
|
2998
3354
|
}
|
|
@@ -3124,10 +3480,6 @@ async function runSubagent(
|
|
|
3124
3480
|
onInterrupt: interruptRunner,
|
|
3125
3481
|
onTimeout: timeoutRunner,
|
|
3126
3482
|
onStop: stopRunner,
|
|
3127
|
-
onCheckpointDecision: (decision) => {
|
|
3128
|
-
pendingCheckpointDecision = decision;
|
|
3129
|
-
wakeCheckpointDecision?.();
|
|
3130
|
-
},
|
|
3131
3483
|
onSteer: (request) => {
|
|
3132
3484
|
const targetStep = request.targetIndex !== undefined ? statusPayload.steps[request.targetIndex] : undefined;
|
|
3133
3485
|
if (targetStep?.status === "pending") {
|
|
@@ -3178,20 +3530,6 @@ async function runSubagent(
|
|
|
3178
3530
|
|
|
3179
3531
|
let flatIndex = 0;
|
|
3180
3532
|
let stepCursor = 0;
|
|
3181
|
-
const waitForCheckpointDecision = async (): Promise<"approved" | "rejected" | undefined> => {
|
|
3182
|
-
while (!pendingCheckpointDecision && !interrupted && !timedOut && !stopped) {
|
|
3183
|
-
await new Promise<void>((resolve) => {
|
|
3184
|
-
wakeCheckpointDecision = resolve;
|
|
3185
|
-
const timer = setTimeout(resolve, POLL_INTERVAL_MS);
|
|
3186
|
-
timer.unref?.();
|
|
3187
|
-
});
|
|
3188
|
-
wakeCheckpointDecision = undefined;
|
|
3189
|
-
}
|
|
3190
|
-
const decision = pendingCheckpointDecision;
|
|
3191
|
-
pendingCheckpointDecision = undefined;
|
|
3192
|
-
return decision;
|
|
3193
|
-
};
|
|
3194
|
-
|
|
3195
3533
|
while (true) {
|
|
3196
3534
|
if (interrupted || timedOut || stopped || turnBudgetExceeded) break;
|
|
3197
3535
|
consumePendingAppendRequests();
|
|
@@ -3209,64 +3547,6 @@ async function runSubagent(
|
|
|
3209
3547
|
const stepIndex = stepCursor++;
|
|
3210
3548
|
const step = steps[stepIndex]!;
|
|
3211
3549
|
|
|
3212
|
-
if (isCheckpointRunnerStep(step)) {
|
|
3213
|
-
const now = Date.now();
|
|
3214
|
-
const statusStep = statusPayload.steps[flatIndex];
|
|
3215
|
-
const checkpoint = { name: step.checkpoint, ...(step.message ? { message: step.message } : {}), status: "pending" as const, stepIndex };
|
|
3216
|
-
statusPayload.state = "paused";
|
|
3217
|
-
statusPayload.currentStep = flatIndex;
|
|
3218
|
-
statusPayload.checkpoint = checkpoint;
|
|
3219
|
-
delete statusPayload.activityState;
|
|
3220
|
-
statusPayload.lastUpdate = now;
|
|
3221
|
-
if (statusStep) {
|
|
3222
|
-
statusStep.status = "paused";
|
|
3223
|
-
statusStep.startedAt = now;
|
|
3224
|
-
statusStep.checkpoint = checkpoint;
|
|
3225
|
-
}
|
|
3226
|
-
writeStatusPayload();
|
|
3227
|
-
appendJsonl(eventsPath, JSON.stringify({ type: "subagent.checkpoint.paused", ts: now, runId: id, stepIndex, checkpoint }));
|
|
3228
|
-
const decision = await waitForCheckpointDecision();
|
|
3229
|
-
if (decision === "approved") {
|
|
3230
|
-
const approvedAt = Date.now();
|
|
3231
|
-
const approved = { ...checkpoint, status: "approved" as const, approvedAt };
|
|
3232
|
-
statusPayload.state = "running";
|
|
3233
|
-
statusPayload.checkpoint = approved;
|
|
3234
|
-
if (statusStep) {
|
|
3235
|
-
statusStep.status = "complete";
|
|
3236
|
-
statusStep.endedAt = approvedAt;
|
|
3237
|
-
statusStep.durationMs = approvedAt - now;
|
|
3238
|
-
statusStep.exitCode = 0;
|
|
3239
|
-
statusStep.checkpoint = approved;
|
|
3240
|
-
}
|
|
3241
|
-
statusPayload.lastUpdate = approvedAt;
|
|
3242
|
-
writeStatusPayload();
|
|
3243
|
-
appendJsonl(eventsPath, JSON.stringify({ type: "subagent.checkpoint.approved", ts: approvedAt, runId: id, stepIndex, checkpoint: approved }));
|
|
3244
|
-
flatIndex++;
|
|
3245
|
-
continue;
|
|
3246
|
-
}
|
|
3247
|
-
if (decision === "rejected") {
|
|
3248
|
-
const rejectedAt = Date.now();
|
|
3249
|
-
const rejected = { ...checkpoint, status: "rejected" as const, rejectedAt };
|
|
3250
|
-
checkpointRejected = true;
|
|
3251
|
-
statusPayload.state = "rejected";
|
|
3252
|
-
statusPayload.error = `Checkpoint '${step.checkpoint}' rejected.`;
|
|
3253
|
-
statusPayload.checkpoint = rejected;
|
|
3254
|
-
if (statusStep) {
|
|
3255
|
-
statusStep.status = "rejected";
|
|
3256
|
-
statusStep.error = statusPayload.error;
|
|
3257
|
-
statusStep.endedAt = rejectedAt;
|
|
3258
|
-
statusStep.durationMs = rejectedAt - now;
|
|
3259
|
-
statusStep.exitCode = 1;
|
|
3260
|
-
statusStep.checkpoint = rejected;
|
|
3261
|
-
}
|
|
3262
|
-
statusPayload.lastUpdate = rejectedAt;
|
|
3263
|
-
writeStatusPayload();
|
|
3264
|
-
appendJsonl(eventsPath, JSON.stringify({ type: "subagent.checkpoint.rejected", ts: rejectedAt, runId: id, stepIndex, checkpoint: rejected }));
|
|
3265
|
-
break;
|
|
3266
|
-
}
|
|
3267
|
-
break;
|
|
3268
|
-
}
|
|
3269
|
-
|
|
3270
3550
|
if (isDynamicRunnerGroup(step)) {
|
|
3271
3551
|
const groupStartFlatIndex = flatIndex;
|
|
3272
3552
|
let materialized: ReturnType<typeof materializeDynamicParallelStep>;
|
|
@@ -3555,10 +3835,12 @@ async function runSubagent(
|
|
|
3555
3835
|
timeoutMessage,
|
|
3556
3836
|
stopMessage,
|
|
3557
3837
|
turnBudget: config.turnBudget,
|
|
3838
|
+
toolTimeoutMs: task.toolTimeoutMs ?? config.toolTimeoutMs,
|
|
3558
3839
|
onAttemptStart: (attempt) => updateStepModel(fi, attempt.model, attempt.thinking),
|
|
3559
3840
|
onChildEvent: (event) => updateStepFromChildEvent(fi, event),
|
|
3560
3841
|
onWriterProcess,
|
|
3561
3842
|
onExternalProcess: (process) => updateExternalProcess(fi, process),
|
|
3843
|
+
onExternalJob: (externalJob) => updateExternalJob(fi, externalJob),
|
|
3562
3844
|
skipAcceptance: () => timedOut || stopped,
|
|
3563
3845
|
}), config.deadlineAt);
|
|
3564
3846
|
const taskEndTime = Date.now();
|
|
@@ -3784,6 +4066,7 @@ async function runSubagent(
|
|
|
3784
4066
|
asyncDir,
|
|
3785
4067
|
runId: id,
|
|
3786
4068
|
stepIndex,
|
|
4069
|
+
writeStatus: () => writeStatusPayload(),
|
|
3787
4070
|
});
|
|
3788
4071
|
flatIndex += group.parallel.length;
|
|
3789
4072
|
break;
|
|
@@ -3811,6 +4094,7 @@ async function runSubagent(
|
|
|
3811
4094
|
asyncDir,
|
|
3812
4095
|
runId: id,
|
|
3813
4096
|
stepIndex,
|
|
4097
|
+
writeStatus: () => writeStatusPayload(),
|
|
3814
4098
|
});
|
|
3815
4099
|
flatIndex += group.parallel.length;
|
|
3816
4100
|
break;
|
|
@@ -3842,6 +4126,7 @@ async function runSubagent(
|
|
|
3842
4126
|
asyncDir,
|
|
3843
4127
|
runId: id,
|
|
3844
4128
|
stepIndex,
|
|
4129
|
+
writeStatus: () => writeStatusPayload(),
|
|
3845
4130
|
});
|
|
3846
4131
|
const parallelResults = await mapConcurrent(
|
|
3847
4132
|
group.parallel,
|
|
@@ -3938,10 +4223,12 @@ async function runSubagent(
|
|
|
3938
4223
|
timeoutMessage,
|
|
3939
4224
|
stopMessage,
|
|
3940
4225
|
turnBudget: config.turnBudget,
|
|
4226
|
+
toolTimeoutMs: taskForRun.toolTimeoutMs ?? config.toolTimeoutMs,
|
|
3941
4227
|
onAttemptStart: (attempt) => updateStepModel(fi, attempt.model, attempt.thinking),
|
|
3942
4228
|
onChildEvent: (event) => updateStepFromChildEvent(fi, event),
|
|
3943
4229
|
onWriterProcess,
|
|
3944
4230
|
onExternalProcess: (process) => updateExternalProcess(fi, process),
|
|
4231
|
+
onExternalJob: (externalJob) => updateExternalJob(fi, externalJob),
|
|
3945
4232
|
skipAcceptance: () => timedOut || stopped,
|
|
3946
4233
|
}), config.deadlineAt);
|
|
3947
4234
|
if (task.sessionFile) {
|
|
@@ -4179,6 +4466,32 @@ async function runSubagent(
|
|
|
4179
4466
|
}
|
|
4180
4467
|
} else {
|
|
4181
4468
|
const seqStep = step as SubagentStep;
|
|
4469
|
+
let singleWorktreeSetup: WorktreeSetup | undefined;
|
|
4470
|
+
if (seqStep.worktree) {
|
|
4471
|
+
try {
|
|
4472
|
+
singleWorktreeSetup = createWorktrees(cwd, `${id}-s${stepIndex}`, 1, omitUndefinedProperties({
|
|
4473
|
+
agents: [seqStep.agent],
|
|
4474
|
+
setupHook: config.worktreeSetupHook
|
|
4475
|
+
? omitUndefinedProperties({ hookPath: config.worktreeSetupHook, timeoutMs: config.worktreeSetupHookTimeoutMs })
|
|
4476
|
+
: undefined,
|
|
4477
|
+
baseDir: config.worktreeBaseDir,
|
|
4478
|
+
}));
|
|
4479
|
+
writePendingParallelHandoff({
|
|
4480
|
+
manifestPath: parallelHandoffPath(asyncDir),
|
|
4481
|
+
runId: id,
|
|
4482
|
+
mode: "single",
|
|
4483
|
+
source: "async",
|
|
4484
|
+
cwd,
|
|
4485
|
+
stepIndex,
|
|
4486
|
+
flatStartIndex: flatIndex,
|
|
4487
|
+
setup: singleWorktreeSetup,
|
|
4488
|
+
});
|
|
4489
|
+
} catch (error) {
|
|
4490
|
+
if (singleWorktreeSetup) cleanupWorktrees(singleWorktreeSetup);
|
|
4491
|
+
throw error;
|
|
4492
|
+
}
|
|
4493
|
+
}
|
|
4494
|
+
const singleCwd = singleWorktreeSetup?.worktrees[0]?.agentCwd ?? cwd;
|
|
4182
4495
|
const stepStartTime = Date.now();
|
|
4183
4496
|
statusPayload.currentStep = flatIndex;
|
|
4184
4497
|
requiredStatusStep(statusPayload, flatIndex).status = "running";
|
|
@@ -4202,8 +4515,11 @@ async function runSubagent(
|
|
|
4202
4515
|
}));
|
|
4203
4516
|
|
|
4204
4517
|
flushPendingStepSteers(flatIndex);
|
|
4205
|
-
const
|
|
4206
|
-
|
|
4518
|
+
const executionStep = singleWorktreeSetup ? { ...seqStep, cwd: singleCwd } : seqStep;
|
|
4519
|
+
let singleResult: Awaited<ReturnType<typeof runSingleStepWithTimeout>>;
|
|
4520
|
+
try {
|
|
4521
|
+
singleResult = await runSingleStepWithTimeout(executionStep, compactOptional<SingleStepContext>({
|
|
4522
|
+
previousOutput, placeholder, cwd: singleCwd, sessionEnabled,
|
|
4207
4523
|
outputs: statusPayload.mode === "single" ? undefined : outputs,
|
|
4208
4524
|
sessionDir: config.sessionDir,
|
|
4209
4525
|
artifactsDir, artifactConfig, id,
|
|
@@ -4228,12 +4544,18 @@ async function runSubagent(
|
|
|
4228
4544
|
timeoutMessage,
|
|
4229
4545
|
stopMessage,
|
|
4230
4546
|
turnBudget: config.turnBudget,
|
|
4547
|
+
toolTimeoutMs: seqStep.toolTimeoutMs ?? config.toolTimeoutMs,
|
|
4231
4548
|
onAttemptStart: (attempt) => updateStepModel(flatIndex, attempt.model, attempt.thinking),
|
|
4232
4549
|
onChildEvent: (event) => updateStepFromChildEvent(flatIndex, event),
|
|
4233
4550
|
onWriterProcess,
|
|
4234
4551
|
onExternalProcess: (process) => updateExternalProcess(flatIndex, process),
|
|
4552
|
+
onExternalJob: (externalJob) => updateExternalJob(flatIndex, externalJob),
|
|
4235
4553
|
skipAcceptance: () => timedOut || stopped,
|
|
4236
|
-
|
|
4554
|
+
}), config.deadlineAt);
|
|
4555
|
+
} catch (error) {
|
|
4556
|
+
if (singleWorktreeSetup) cleanupWorktrees(singleWorktreeSetup);
|
|
4557
|
+
throw error;
|
|
4558
|
+
}
|
|
4237
4559
|
if (seqStep.sessionFile) {
|
|
4238
4560
|
latestSessionFile = seqStep.sessionFile;
|
|
4239
4561
|
}
|
|
@@ -4282,6 +4604,7 @@ async function runSubagent(
|
|
|
4282
4604
|
toolBudgetBlocked: singleResult.toolBudgetBlocked,
|
|
4283
4605
|
runner: singleResult.runner,
|
|
4284
4606
|
externalProcess: singleResult.externalProcess,
|
|
4607
|
+
externalJob: singleResult.externalJob,
|
|
4285
4608
|
}));
|
|
4286
4609
|
if (seqStep.outputName) {
|
|
4287
4610
|
outputs[seqStep.outputName] = outputEntryFromAsyncResult({
|
|
@@ -4372,6 +4695,48 @@ async function runSubagent(
|
|
|
4372
4695
|
durationMs: stepEndTime - stepStartTime,
|
|
4373
4696
|
tokens: stepTokens,
|
|
4374
4697
|
}));
|
|
4698
|
+
if (singleWorktreeSetup && !singleResult.detached) {
|
|
4699
|
+
const diffs = diffWorktrees(singleWorktreeSetup, [seqStep.agent], path.join(asyncDir, "worktree-diffs", `step-${stepIndex}`));
|
|
4700
|
+
const diffSummary = formatWorktreeDiffSummary(diffs);
|
|
4701
|
+
const manifestPath = parallelHandoffPath(asyncDir);
|
|
4702
|
+
const handoff = {
|
|
4703
|
+
manifestPath,
|
|
4704
|
+
runId: id,
|
|
4705
|
+
mode: "single" as const,
|
|
4706
|
+
source: "async" as const,
|
|
4707
|
+
cwd,
|
|
4708
|
+
stepIndex,
|
|
4709
|
+
flatStartIndex: flatIndex,
|
|
4710
|
+
setup: singleWorktreeSetup,
|
|
4711
|
+
diffs,
|
|
4712
|
+
results: [{
|
|
4713
|
+
agent: singleResult.agent,
|
|
4714
|
+
status: singleResult.stopped ? "stopped" as const : singleResult.interrupted ? "paused" as const : singleResult.exitCode === 0 ? "completed" as const : "failed" as const,
|
|
4715
|
+
summary: singleResult.output || singleResult.error || "(no output)",
|
|
4716
|
+
...(singleResult.artifactPaths?.outputPath ? { outputPath: singleResult.artifactPaths.outputPath } : {}),
|
|
4717
|
+
...(singleResult.structuredOutput !== undefined ? { structuredOutput: singleResult.structuredOutput } : {}),
|
|
4718
|
+
...(singleResult.structuredOutputPath ? { structuredOutputPath: singleResult.structuredOutputPath } : {}),
|
|
4719
|
+
...(singleResult.sessionFile ? { sessionPath: singleResult.sessionFile } : {}),
|
|
4720
|
+
}],
|
|
4721
|
+
};
|
|
4722
|
+
try {
|
|
4723
|
+
writeParallelHandoffGroup(handoff);
|
|
4724
|
+
const cleanup = cleanupWorktrees(singleWorktreeSetup, {
|
|
4725
|
+
kind: "preserve",
|
|
4726
|
+
capturedDiffs: diffs,
|
|
4727
|
+
handoffManifestPath: manifestPath,
|
|
4728
|
+
...(config.parentWorkflowRunId && singleResult.sessionFile && fs.existsSync(singleResult.sessionFile) && !singleResult.stopped
|
|
4729
|
+
? { cleanupBlocker: "retained child resume requires managed worktree cwd" }
|
|
4730
|
+
: {}),
|
|
4731
|
+
});
|
|
4732
|
+
statusPayload.parallelHandoff = writeParallelHandoffGroup({ ...handoff, cleanup });
|
|
4733
|
+
previousOutput = [previousOutput, diffSummary, formatParallelHandoffReference(statusPayload.parallelHandoff)].filter(Boolean).join("\n\n");
|
|
4734
|
+
} catch (error) {
|
|
4735
|
+
previousOutput = [previousOutput, diffSummary, formatParallelHandoffError(error)].filter(Boolean).join("\n\n");
|
|
4736
|
+
}
|
|
4737
|
+
writeStatusPayload();
|
|
4738
|
+
}
|
|
4739
|
+
|
|
4375
4740
|
if (singleResult.completionGuardTriggered) {
|
|
4376
4741
|
const event = buildControlEvent(omitUndefinedProperties({
|
|
4377
4742
|
from: requiredStatusStep(statusPayload, flatIndex).activityState,
|
|
@@ -4473,8 +4838,8 @@ async function runSubagent(
|
|
|
4473
4838
|
stopped: result.stopped,
|
|
4474
4839
|
turnBudgetExceeded: result.turnBudgetExceeded,
|
|
4475
4840
|
})));
|
|
4476
|
-
statusPayload.state =
|
|
4477
|
-
closeSteerInbox(asyncDir, statusPayload.state);
|
|
4841
|
+
statusPayload.state = stopped || signalTerminated ? "stopped" : timedOut || turnBudgetExceeded || usageBudgetExceeded || statusPayload.error ? "failed" : interrupted ? "paused" : results.every((r) => r.success) ? "complete" : "failed";
|
|
4842
|
+
closeSteerInbox(asyncDir, statusPayload.state, (filePath, payload) => runPersistence.write(filePath, payload));
|
|
4478
4843
|
disposeControlInbox();
|
|
4479
4844
|
for (const request of consumeSteerRequests(asyncDir)) deliverSteerRequest(request);
|
|
4480
4845
|
const effectiveSessionFile = sessionFile ?? latestSessionFile;
|
|
@@ -4525,16 +4890,15 @@ async function runSubagent(
|
|
|
4525
4890
|
statusPayload.error = `Step failed: ${failedStep.agent}`;
|
|
4526
4891
|
}
|
|
4527
4892
|
}
|
|
4528
|
-
writeStatusPayload();
|
|
4529
4893
|
try {
|
|
4530
|
-
|
|
4894
|
+
runPersistence.write(resultPath, {
|
|
4531
4895
|
lifecycleArtifactVersion: SUBAGENT_LIFECYCLE_ARTIFACT_VERSION,
|
|
4532
4896
|
id,
|
|
4533
4897
|
agent: agentName,
|
|
4534
4898
|
mode: resultMode,
|
|
4535
|
-
success: !
|
|
4536
|
-
state:
|
|
4537
|
-
summary:
|
|
4899
|
+
success: !stopped && !timedOut && !turnBudgetExceeded && !usageBudgetExceeded && !interrupted && !signalTerminated && results.every((r) => r.success),
|
|
4900
|
+
state: stopped || signalTerminated ? "stopped" : timedOut || turnBudgetExceeded || usageBudgetExceeded ? "failed" : interrupted ? "paused" : results.every((r) => r.success) ? "complete" : "failed",
|
|
4901
|
+
summary: stopped ? stopMessage : signalTerminated ? (statusPayload.error ?? "Subagent process terminated by signal.") : timedOut ? (timeoutMessage ?? "Subagent timed out.") : turnBudgetExceeded ? (statusPayload.error ?? "Subagent exceeded turn budget.") : usageBudgetExceeded ? (statusPayload.error ?? "Usage budget exhausted.") : interrupted ? "Paused after interrupt. Waiting for explicit next action." : summary,
|
|
4538
4902
|
...(config.timeoutMs !== undefined ? { timeoutMs: config.timeoutMs } : {}),
|
|
4539
4903
|
...(config.deadlineAt !== undefined ? { deadlineAt: config.deadlineAt } : {}),
|
|
4540
4904
|
...(statusPayload.turnBudget ? { turnBudget: statusPayload.turnBudget } : {}),
|
|
@@ -4543,7 +4907,6 @@ async function runSubagent(
|
|
|
4543
4907
|
...(statusPayload.toolBudget ? { toolBudget: statusPayload.toolBudget } : {}),
|
|
4544
4908
|
...(statusPayload.toolBudgetBlocked ? { toolBudgetBlocked: true } : {}),
|
|
4545
4909
|
...(statusPayload.usageBudget ? { usageBudget: statusPayload.usageBudget } : {}),
|
|
4546
|
-
...(statusPayload.checkpoint ? { checkpoint: statusPayload.checkpoint } : {}),
|
|
4547
4910
|
...(stopped ? { stopped: true, error: stopMessage } : timedOut ? { timedOut: true, error: timeoutMessage ?? "Subagent timed out." } : turnBudgetExceeded ? { error: statusPayload.error ?? "Subagent exceeded turn budget." } : usageBudgetExceeded ? { error: statusPayload.error ?? "Usage budget exhausted." } : {}),
|
|
4548
4911
|
results: results.map((r) => omitUndefinedProperties({
|
|
4549
4912
|
agent: r.agent,
|
|
@@ -4581,6 +4944,7 @@ async function runSubagent(
|
|
|
4581
4944
|
runtimeAcknowledgedExtensions: r.runtimeAcknowledgedExtensions,
|
|
4582
4945
|
runner: r.runner,
|
|
4583
4946
|
externalProcess: r.externalProcess,
|
|
4947
|
+
externalJob: r.externalJob,
|
|
4584
4948
|
execution: r.execution,
|
|
4585
4949
|
review: r.review,
|
|
4586
4950
|
effects: r.effects,
|
|
@@ -4599,13 +4963,12 @@ async function runSubagent(
|
|
|
4599
4963
|
capabilityAudit: statusPayload.capabilityAudit,
|
|
4600
4964
|
...(config.parentWorkflowRunId ? { parentWorkflowRunId: config.parentWorkflowRunId } : {}),
|
|
4601
4965
|
...(config.workflowKey ? { workflowKey: config.workflowKey } : {}),
|
|
4602
|
-
exitCode:
|
|
4966
|
+
exitCode: stopped || timedOut || turnBudgetExceeded || usageBudgetExceeded ? 1 : interrupted || results.every((r) => r.success) ? 0 : 1,
|
|
4603
4967
|
timestamp: runEndedAt,
|
|
4604
4968
|
durationMs: runEndedAt - overallStartTime,
|
|
4605
4969
|
totalTokens: statusPayload.totalTokens,
|
|
4606
4970
|
totalCost: finalTotalCost,
|
|
4607
4971
|
usageBudget: statusPayload.usageBudget,
|
|
4608
|
-
checkpoint: statusPayload.checkpoint,
|
|
4609
4972
|
truncated,
|
|
4610
4973
|
artifactsDir,
|
|
4611
4974
|
cwd,
|
|
@@ -4614,6 +4977,7 @@ async function runSubagent(
|
|
|
4614
4977
|
launchResolvedExtensions: config.launchResolvedExtensions,
|
|
4615
4978
|
runtimeAcknowledgedExtensions: singleRuntimeAcknowledgedExtensions,
|
|
4616
4979
|
sessionId: config.sessionId,
|
|
4980
|
+
completionOwnerId: config.completionOwnerId,
|
|
4617
4981
|
sessionFile: effectiveSessionFile,
|
|
4618
4982
|
intercomTarget: config.controlIntercomTarget,
|
|
4619
4983
|
shareUrl,
|
|
@@ -4621,10 +4985,16 @@ async function runSubagent(
|
|
|
4621
4985
|
shareError,
|
|
4622
4986
|
...(taskIndex !== undefined && { taskIndex }),
|
|
4623
4987
|
...(totalTasks !== undefined && { totalTasks }),
|
|
4624
|
-
});
|
|
4988
|
+
}, (filePath, payload) => { writeAsyncResultFile(filePath, payload as Record<string, unknown>); });
|
|
4989
|
+
finalResultCommitted = true;
|
|
4625
4990
|
} catch (err) {
|
|
4626
|
-
|
|
4991
|
+
const message = `Failed to write result file ${resultPath}: ${err instanceof Error ? err.message : String(err)}`;
|
|
4992
|
+
console.error(message, err);
|
|
4993
|
+
statusPayload.state = "failed";
|
|
4994
|
+
statusPayload.error = message;
|
|
4995
|
+
statusPayload.lastUpdate = Date.now();
|
|
4627
4996
|
}
|
|
4997
|
+
writeStatusPayload();
|
|
4628
4998
|
appendJsonl(
|
|
4629
4999
|
eventsPath,
|
|
4630
5000
|
JSON.stringify({
|
|
@@ -4656,7 +5026,11 @@ async function runSubagent(
|
|
|
4656
5026
|
sessionFile: effectiveSessionFile,
|
|
4657
5027
|
shareUrl,
|
|
4658
5028
|
shareError,
|
|
5029
|
+
}), (filePath, content) => runPersistence.write(filePath, { content }, (_path, payload) => {
|
|
5030
|
+
fs.writeFileSync(_path, (payload as { content: string }).content, "utf-8");
|
|
4659
5031
|
}));
|
|
5032
|
+
if (runPersistence.pendingCount() === 0) runPersistence.dispose();
|
|
5033
|
+
if (indexPersistence.pendingCount() === 0) indexPersistence.dispose();
|
|
4660
5034
|
if (config.runnerProcessInstanceId) {
|
|
4661
5035
|
const writers: Record<string, PiWriterProcessInstanceExitV1[]> = {};
|
|
4662
5036
|
const expectedWriters: Record<string, number> = {};
|