pi-subagents 0.50.0 → 0.52.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 +103 -0
- package/agents/oracle.md +3 -1
- package/agents/reviewer.md +1 -0
- package/agents/scout.md +2 -2
- package/agents/worker.md +2 -1
- package/async-retention-discovery-worker.mjs +180 -0
- package/docs/agents.md +40 -2
- package/docs/configuration.md +30 -12
- package/docs/extension-api.md +42 -1
- package/docs/models.md +2 -0
- package/docs/observability.md +41 -5
- package/docs/tool-reference.md +38 -39
- package/docs/workflows.md +171 -3
- package/package.json +4 -2
- package/skills/pi-subagents/SKILL.md +6 -4
- package/skills/pi-subagents/references/constraints-and-recipes.md +12 -6
- package/skills/pi-subagents/references/execution-controls.md +29 -15
- package/skills/pi-subagents/references/management-authoring-rpc.md +3 -3
- package/skills/pi-subagents/references/prompting-and-roles.md +4 -2
- package/src/agents/agent-management.ts +124 -351
- package/src/agents/agents.ts +157 -31
- package/src/agents/skills.ts +1 -1
- package/src/api/external-job-provider.ts +185 -0
- package/src/api/preflight.ts +36 -8
- package/src/api/shared-types.ts +2 -0
- package/src/extension/config.ts +3 -3
- package/src/extension/doctor.ts +3 -6
- package/src/extension/fanout-child.ts +2 -2
- package/src/extension/index.ts +166 -88
- package/src/extension/public-execution.ts +31 -2
- package/src/extension/schemas.ts +12 -35
- package/src/extension/tool-description.ts +35 -24
- package/src/inspectors/herdr/actions.ts +2 -2
- package/src/inspectors/herdr/inspector-runner.ts +2 -1
- package/src/inspectors/herdr/project-panes.ts +2 -2
- package/src/intercom/native-supervisor-channel.ts +32 -10
- package/src/missions/lifecycle.ts +6 -1
- package/src/missions/store.ts +4 -9
- package/src/missions/workflow-state.ts +2 -2
- package/src/profiles/profiles.ts +3 -1
- package/src/runs/background/active-run-index.ts +31 -8
- package/src/runs/background/async-execution.ts +68 -40
- package/src/runs/background/async-job-tracker.ts +20 -4
- package/src/runs/background/async-resume.ts +47 -15
- package/src/runs/background/async-retention.ts +886 -0
- package/src/runs/background/async-status.ts +39 -53
- package/src/runs/background/chain-append.ts +3 -33
- package/src/runs/background/completion-dedupe.ts +5 -1
- package/src/runs/background/completion-replay.ts +22 -12
- package/src/runs/background/control-channel.ts +14 -68
- package/src/runs/background/fleet-view.ts +68 -19
- package/src/runs/background/index-segment.ts +59 -0
- package/src/runs/background/inspect-rpc.ts +443 -0
- package/src/runs/background/notify.ts +31 -5
- package/src/runs/background/result-files.ts +158 -90
- package/src/runs/background/result-watcher.ts +116 -20
- package/src/runs/background/resume-guidance.ts +8 -5
- package/src/runs/background/retained-children.ts +13 -3
- package/src/runs/background/run-id-query.ts +7 -0
- package/src/runs/background/run-id-resolver.ts +11 -9
- package/src/runs/background/run-status.ts +27 -18
- package/src/runs/background/scheduled-runs.ts +24 -4
- package/src/runs/background/stale-run-reconciler.ts +6 -3
- package/src/runs/background/steering.ts +11 -1
- package/src/runs/background/subagent-runner.ts +253 -140
- package/src/runs/background/subagent-wait.ts +8 -8
- package/src/runs/background/terminal-run-index.ts +129 -0
- package/src/runs/background/wait-completions.ts +21 -4
- package/src/runs/background/wait-subscriptions.ts +81 -2
- package/src/runs/foreground/async-steering-action.ts +21 -14
- package/src/runs/foreground/execution.ts +3 -1
- package/src/runs/foreground/subagent-executor.ts +666 -1579
- package/src/runs/foreground/workflow-detach-reconcile.ts +194 -0
- package/src/runs/foreground/workflow-foreground-steering.ts +6 -5
- package/src/runs/shared/acceptance.ts +4 -4
- package/src/runs/shared/chain-outputs.ts +1 -3
- package/src/runs/shared/external-job-bridge.ts +444 -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 +98 -38
- package/src/runs/shared/orca-progress-tabs.ts +84 -22
- package/src/runs/shared/parallel-handoff.ts +46 -4
- package/src/runs/shared/parallel-utils.ts +4 -15
- package/src/runs/shared/permissions.ts +5 -1
- package/src/runs/shared/pi-args.ts +8 -1
- package/src/runs/shared/session-lease.ts +0 -6
- package/src/runs/shared/subagent-control.ts +26 -4
- package/src/runs/shared/subagent-prompt-runtime.ts +12 -6
- package/src/runs/shared/workflow-graph.ts +1 -23
- package/src/runs/shared/worktree.ts +14 -2
- 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 +71 -26
- package/src/shared/utils.ts +8 -0
- package/src/shared/watch-strategy.ts +10 -0
- package/src/slash/slash-bridge.ts +2 -1
- package/src/slash/slash-commands.ts +29 -3
- package/src/tui/fleet.ts +63 -15
- package/src/watchdog/change-signature.ts +1 -1
- package/src/watchdog/lsp-diagnostics.ts +1 -0
- package/src/workflows/chat-progress.ts +2 -2
- package/src/workflows/scripted-workflow.ts +220 -67
- package/src/runs/foreground/chain-clarify.ts +0 -1354
- package/src/runs/foreground/chain-execution.ts +0 -1581
|
@@ -7,7 +7,9 @@ import type { Message } from "@earendil-works/pi-ai";
|
|
|
7
7
|
import { writeAtomicJson } from "../../shared/atomic-json.ts";
|
|
8
8
|
import { writeAsyncResultFile, writePendingAsyncResultFile } from "./result-files.ts";
|
|
9
9
|
import { createFileCoalescer } from "../../shared/file-coalescer.ts";
|
|
10
|
-
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";
|
|
11
13
|
import { createChildTranscriptWriter, type ChildTranscriptWriter } from "../../shared/child-transcript.ts";
|
|
12
14
|
import { closeSteerInbox, consumeInterruptRequest, consumeSteerRequests, deliverInterruptRequest, deliverStopRequest, deliverTimeoutRequest, enqueueStepSteer, steerAcksDir, steerCapabilityPath, stepSteerInboxDir, watchAsyncControlInbox, type SteerAck, type SteerCapability, type SteerRequest } from "./control-channel.ts";
|
|
13
15
|
import { appendJsonl as appendRawJsonl, formatOutputArtifactContent, getArtifactPaths, writeArtifact, writeMetadata } from "../../shared/artifacts.ts";
|
|
@@ -17,6 +19,8 @@ import {
|
|
|
17
19
|
type ActivityState,
|
|
18
20
|
type ArtifactConfig,
|
|
19
21
|
type ExternalCliRunnerStatus,
|
|
22
|
+
type ExternalJobRunnerStatus,
|
|
23
|
+
type ExternalJobStatus,
|
|
20
24
|
type ExternalProcessStatus,
|
|
21
25
|
type ArtifactPaths,
|
|
22
26
|
type AsyncParallelGroupStatus,
|
|
@@ -63,7 +67,6 @@ import {
|
|
|
63
67
|
import {
|
|
64
68
|
type RunnerSubagentStep as SubagentStep,
|
|
65
69
|
type RunnerStep,
|
|
66
|
-
isCheckpointRunnerStep,
|
|
67
70
|
isDynamicRunnerGroup,
|
|
68
71
|
isParallelGroup,
|
|
69
72
|
flattenSteps,
|
|
@@ -136,6 +139,7 @@ import { resolveWatchdogConfig } from "../../watchdog/settings.ts";
|
|
|
136
139
|
import { createBoundedByteTail, createBoundedLineReader, formatProtocolOutputLimit, MAX_CHILD_STDERR_BYTES, PI_AGGREGATE_EVENT_PROJECTOR, projectChildLifecycle, type ChildLifecycleAction, type ProtocolOutputLimit } from "../shared/child-protocol.ts";
|
|
137
140
|
import { acquireSessionLease, type SessionLeaseRequest } from "../shared/session-lease.ts";
|
|
138
141
|
import { buildExternalCliPrompt, runExternalCli } from "../shared/external-cli-runner.ts";
|
|
142
|
+
import { runExternalJob } from "../shared/external-job-runner.ts";
|
|
139
143
|
import { createOrcaProgressTab, type OrcaProgressTab } from "../shared/orca-progress-tabs.ts";
|
|
140
144
|
import { decodeSubagentCapabilityCeiling, SUBAGENT_CAPABILITY_CEILING_ENV, type ResolvedSubagentCapabilityCeiling } from "../shared/capability-ceiling.ts";
|
|
141
145
|
import {
|
|
@@ -165,6 +169,7 @@ interface SubagentRunConfig {
|
|
|
165
169
|
sessionDir?: string;
|
|
166
170
|
asyncDir: string;
|
|
167
171
|
sessionId?: string | null;
|
|
172
|
+
completionOwnerId?: string;
|
|
168
173
|
piPackageRoot?: string;
|
|
169
174
|
piArgv1?: string;
|
|
170
175
|
worktreeSetupHook?: string;
|
|
@@ -217,6 +222,7 @@ interface StepResult {
|
|
|
217
222
|
savedOutputPath?: string;
|
|
218
223
|
skipped?: boolean;
|
|
219
224
|
interrupted?: boolean;
|
|
225
|
+
detached?: boolean;
|
|
220
226
|
timedOut?: boolean;
|
|
221
227
|
stopped?: boolean;
|
|
222
228
|
processSignal?: string | null;
|
|
@@ -249,8 +255,9 @@ interface StepResult {
|
|
|
249
255
|
watchdog?: import("../../shared/types.ts").ChildWatchdogProgress;
|
|
250
256
|
writerProcesses?: PiWriterProcessInstanceExitV1[];
|
|
251
257
|
writerAttemptCount?: number;
|
|
252
|
-
runner?: ExternalCliRunnerStatus;
|
|
258
|
+
runner?: ExternalCliRunnerStatus | ExternalJobRunnerStatus;
|
|
253
259
|
externalProcess?: ExternalProcessStatus;
|
|
260
|
+
externalJob?: ExternalJobStatus;
|
|
254
261
|
}
|
|
255
262
|
|
|
256
263
|
function persistStepArtifacts(input: {
|
|
@@ -1057,7 +1064,7 @@ async function exportSessionHtml(sessionFile: string, outputDir: string, piPacka
|
|
|
1057
1064
|
|
|
1058
1065
|
function createShareLink(htmlPath: string): { shareUrl: string; gistUrl: string } | { error: string } {
|
|
1059
1066
|
try {
|
|
1060
|
-
const auth = spawnSync("gh", ["auth", "status"], { encoding: "utf-8" });
|
|
1067
|
+
const auth = spawnSync("gh", ["auth", "status"], { encoding: "utf-8", windowsHide: true });
|
|
1061
1068
|
if (auth.status !== 0) {
|
|
1062
1069
|
return { error: "GitHub CLI is not logged in. Run 'gh auth login' first." };
|
|
1063
1070
|
}
|
|
@@ -1066,7 +1073,7 @@ function createShareLink(htmlPath: string): { shareUrl: string; gistUrl: string
|
|
|
1066
1073
|
}
|
|
1067
1074
|
|
|
1068
1075
|
try {
|
|
1069
|
-
const result = spawnSync("gh", ["gist", "create", htmlPath], { encoding: "utf-8" });
|
|
1076
|
+
const result = spawnSync("gh", ["gist", "create", htmlPath], { encoding: "utf-8", windowsHide: true });
|
|
1070
1077
|
if (result.status !== 0) {
|
|
1071
1078
|
const err = (result.stderr || "").trim() || "Failed to create gist.";
|
|
1072
1079
|
return { error: err };
|
|
@@ -1109,6 +1116,7 @@ function writeRunLog(
|
|
|
1109
1116
|
shareUrl?: string;
|
|
1110
1117
|
shareError?: string;
|
|
1111
1118
|
},
|
|
1119
|
+
write: (filePath: string, content: string) => void = (filePath, content) => fs.writeFileSync(filePath, content, "utf-8"),
|
|
1112
1120
|
): void {
|
|
1113
1121
|
const lines: string[] = [];
|
|
1114
1122
|
lines.push(`# Subagent run ${input.id}`);
|
|
@@ -1138,7 +1146,7 @@ function writeRunLog(
|
|
|
1138
1146
|
}
|
|
1139
1147
|
lines.push(input.summary.trim() || "(no output)");
|
|
1140
1148
|
lines.push("");
|
|
1141
|
-
|
|
1149
|
+
write(logPath, lines.join("\n"));
|
|
1142
1150
|
}
|
|
1143
1151
|
|
|
1144
1152
|
/** Context for running a single step */
|
|
@@ -1183,6 +1191,7 @@ interface SingleStepContext {
|
|
|
1183
1191
|
onChildEvent?: (event: ChildEvent) => void;
|
|
1184
1192
|
onWriterProcess?: (writer: { state: "none" | "spawning" } | { state: "running"; pid: number }) => void;
|
|
1185
1193
|
onExternalProcess?: (process: ExternalProcessStatus) => void;
|
|
1194
|
+
onExternalJob?: (status: ExternalJobStatus) => void;
|
|
1186
1195
|
skipAcceptance?: () => boolean;
|
|
1187
1196
|
orcaProgressTab?: OrcaProgressTab;
|
|
1188
1197
|
}
|
|
@@ -1360,6 +1369,69 @@ async function runSingleStepInner(
|
|
|
1360
1369
|
});
|
|
1361
1370
|
}
|
|
1362
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
|
+
|
|
1363
1435
|
const candidates = step.modelCandidates && step.modelCandidates.length > 0
|
|
1364
1436
|
? step.modelCandidates
|
|
1365
1437
|
: step.model
|
|
@@ -1891,15 +1963,25 @@ type RunnerStatusStep = NonNullable<AsyncStatus["steps"]>[number] & {
|
|
|
1891
1963
|
description?: string;
|
|
1892
1964
|
};
|
|
1893
1965
|
|
|
1894
|
-
function externalRunnerStatus(runner: SubagentStep["runner"]): ExternalCliRunnerStatus | undefined {
|
|
1895
|
-
if (runner?.type
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
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;
|
|
1903
1985
|
}
|
|
1904
1986
|
|
|
1905
1987
|
function appendCapabilityCeilingAppliedEvent(eventsPath: string, runId: string, stepIndex: number, agent: string, result: StepResult): void {
|
|
@@ -1948,6 +2030,7 @@ function markParallelGroupSetupFailure(input: {
|
|
|
1948
2030
|
asyncDir: string;
|
|
1949
2031
|
runId: string;
|
|
1950
2032
|
stepIndex: number;
|
|
2033
|
+
writeStatus: (status: RunnerStatusPayload) => void;
|
|
1951
2034
|
}): void {
|
|
1952
2035
|
for (let taskIndex = 0; taskIndex < input.group.parallel.length; taskIndex++) {
|
|
1953
2036
|
const flatTaskIndex = input.groupStartFlatIndex + taskIndex;
|
|
@@ -1964,7 +2047,7 @@ function markParallelGroupSetupFailure(input: {
|
|
|
1964
2047
|
input.statusPayload.currentStep = input.groupStartFlatIndex;
|
|
1965
2048
|
input.statusPayload.lastUpdate = input.failedAt;
|
|
1966
2049
|
input.statusPayload.outputFile = path.join(input.asyncDir, `output-${input.groupStartFlatIndex}.log`);
|
|
1967
|
-
|
|
2050
|
+
input.writeStatus(input.statusPayload);
|
|
1968
2051
|
appendJsonl(input.eventsPath, JSON.stringify({
|
|
1969
2052
|
type: "subagent.parallel.completed",
|
|
1970
2053
|
ts: input.failedAt,
|
|
@@ -1984,6 +2067,7 @@ function markParallelGroupRunning(input: {
|
|
|
1984
2067
|
asyncDir: string;
|
|
1985
2068
|
runId: string;
|
|
1986
2069
|
stepIndex: number;
|
|
2070
|
+
writeStatus: (status: RunnerStatusPayload) => void;
|
|
1987
2071
|
}): void {
|
|
1988
2072
|
for (let taskIndex = 0; taskIndex < input.group.parallel.length; taskIndex++) {
|
|
1989
2073
|
const flatTaskIndex = input.groupStartFlatIndex + taskIndex;
|
|
@@ -2001,7 +2085,7 @@ function markParallelGroupRunning(input: {
|
|
|
2001
2085
|
input.statusPayload.lastActivityAt = input.groupStartTime;
|
|
2002
2086
|
input.statusPayload.lastUpdate = input.groupStartTime;
|
|
2003
2087
|
input.statusPayload.outputFile = path.join(input.asyncDir, `output-${input.groupStartFlatIndex}.log`);
|
|
2004
|
-
|
|
2088
|
+
input.writeStatus(input.statusPayload);
|
|
2005
2089
|
appendJsonl(input.eventsPath, JSON.stringify({
|
|
2006
2090
|
type: "subagent.parallel.started",
|
|
2007
2091
|
ts: input.groupStartTime,
|
|
@@ -2151,9 +2235,6 @@ async function runSubagent(
|
|
|
2151
2235
|
let stopped = false;
|
|
2152
2236
|
let turnBudgetExceeded = false;
|
|
2153
2237
|
let usageBudgetExceeded = false;
|
|
2154
|
-
let checkpointRejected = false;
|
|
2155
|
-
let pendingCheckpointDecision: "approved" | "rejected" | undefined;
|
|
2156
|
-
let wakeCheckpointDecision: (() => void) | undefined;
|
|
2157
2238
|
const timeoutMessage = config.timeoutMs !== undefined ? `Subagent timed out after ${config.timeoutMs}ms.` : undefined;
|
|
2158
2239
|
const stopMessage = "Subagent stopped by user.";
|
|
2159
2240
|
const timeoutAbortController = new AbortController();
|
|
@@ -2168,19 +2249,6 @@ async function runSubagent(
|
|
|
2168
2249
|
let flatStepCount = 0;
|
|
2169
2250
|
for (let stepIndex = 0; stepIndex < steps.length; stepIndex++) {
|
|
2170
2251
|
const step = steps[stepIndex]!;
|
|
2171
|
-
if (isCheckpointRunnerStep(step)) {
|
|
2172
|
-
initialStatusSteps.push(omitUndefinedProperties({
|
|
2173
|
-
agent: `checkpoint:${step.checkpoint}`,
|
|
2174
|
-
phase: step.phase,
|
|
2175
|
-
label: step.label ?? step.checkpoint,
|
|
2176
|
-
status: "pending",
|
|
2177
|
-
checkpoint: { name: step.checkpoint, ...(step.message ? { message: step.message } : {}), status: "pending", stepIndex },
|
|
2178
|
-
recentTools: [],
|
|
2179
|
-
recentOutput: [],
|
|
2180
|
-
}));
|
|
2181
|
-
flatStepCount++;
|
|
2182
|
-
continue;
|
|
2183
|
-
}
|
|
2184
2252
|
if (isParallelGroup(step)) {
|
|
2185
2253
|
parallelGroups.push({ start: flatStepCount, count: step.parallel.length, stepIndex });
|
|
2186
2254
|
for (const task of step.parallel) {
|
|
@@ -2272,8 +2340,9 @@ async function runSubagent(
|
|
|
2272
2340
|
lifecycleArtifactVersion: SUBAGENT_LIFECYCLE_ARTIFACT_VERSION,
|
|
2273
2341
|
runId: id,
|
|
2274
2342
|
...(config.sessionId ? { sessionId: config.sessionId } : {}),
|
|
2343
|
+
...(config.completionOwnerId ? { completionOwnerId: config.completionOwnerId } : {}),
|
|
2275
2344
|
mode: config.resultMode ?? (flatSteps.length > 1 ? "chain" : "single"),
|
|
2276
|
-
...(config.
|
|
2345
|
+
...(config.nestedSelf ? { isNested: true } : {}),
|
|
2277
2346
|
state: "running",
|
|
2278
2347
|
steering: createSteeringStatus(),
|
|
2279
2348
|
lastActivityAt: overallStartTime,
|
|
@@ -2303,9 +2372,37 @@ async function runSubagent(
|
|
|
2303
2372
|
outputFile: path.join(asyncDir, "output-0.log"),
|
|
2304
2373
|
});
|
|
2305
2374
|
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
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
|
+
|
|
2309
2406
|
let pendingParallelUsageCost: CostSummary = { inputTokens: 0, outputTokens: 0, costUsd: 0 };
|
|
2310
2407
|
const currentUsageTotals = (): CostSummary => {
|
|
2311
2408
|
const cost = results.reduce<CostSummary>((sum, result) => ({
|
|
@@ -2360,7 +2457,6 @@ async function runSubagent(
|
|
|
2360
2457
|
node.status = normalize(step.status);
|
|
2361
2458
|
setOptionalProperty(node, "error", step.error);
|
|
2362
2459
|
setOptionalProperty(node, "acceptanceStatus", step.acceptance?.status);
|
|
2363
|
-
setOptionalProperty(node, "checkpoint", step.checkpoint);
|
|
2364
2460
|
}
|
|
2365
2461
|
if (statusPayload.currentStep === node.flatIndex) graph.currentNodeId = node.id;
|
|
2366
2462
|
}
|
|
@@ -2379,17 +2475,15 @@ async function runSubagent(
|
|
|
2379
2475
|
statusPayload.workflowGraph = graph;
|
|
2380
2476
|
};
|
|
2381
2477
|
let finalResultCommitted = false;
|
|
2382
|
-
let lastIndexedActiveState = isActiveAsyncState(statusPayload.state);
|
|
2383
2478
|
const statusResultState = (): AsyncStatus["state"] | undefined => {
|
|
2384
2479
|
if (statusPayload.state === "running" || statusPayload.state === "queued") return undefined;
|
|
2385
|
-
if (statusPayload.state === "paused" && statusPayload.checkpoint?.status === "pending") return undefined;
|
|
2386
2480
|
return statusPayload.state;
|
|
2387
2481
|
};
|
|
2388
2482
|
const statusResultSummary = (state: AsyncStatus["state"]): string => {
|
|
2389
2483
|
if (statusPayload.error) return statusPayload.error;
|
|
2390
2484
|
if (state === "paused") return "Paused after interrupt. Waiting for explicit next action.";
|
|
2391
2485
|
if (state === "stopped") return stopMessage;
|
|
2392
|
-
if (state === "rejected") return
|
|
2486
|
+
if (state === "rejected") return "Subagent rejected.";
|
|
2393
2487
|
return state === "complete" ? "Subagent completed." : "Subagent failed.";
|
|
2394
2488
|
};
|
|
2395
2489
|
const statusResultSuccess = (state: AsyncStatus["state"], step: RunnerStatusStep): boolean | undefined => {
|
|
@@ -2404,7 +2498,7 @@ async function runSubagent(
|
|
|
2404
2498
|
if (!state || finalResultCommitted || !config.sessionId) return;
|
|
2405
2499
|
const now = statusPayload.endedAt ?? statusPayload.lastUpdate ?? Date.now();
|
|
2406
2500
|
const summary = statusResultSummary(state);
|
|
2407
|
-
|
|
2501
|
+
runPersistence.write(resultPath, omitUndefinedProperties({
|
|
2408
2502
|
lifecycleArtifactVersion: SUBAGENT_LIFECYCLE_ARTIFACT_VERSION,
|
|
2409
2503
|
id,
|
|
2410
2504
|
runId: id,
|
|
@@ -2415,7 +2509,6 @@ async function runSubagent(
|
|
|
2415
2509
|
summary,
|
|
2416
2510
|
error: state === "failed" || state === "stopped" || state === "rejected" ? summary : undefined,
|
|
2417
2511
|
stopped: state === "stopped" ? true : undefined,
|
|
2418
|
-
checkpoint: statusPayload.checkpoint,
|
|
2419
2512
|
results: statusPayload.steps.map((step) => omitUndefinedProperties({
|
|
2420
2513
|
agent: step.agent,
|
|
2421
2514
|
output: step.status === "complete" || step.status === "completed" ? "" : step.error ?? summary,
|
|
@@ -2432,18 +2525,14 @@ async function runSubagent(
|
|
|
2432
2525
|
asyncDir,
|
|
2433
2526
|
cwd,
|
|
2434
2527
|
sessionId: config.sessionId,
|
|
2528
|
+
completionOwnerId: config.completionOwnerId,
|
|
2435
2529
|
sessionFile: statusPayload.sessionFile ?? latestSessionFile,
|
|
2436
|
-
}));
|
|
2530
|
+
}), (filePath, payload) => writePendingAsyncResultFile(filePath, payload as Record<string, unknown>));
|
|
2437
2531
|
};
|
|
2438
2532
|
const writeStatusPayloadNow = (): void => {
|
|
2439
2533
|
refreshWorkflowGraph();
|
|
2440
2534
|
writeRecoverableStatusResult();
|
|
2441
|
-
|
|
2442
|
-
const activeState = isActiveAsyncState(statusPayload.state);
|
|
2443
|
-
if (activeState !== lastIndexedActiveState) {
|
|
2444
|
-
updateActiveRunIndex(asyncDir, statusPayload.state, statusPayload.toolCallId);
|
|
2445
|
-
}
|
|
2446
|
-
lastIndexedActiveState = activeState;
|
|
2535
|
+
runPersistence.write(statusPath, statusPayload);
|
|
2447
2536
|
emitNestedSelfEvent(statusPayload.state === "running" || statusPayload.state === "queued" ? "subagent.nested.updated" : "subagent.nested.completed");
|
|
2448
2537
|
};
|
|
2449
2538
|
const statusWriteCoalescer = createFileCoalescer(writeStatusPayloadNow, 100);
|
|
@@ -2459,6 +2548,11 @@ async function runSubagent(
|
|
|
2459
2548
|
statusPayload.lastUpdate = Date.now();
|
|
2460
2549
|
writeStatusPayload();
|
|
2461
2550
|
};
|
|
2551
|
+
const updateExternalJob = (index: number, externalJob: ExternalJobStatus): void => {
|
|
2552
|
+
requiredStatusStep(statusPayload, index).externalJob = externalJob;
|
|
2553
|
+
statusPayload.lastUpdate = Date.now();
|
|
2554
|
+
writeStatusPayload();
|
|
2555
|
+
};
|
|
2462
2556
|
const registerStepInterrupt = (flatIndex: number, interrupt: (() => void) | undefined): void => {
|
|
2463
2557
|
if (!interrupt) {
|
|
2464
2558
|
activeChildInterrupts.delete(flatIndex);
|
|
@@ -2754,8 +2848,16 @@ async function runSubagent(
|
|
|
2754
2848
|
.sort((left, right) => left.startedAt - right.startedAt)[0];
|
|
2755
2849
|
const supervisorAttentionSteps = new Map<number, ActivityState | undefined>();
|
|
2756
2850
|
const mutatingFailureWindowMs = 5 * 60_000;
|
|
2757
|
-
const appendControlEvent = (
|
|
2851
|
+
const appendControlEvent = (rawEvent: ReturnType<typeof buildControlEvent>) => {
|
|
2758
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
|
+
};
|
|
2759
2861
|
const childIntercomTarget = config.childIntercomTargets?.[event.index ?? statusPayload.currentStep];
|
|
2760
2862
|
const channels = event.type === "active_long_running"
|
|
2761
2863
|
? controlConfig.notifyChannels.filter((channel) => channel !== "intercom")
|
|
@@ -3378,10 +3480,6 @@ async function runSubagent(
|
|
|
3378
3480
|
onInterrupt: interruptRunner,
|
|
3379
3481
|
onTimeout: timeoutRunner,
|
|
3380
3482
|
onStop: stopRunner,
|
|
3381
|
-
onCheckpointDecision: (decision) => {
|
|
3382
|
-
pendingCheckpointDecision = decision;
|
|
3383
|
-
wakeCheckpointDecision?.();
|
|
3384
|
-
},
|
|
3385
3483
|
onSteer: (request) => {
|
|
3386
3484
|
const targetStep = request.targetIndex !== undefined ? statusPayload.steps[request.targetIndex] : undefined;
|
|
3387
3485
|
if (targetStep?.status === "pending") {
|
|
@@ -3432,20 +3530,6 @@ async function runSubagent(
|
|
|
3432
3530
|
|
|
3433
3531
|
let flatIndex = 0;
|
|
3434
3532
|
let stepCursor = 0;
|
|
3435
|
-
const waitForCheckpointDecision = async (): Promise<"approved" | "rejected" | undefined> => {
|
|
3436
|
-
while (!pendingCheckpointDecision && !interrupted && !timedOut && !stopped) {
|
|
3437
|
-
await new Promise<void>((resolve) => {
|
|
3438
|
-
wakeCheckpointDecision = resolve;
|
|
3439
|
-
const timer = setTimeout(resolve, POLL_INTERVAL_MS);
|
|
3440
|
-
timer.unref?.();
|
|
3441
|
-
});
|
|
3442
|
-
wakeCheckpointDecision = undefined;
|
|
3443
|
-
}
|
|
3444
|
-
const decision = pendingCheckpointDecision;
|
|
3445
|
-
pendingCheckpointDecision = undefined;
|
|
3446
|
-
return decision;
|
|
3447
|
-
};
|
|
3448
|
-
|
|
3449
3533
|
while (true) {
|
|
3450
3534
|
if (interrupted || timedOut || stopped || turnBudgetExceeded) break;
|
|
3451
3535
|
consumePendingAppendRequests();
|
|
@@ -3463,64 +3547,6 @@ async function runSubagent(
|
|
|
3463
3547
|
const stepIndex = stepCursor++;
|
|
3464
3548
|
const step = steps[stepIndex]!;
|
|
3465
3549
|
|
|
3466
|
-
if (isCheckpointRunnerStep(step)) {
|
|
3467
|
-
const now = Date.now();
|
|
3468
|
-
const statusStep = statusPayload.steps[flatIndex];
|
|
3469
|
-
const checkpoint = { name: step.checkpoint, ...(step.message ? { message: step.message } : {}), status: "pending" as const, stepIndex };
|
|
3470
|
-
statusPayload.state = "paused";
|
|
3471
|
-
statusPayload.currentStep = flatIndex;
|
|
3472
|
-
statusPayload.checkpoint = checkpoint;
|
|
3473
|
-
delete statusPayload.activityState;
|
|
3474
|
-
statusPayload.lastUpdate = now;
|
|
3475
|
-
if (statusStep) {
|
|
3476
|
-
statusStep.status = "paused";
|
|
3477
|
-
statusStep.startedAt = now;
|
|
3478
|
-
statusStep.checkpoint = checkpoint;
|
|
3479
|
-
}
|
|
3480
|
-
writeStatusPayload();
|
|
3481
|
-
appendJsonl(eventsPath, JSON.stringify({ type: "subagent.checkpoint.paused", ts: now, runId: id, stepIndex, checkpoint }));
|
|
3482
|
-
const decision = await waitForCheckpointDecision();
|
|
3483
|
-
if (decision === "approved") {
|
|
3484
|
-
const approvedAt = Date.now();
|
|
3485
|
-
const approved = { ...checkpoint, status: "approved" as const, approvedAt };
|
|
3486
|
-
statusPayload.state = "running";
|
|
3487
|
-
statusPayload.checkpoint = approved;
|
|
3488
|
-
if (statusStep) {
|
|
3489
|
-
statusStep.status = "complete";
|
|
3490
|
-
statusStep.endedAt = approvedAt;
|
|
3491
|
-
statusStep.durationMs = approvedAt - now;
|
|
3492
|
-
statusStep.exitCode = 0;
|
|
3493
|
-
statusStep.checkpoint = approved;
|
|
3494
|
-
}
|
|
3495
|
-
statusPayload.lastUpdate = approvedAt;
|
|
3496
|
-
writeStatusPayload();
|
|
3497
|
-
appendJsonl(eventsPath, JSON.stringify({ type: "subagent.checkpoint.approved", ts: approvedAt, runId: id, stepIndex, checkpoint: approved }));
|
|
3498
|
-
flatIndex++;
|
|
3499
|
-
continue;
|
|
3500
|
-
}
|
|
3501
|
-
if (decision === "rejected") {
|
|
3502
|
-
const rejectedAt = Date.now();
|
|
3503
|
-
const rejected = { ...checkpoint, status: "rejected" as const, rejectedAt };
|
|
3504
|
-
checkpointRejected = true;
|
|
3505
|
-
statusPayload.state = "rejected";
|
|
3506
|
-
statusPayload.error = `Checkpoint '${step.checkpoint}' rejected.`;
|
|
3507
|
-
statusPayload.checkpoint = rejected;
|
|
3508
|
-
if (statusStep) {
|
|
3509
|
-
statusStep.status = "rejected";
|
|
3510
|
-
statusStep.error = statusPayload.error;
|
|
3511
|
-
statusStep.endedAt = rejectedAt;
|
|
3512
|
-
statusStep.durationMs = rejectedAt - now;
|
|
3513
|
-
statusStep.exitCode = 1;
|
|
3514
|
-
statusStep.checkpoint = rejected;
|
|
3515
|
-
}
|
|
3516
|
-
statusPayload.lastUpdate = rejectedAt;
|
|
3517
|
-
writeStatusPayload();
|
|
3518
|
-
appendJsonl(eventsPath, JSON.stringify({ type: "subagent.checkpoint.rejected", ts: rejectedAt, runId: id, stepIndex, checkpoint: rejected }));
|
|
3519
|
-
break;
|
|
3520
|
-
}
|
|
3521
|
-
break;
|
|
3522
|
-
}
|
|
3523
|
-
|
|
3524
3550
|
if (isDynamicRunnerGroup(step)) {
|
|
3525
3551
|
const groupStartFlatIndex = flatIndex;
|
|
3526
3552
|
let materialized: ReturnType<typeof materializeDynamicParallelStep>;
|
|
@@ -3814,6 +3840,7 @@ async function runSubagent(
|
|
|
3814
3840
|
onChildEvent: (event) => updateStepFromChildEvent(fi, event),
|
|
3815
3841
|
onWriterProcess,
|
|
3816
3842
|
onExternalProcess: (process) => updateExternalProcess(fi, process),
|
|
3843
|
+
onExternalJob: (externalJob) => updateExternalJob(fi, externalJob),
|
|
3817
3844
|
skipAcceptance: () => timedOut || stopped,
|
|
3818
3845
|
}), config.deadlineAt);
|
|
3819
3846
|
const taskEndTime = Date.now();
|
|
@@ -4039,6 +4066,7 @@ async function runSubagent(
|
|
|
4039
4066
|
asyncDir,
|
|
4040
4067
|
runId: id,
|
|
4041
4068
|
stepIndex,
|
|
4069
|
+
writeStatus: () => writeStatusPayload(),
|
|
4042
4070
|
});
|
|
4043
4071
|
flatIndex += group.parallel.length;
|
|
4044
4072
|
break;
|
|
@@ -4066,6 +4094,7 @@ async function runSubagent(
|
|
|
4066
4094
|
asyncDir,
|
|
4067
4095
|
runId: id,
|
|
4068
4096
|
stepIndex,
|
|
4097
|
+
writeStatus: () => writeStatusPayload(),
|
|
4069
4098
|
});
|
|
4070
4099
|
flatIndex += group.parallel.length;
|
|
4071
4100
|
break;
|
|
@@ -4097,6 +4126,7 @@ async function runSubagent(
|
|
|
4097
4126
|
asyncDir,
|
|
4098
4127
|
runId: id,
|
|
4099
4128
|
stepIndex,
|
|
4129
|
+
writeStatus: () => writeStatusPayload(),
|
|
4100
4130
|
});
|
|
4101
4131
|
const parallelResults = await mapConcurrent(
|
|
4102
4132
|
group.parallel,
|
|
@@ -4198,6 +4228,7 @@ async function runSubagent(
|
|
|
4198
4228
|
onChildEvent: (event) => updateStepFromChildEvent(fi, event),
|
|
4199
4229
|
onWriterProcess,
|
|
4200
4230
|
onExternalProcess: (process) => updateExternalProcess(fi, process),
|
|
4231
|
+
onExternalJob: (externalJob) => updateExternalJob(fi, externalJob),
|
|
4201
4232
|
skipAcceptance: () => timedOut || stopped,
|
|
4202
4233
|
}), config.deadlineAt);
|
|
4203
4234
|
if (task.sessionFile) {
|
|
@@ -4435,6 +4466,32 @@ async function runSubagent(
|
|
|
4435
4466
|
}
|
|
4436
4467
|
} else {
|
|
4437
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;
|
|
4438
4495
|
const stepStartTime = Date.now();
|
|
4439
4496
|
statusPayload.currentStep = flatIndex;
|
|
4440
4497
|
requiredStatusStep(statusPayload, flatIndex).status = "running";
|
|
@@ -4458,8 +4515,11 @@ async function runSubagent(
|
|
|
4458
4515
|
}));
|
|
4459
4516
|
|
|
4460
4517
|
flushPendingStepSteers(flatIndex);
|
|
4461
|
-
const
|
|
4462
|
-
|
|
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,
|
|
4463
4523
|
outputs: statusPayload.mode === "single" ? undefined : outputs,
|
|
4464
4524
|
sessionDir: config.sessionDir,
|
|
4465
4525
|
artifactsDir, artifactConfig, id,
|
|
@@ -4489,8 +4549,13 @@ async function runSubagent(
|
|
|
4489
4549
|
onChildEvent: (event) => updateStepFromChildEvent(flatIndex, event),
|
|
4490
4550
|
onWriterProcess,
|
|
4491
4551
|
onExternalProcess: (process) => updateExternalProcess(flatIndex, process),
|
|
4552
|
+
onExternalJob: (externalJob) => updateExternalJob(flatIndex, externalJob),
|
|
4492
4553
|
skipAcceptance: () => timedOut || stopped,
|
|
4493
|
-
|
|
4554
|
+
}), config.deadlineAt);
|
|
4555
|
+
} catch (error) {
|
|
4556
|
+
if (singleWorktreeSetup) cleanupWorktrees(singleWorktreeSetup);
|
|
4557
|
+
throw error;
|
|
4558
|
+
}
|
|
4494
4559
|
if (seqStep.sessionFile) {
|
|
4495
4560
|
latestSessionFile = seqStep.sessionFile;
|
|
4496
4561
|
}
|
|
@@ -4539,6 +4604,7 @@ async function runSubagent(
|
|
|
4539
4604
|
toolBudgetBlocked: singleResult.toolBudgetBlocked,
|
|
4540
4605
|
runner: singleResult.runner,
|
|
4541
4606
|
externalProcess: singleResult.externalProcess,
|
|
4607
|
+
externalJob: singleResult.externalJob,
|
|
4542
4608
|
}));
|
|
4543
4609
|
if (seqStep.outputName) {
|
|
4544
4610
|
outputs[seqStep.outputName] = outputEntryFromAsyncResult({
|
|
@@ -4629,6 +4695,48 @@ async function runSubagent(
|
|
|
4629
4695
|
durationMs: stepEndTime - stepStartTime,
|
|
4630
4696
|
tokens: stepTokens,
|
|
4631
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
|
+
|
|
4632
4740
|
if (singleResult.completionGuardTriggered) {
|
|
4633
4741
|
const event = buildControlEvent(omitUndefinedProperties({
|
|
4634
4742
|
from: requiredStatusStep(statusPayload, flatIndex).activityState,
|
|
@@ -4730,8 +4838,8 @@ async function runSubagent(
|
|
|
4730
4838
|
stopped: result.stopped,
|
|
4731
4839
|
turnBudgetExceeded: result.turnBudgetExceeded,
|
|
4732
4840
|
})));
|
|
4733
|
-
statusPayload.state =
|
|
4734
|
-
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));
|
|
4735
4843
|
disposeControlInbox();
|
|
4736
4844
|
for (const request of consumeSteerRequests(asyncDir)) deliverSteerRequest(request);
|
|
4737
4845
|
const effectiveSessionFile = sessionFile ?? latestSessionFile;
|
|
@@ -4782,15 +4890,16 @@ async function runSubagent(
|
|
|
4782
4890
|
statusPayload.error = `Step failed: ${failedStep.agent}`;
|
|
4783
4891
|
}
|
|
4784
4892
|
}
|
|
4893
|
+
writeStatusPayload();
|
|
4785
4894
|
try {
|
|
4786
|
-
|
|
4895
|
+
runPersistence.write(resultPath, {
|
|
4787
4896
|
lifecycleArtifactVersion: SUBAGENT_LIFECYCLE_ARTIFACT_VERSION,
|
|
4788
4897
|
id,
|
|
4789
4898
|
agent: agentName,
|
|
4790
4899
|
mode: resultMode,
|
|
4791
|
-
success: !
|
|
4792
|
-
state:
|
|
4793
|
-
summary:
|
|
4900
|
+
success: !stopped && !timedOut && !turnBudgetExceeded && !usageBudgetExceeded && !interrupted && !signalTerminated && results.every((r) => r.success),
|
|
4901
|
+
state: stopped || signalTerminated ? "stopped" : timedOut || turnBudgetExceeded || usageBudgetExceeded ? "failed" : interrupted ? "paused" : results.every((r) => r.success) ? "complete" : "failed",
|
|
4902
|
+
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,
|
|
4794
4903
|
...(config.timeoutMs !== undefined ? { timeoutMs: config.timeoutMs } : {}),
|
|
4795
4904
|
...(config.deadlineAt !== undefined ? { deadlineAt: config.deadlineAt } : {}),
|
|
4796
4905
|
...(statusPayload.turnBudget ? { turnBudget: statusPayload.turnBudget } : {}),
|
|
@@ -4799,7 +4908,6 @@ async function runSubagent(
|
|
|
4799
4908
|
...(statusPayload.toolBudget ? { toolBudget: statusPayload.toolBudget } : {}),
|
|
4800
4909
|
...(statusPayload.toolBudgetBlocked ? { toolBudgetBlocked: true } : {}),
|
|
4801
4910
|
...(statusPayload.usageBudget ? { usageBudget: statusPayload.usageBudget } : {}),
|
|
4802
|
-
...(statusPayload.checkpoint ? { checkpoint: statusPayload.checkpoint } : {}),
|
|
4803
4911
|
...(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." } : {}),
|
|
4804
4912
|
results: results.map((r) => omitUndefinedProperties({
|
|
4805
4913
|
agent: r.agent,
|
|
@@ -4837,6 +4945,7 @@ async function runSubagent(
|
|
|
4837
4945
|
runtimeAcknowledgedExtensions: r.runtimeAcknowledgedExtensions,
|
|
4838
4946
|
runner: r.runner,
|
|
4839
4947
|
externalProcess: r.externalProcess,
|
|
4948
|
+
externalJob: r.externalJob,
|
|
4840
4949
|
execution: r.execution,
|
|
4841
4950
|
review: r.review,
|
|
4842
4951
|
effects: r.effects,
|
|
@@ -4855,13 +4964,12 @@ async function runSubagent(
|
|
|
4855
4964
|
capabilityAudit: statusPayload.capabilityAudit,
|
|
4856
4965
|
...(config.parentWorkflowRunId ? { parentWorkflowRunId: config.parentWorkflowRunId } : {}),
|
|
4857
4966
|
...(config.workflowKey ? { workflowKey: config.workflowKey } : {}),
|
|
4858
|
-
exitCode:
|
|
4967
|
+
exitCode: stopped || timedOut || turnBudgetExceeded || usageBudgetExceeded ? 1 : interrupted || results.every((r) => r.success) ? 0 : 1,
|
|
4859
4968
|
timestamp: runEndedAt,
|
|
4860
4969
|
durationMs: runEndedAt - overallStartTime,
|
|
4861
4970
|
totalTokens: statusPayload.totalTokens,
|
|
4862
4971
|
totalCost: finalTotalCost,
|
|
4863
4972
|
usageBudget: statusPayload.usageBudget,
|
|
4864
|
-
checkpoint: statusPayload.checkpoint,
|
|
4865
4973
|
truncated,
|
|
4866
4974
|
artifactsDir,
|
|
4867
4975
|
cwd,
|
|
@@ -4870,6 +4978,7 @@ async function runSubagent(
|
|
|
4870
4978
|
launchResolvedExtensions: config.launchResolvedExtensions,
|
|
4871
4979
|
runtimeAcknowledgedExtensions: singleRuntimeAcknowledgedExtensions,
|
|
4872
4980
|
sessionId: config.sessionId,
|
|
4981
|
+
completionOwnerId: config.completionOwnerId,
|
|
4873
4982
|
sessionFile: effectiveSessionFile,
|
|
4874
4983
|
intercomTarget: config.controlIntercomTarget,
|
|
4875
4984
|
shareUrl,
|
|
@@ -4877,7 +4986,7 @@ async function runSubagent(
|
|
|
4877
4986
|
shareError,
|
|
4878
4987
|
...(taskIndex !== undefined && { taskIndex }),
|
|
4879
4988
|
...(totalTasks !== undefined && { totalTasks }),
|
|
4880
|
-
});
|
|
4989
|
+
}, (filePath, payload) => { writeAsyncResultFile(filePath, payload as Record<string, unknown>); });
|
|
4881
4990
|
finalResultCommitted = true;
|
|
4882
4991
|
} catch (err) {
|
|
4883
4992
|
const message = `Failed to write result file ${resultPath}: ${err instanceof Error ? err.message : String(err)}`;
|
|
@@ -4918,7 +5027,11 @@ async function runSubagent(
|
|
|
4918
5027
|
sessionFile: effectiveSessionFile,
|
|
4919
5028
|
shareUrl,
|
|
4920
5029
|
shareError,
|
|
5030
|
+
}), (filePath, content) => runPersistence.write(filePath, { content }, (_path, payload) => {
|
|
5031
|
+
fs.writeFileSync(_path, (payload as { content: string }).content, "utf-8");
|
|
4921
5032
|
}));
|
|
5033
|
+
if (runPersistence.pendingCount() === 0) runPersistence.dispose();
|
|
5034
|
+
if (indexPersistence.pendingCount() === 0) indexPersistence.dispose();
|
|
4922
5035
|
if (config.runnerProcessInstanceId) {
|
|
4923
5036
|
const writers: Record<string, PiWriterProcessInstanceExitV1[]> = {};
|
|
4924
5037
|
const expectedWriters: Record<string, number> = {};
|