pi-subagents 0.47.1 → 0.48.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 +23 -0
- package/README.md +2 -0
- package/docs/configuration.md +46 -2
- package/docs/observability.md +1 -1
- package/docs/tool-reference.md +1 -1
- package/package.json +1 -1
- package/src/agents/agents.ts +9 -3
- package/src/extension/config.ts +6 -0
- package/src/extension/doctor.ts +40 -0
- package/src/extension/index.ts +21 -0
- package/src/extension/public-execution.ts +5 -0
- package/src/extension/rpc.ts +3 -9
- package/src/extension/schemas.ts +2 -2
- package/src/intercom/intercom-bridge.ts +4 -1
- package/src/missions/lifecycle.ts +4 -7
- package/src/missions/store.ts +4 -4
- package/src/missions/workflow-state.ts +6 -2
- package/src/runs/background/active-async-capacity.ts +374 -0
- package/src/runs/background/active-run-index.ts +9 -5
- package/src/runs/background/async-execution.ts +117 -29
- package/src/runs/background/async-resume.ts +7 -1
- package/src/runs/background/async-status.ts +11 -5
- package/src/runs/background/chain-append.ts +33 -15
- package/src/runs/background/owned-process-tree.ts +104 -0
- package/src/runs/background/process-terminal.ts +17 -3
- package/src/runs/background/run-status.ts +5 -1
- package/src/runs/background/stale-run-reconciler.ts +3 -3
- package/src/runs/background/subagent-runner.ts +56 -31
- package/src/runs/foreground/chain-execution.ts +37 -2
- package/src/runs/foreground/execution.ts +90 -19
- package/src/runs/foreground/foreground-control.ts +12 -0
- package/src/runs/foreground/prompt-audit.ts +171 -0
- package/src/runs/foreground/subagent-executor.ts +614 -187
- package/src/runs/shared/acceptance.ts +13 -4
- package/src/runs/shared/llm-intent-arbiter.ts +286 -0
- package/src/runs/shared/parallel-utils.ts +2 -0
- package/src/runs/shared/pi-args.ts +44 -1
- package/src/runs/shared/run-fanout-budget.ts +280 -0
- package/src/runs/shared/single-output.ts +4 -2
- package/src/runs/shared/task-intent.ts +19 -3
- package/src/runs/shared/worktree.ts +17 -5
- package/src/shared/types.ts +92 -0
- package/src/shared/utils.ts +3 -1
- package/src/tui/fleet-status.ts +7 -5
- package/src/tui/fleet.ts +225 -12
- package/src/workflows/scripted-workflow.ts +22 -5
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
2
|
+
import type { ProcessTreeTerminalV1 } from "../../shared/types.ts";
|
|
3
|
+
|
|
4
|
+
const DEFAULT_TERM_GRACE_MS = 3000;
|
|
5
|
+
const DEFAULT_KILL_VERIFY_MS = 1000;
|
|
6
|
+
const VERIFY_INTERVAL_MS = 25;
|
|
7
|
+
|
|
8
|
+
type SignalResult = "sent" | "absent" | { diagnostic: string };
|
|
9
|
+
|
|
10
|
+
function diagnostic(error: unknown): string {
|
|
11
|
+
return error instanceof Error ? error.message : String(error);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function signalProcess(id: number, signal: NodeJS.Signals): SignalResult {
|
|
15
|
+
try {
|
|
16
|
+
process.kill(id, signal);
|
|
17
|
+
return "sent";
|
|
18
|
+
} catch (error) {
|
|
19
|
+
if ((error as NodeJS.ErrnoException).code === "ESRCH") return "absent";
|
|
20
|
+
return { diagnostic: diagnostic(error) };
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function activeProcessGroupMembers(processGroupId: number): number[] | { diagnostic: string } {
|
|
25
|
+
const result = spawnSync("ps", ["-axo", "pid=,pgid=,stat="], { encoding: "utf-8" });
|
|
26
|
+
if (result.error || result.status !== 0) {
|
|
27
|
+
return { diagnostic: result.error ? diagnostic(result.error) : (result.stderr.trim() || `ps exited with ${result.status}`) };
|
|
28
|
+
}
|
|
29
|
+
const members: number[] = [];
|
|
30
|
+
for (const line of result.stdout.split("\n")) {
|
|
31
|
+
const match = /^\s*(\d+)\s+(\d+)\s+(\S+)/.exec(line);
|
|
32
|
+
if (!match || Number(match[2]) !== processGroupId || match[3]!.startsWith("Z")) continue;
|
|
33
|
+
members.push(Number(match[1]));
|
|
34
|
+
}
|
|
35
|
+
return members;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async function waitUntilGroupTerminal(
|
|
39
|
+
processGroupId: number,
|
|
40
|
+
timeoutMs: number,
|
|
41
|
+
): Promise<false | { state: "enumeration-failed" | "still-active"; diagnostic: string }> {
|
|
42
|
+
const deadline = Date.now() + timeoutMs;
|
|
43
|
+
while (true) {
|
|
44
|
+
const members = activeProcessGroupMembers(processGroupId);
|
|
45
|
+
if (Array.isArray(members) && members.length === 0) return false;
|
|
46
|
+
const remaining = deadline - Date.now();
|
|
47
|
+
if (remaining <= 0) {
|
|
48
|
+
if (!Array.isArray(members)) return { state: "enumeration-failed", diagnostic: members.diagnostic };
|
|
49
|
+
return { state: "still-active", diagnostic: `Process group ${processGroupId} still has active members: ${members.join(", ")}.` };
|
|
50
|
+
}
|
|
51
|
+
await new Promise<void>((resolve) => setTimeout(resolve, Math.min(VERIFY_INTERVAL_MS, remaining)));
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function observed(processGroupId: number): ProcessTreeTerminalV1 {
|
|
56
|
+
return { state: "observed", mechanism: "posix-process-group", processGroupId, verifiedAt: Date.now() };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** Owns one writer process group and arbitrates its cleanup exactly once. */
|
|
60
|
+
export interface OwnedProcessTreeController {
|
|
61
|
+
terminate(): Promise<ProcessTreeTerminalV1>;
|
|
62
|
+
finishAfterWriterClose(): Promise<ProcessTreeTerminalV1>;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function createOwnedProcessTreeController(
|
|
66
|
+
pid: number,
|
|
67
|
+
options: { termGraceMs?: number; killVerifyMs?: number } = {},
|
|
68
|
+
): OwnedProcessTreeController {
|
|
69
|
+
let termination: Promise<ProcessTreeTerminalV1> | undefined;
|
|
70
|
+
const posixGroupOwned = process.platform !== "win32";
|
|
71
|
+
const target = posixGroupOwned ? -pid : pid;
|
|
72
|
+
|
|
73
|
+
const terminate = (): Promise<ProcessTreeTerminalV1> => {
|
|
74
|
+
if (termination) return termination;
|
|
75
|
+
termination = (async () => {
|
|
76
|
+
if (!posixGroupOwned) {
|
|
77
|
+
signalProcess(target, "SIGTERM");
|
|
78
|
+
return { state: "unknown", reason: "unsupported-platform" };
|
|
79
|
+
}
|
|
80
|
+
const term = signalProcess(target, "SIGTERM");
|
|
81
|
+
if (term !== "sent" && term !== "absent") {
|
|
82
|
+
return { state: "unknown", reason: "signal-failed", diagnostic: term.diagnostic };
|
|
83
|
+
}
|
|
84
|
+
const termExit = await waitUntilGroupTerminal(pid, options.termGraceMs ?? DEFAULT_TERM_GRACE_MS);
|
|
85
|
+
if (termExit === false) return observed(pid);
|
|
86
|
+
|
|
87
|
+
const kill = signalProcess(target, "SIGKILL");
|
|
88
|
+
if (kill !== "sent" && kill !== "absent") {
|
|
89
|
+
const members = activeProcessGroupMembers(pid);
|
|
90
|
+
if (!Array.isArray(members) || members.length > 0) {
|
|
91
|
+
return { state: "unknown", reason: "signal-failed", diagnostic: kill.diagnostic };
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
const killExit = await waitUntilGroupTerminal(pid, options.killVerifyMs ?? DEFAULT_KILL_VERIFY_MS);
|
|
95
|
+
if (killExit !== false) {
|
|
96
|
+
return { state: "unknown", reason: "verification-failed", diagnostic: killExit.diagnostic };
|
|
97
|
+
}
|
|
98
|
+
return observed(pid);
|
|
99
|
+
})();
|
|
100
|
+
return termination;
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
return { terminate, finishAfterWriterClose: terminate };
|
|
104
|
+
}
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
type ProcessTerminalV1,
|
|
11
11
|
} from "../../shared/types.ts";
|
|
12
12
|
import { canonicalSessionId, inspectSessionLease } from "../shared/session-lease.ts";
|
|
13
|
+
import { releaseActiveRunIndex } from "./active-run-index.ts";
|
|
13
14
|
|
|
14
15
|
export interface ProcessTerminalCandidate {
|
|
15
16
|
version: 1;
|
|
@@ -40,9 +41,19 @@ function validProcessInstance(value: unknown, kind?: "runner" | "pi-writer"): va
|
|
|
40
41
|
if (typeof value.closeObservedAt !== "number" || !Number.isFinite(value.closeObservedAt)) return false;
|
|
41
42
|
if (typeof value.exitCode !== "number" && value.exitCode !== null) return false;
|
|
42
43
|
if (typeof value.signal !== "string" && value.signal !== null) return false;
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
44
|
+
if (value.kind === "runner") return value.attempt === undefined;
|
|
45
|
+
if (typeof value.attempt !== "number" || !Number.isInteger(value.attempt) || value.attempt < 0 || !isRecord(value.processTree)) return false;
|
|
46
|
+
if (value.processTree.state === "observed") {
|
|
47
|
+
return value.processTree.mechanism === "posix-process-group"
|
|
48
|
+
&& typeof value.processTree.processGroupId === "number"
|
|
49
|
+
&& Number.isInteger(value.processTree.processGroupId)
|
|
50
|
+
&& value.processTree.processGroupId > 0
|
|
51
|
+
&& typeof value.processTree.verifiedAt === "number"
|
|
52
|
+
&& Number.isFinite(value.processTree.verifiedAt);
|
|
53
|
+
}
|
|
54
|
+
return value.processTree.state === "unknown"
|
|
55
|
+
&& ["unsupported-platform", "signal-failed", "verification-failed"].includes(String(value.processTree.reason))
|
|
56
|
+
&& (value.processTree.diagnostic === undefined || typeof value.processTree.diagnostic === "string");
|
|
46
57
|
}
|
|
47
58
|
|
|
48
59
|
function validInstance(value: unknown): value is ProcessInstanceExitV1 {
|
|
@@ -249,6 +260,8 @@ export function finalizeProcessTerminal(
|
|
|
249
260
|
proof = unknownProof(runId, runnerClose.processInstanceId, "canonical-session-release-unverified");
|
|
250
261
|
} else if (inconsistentWriters || (allWriters.length === 0 && expectedEntries.length === 0)) {
|
|
251
262
|
proof = unknownProof(runId, runnerClose.processInstanceId, "writer-close-unverified");
|
|
263
|
+
} else if (allWriters.some((writer) => writer.kind === "pi-writer" && writer.processTree.state !== "observed")) {
|
|
264
|
+
proof = unknownProof(runId, runnerClose.processInstanceId, "process-tree-unverified");
|
|
252
265
|
} else {
|
|
253
266
|
const runner: ProcessInstanceExitV1 = { kind: "runner", ...runnerClose };
|
|
254
267
|
const canonicalSession = session && sessionProjection(candidate, session);
|
|
@@ -271,6 +284,7 @@ export function finalizeProcessTerminal(
|
|
|
271
284
|
try {
|
|
272
285
|
writeAtomicJson(processTerminalPath(asyncDir), proof);
|
|
273
286
|
durable = true;
|
|
287
|
+
if (proof.state === "observed") releaseActiveRunIndex(asyncDir);
|
|
274
288
|
overlayStatus(asyncDir, proof, candidateForOverlay);
|
|
275
289
|
fs.appendFileSync(path.join(asyncDir, "events.jsonl"), `${JSON.stringify({ type: "subagent.run.process_terminal", lifecycleArtifactVersion: SUBAGENT_LIFECYCLE_ARTIFACT_VERSION, ts: Date.now(), runId, processTerminal: proof })}\n`, "utf-8");
|
|
276
290
|
} catch {
|
|
@@ -19,6 +19,7 @@ import { reconcileAsyncRun, reconcileNestedAsyncDescendants } from "./stale-run-
|
|
|
19
19
|
import { attachRootChildrenToSteps, findNestedRouteForRootId, projectNestedRegistryForRoot, type NestedRunResolutionScope } from "../shared/nested-events.ts";
|
|
20
20
|
import { readMissionBinding } from "../../missions/lifecycle.ts";
|
|
21
21
|
import { formatWorkflowJsonPreview } from "../../workflows/scripted-workflow.ts";
|
|
22
|
+
import { formatRunFanoutBudget, getRunFanoutBudgetSnapshot, readRunFanoutBudgetDescriptor } from "../shared/run-fanout-budget.ts";
|
|
22
23
|
|
|
23
24
|
interface RunStatusParams {
|
|
24
25
|
action?: string;
|
|
@@ -389,6 +390,8 @@ export function inspectSubagentStatus(params: RunStatusParams, deps: RunStatusDe
|
|
|
389
390
|
const steeringText = formatSteeringSummary(status);
|
|
390
391
|
const processTerminal = readProcessTerminal(asyncDir, { runId: status.runId, runnerProcessInstanceId: status.processTerminal?.runnerProcessInstanceId })
|
|
391
392
|
?? sanitizeProcessTerminal(status.processTerminal, { runId: status.runId, runnerProcessInstanceId: status.processTerminal?.runnerProcessInstanceId }, path.join(asyncDir, "status.json"));
|
|
393
|
+
const runFanoutBudgetDescriptor = readRunFanoutBudgetDescriptor(asyncDir);
|
|
394
|
+
const runFanoutBudget = runFanoutBudgetDescriptor ? getRunFanoutBudgetSnapshot(runFanoutBudgetDescriptor) : status.runFanoutBudget;
|
|
392
395
|
let missionId: string | undefined;
|
|
393
396
|
try {
|
|
394
397
|
missionId = readMissionBinding(asyncDir)?.missionId;
|
|
@@ -410,6 +413,7 @@ export function inspectSubagentStatus(params: RunStatusParams, deps: RunStatusDe
|
|
|
410
413
|
statusActivityText ? `Activity: ${statusActivityText}` : undefined,
|
|
411
414
|
steeringText ? `Steering: ${steeringText}` : undefined,
|
|
412
415
|
`Mode: ${status.mode}`,
|
|
416
|
+
runFanoutBudget ? formatRunFanoutBudget(runFanoutBudget) : undefined,
|
|
413
417
|
status.parentWorkflowRunId ? `Workflow parent: ${status.parentWorkflowRunId}${status.workflowKey ? ` (${status.workflowKey})` : ""}` : undefined,
|
|
414
418
|
status.mode === "workflow" && workflowReturnPreview !== undefined ? `Return: ${workflowReturnPreview}` : undefined,
|
|
415
419
|
status.mode === "workflow" && workflowEmitPreview !== undefined ? `Latest emit: ${workflowEmitPreview}` : undefined,
|
|
@@ -466,7 +470,7 @@ export function inspectSubagentStatus(params: RunStatusParams, deps: RunStatusDe
|
|
|
466
470
|
if (fs.existsSync(logPath)) lines.push(`Log: ${logPath}`);
|
|
467
471
|
if (fs.existsSync(eventsPath)) lines.push(`Events: ${eventsPath}`);
|
|
468
472
|
|
|
469
|
-
return { content: [{ type: "text", text: lines.join("\n") }], details: { mode: "single", results: [], ...(processTerminal ? { lifecycleStatus: { processTerminal } } : {}) } };
|
|
473
|
+
return { content: [{ type: "text", text: lines.join("\n") }], details: { mode: "single", results: [], ...(runFanoutBudget ? { runFanoutBudget } : {}), ...(processTerminal ? { lifecycleStatus: { processTerminal } } : {}) } };
|
|
470
474
|
}
|
|
471
475
|
}
|
|
472
476
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
2
|
import * as path from "node:path";
|
|
3
3
|
import { writeAtomicJson } from "../../shared/atomic-json.ts";
|
|
4
|
-
import { updateActiveRunIndex } from "./active-run-index.ts";
|
|
4
|
+
import { releaseActiveRunIndex, updateActiveRunIndex } from "./active-run-index.ts";
|
|
5
5
|
import { readStatus } from "../../shared/utils.ts";
|
|
6
6
|
import { DIRS, type AsyncParallelGroupStatus, type AsyncStatus, type NestedRunSummary, type SubagentRunMode } from "../../shared/types.ts";
|
|
7
7
|
import { resolveEffectiveThinking } from "../../shared/model-info.ts";
|
|
@@ -261,7 +261,7 @@ function writeFailedRepair(asyncDir: string, status: AsyncStatus, resultPath: st
|
|
|
261
261
|
const repair = buildFailedRepair(status, asyncDir, now, reason);
|
|
262
262
|
writeAtomicJson(resultPath, repair.result);
|
|
263
263
|
writeAtomicJson(path.join(asyncDir, "status.json"), repair.status);
|
|
264
|
-
|
|
264
|
+
if (repair.status.processTerminal?.state === "observed") releaseActiveRunIndex(asyncDir);
|
|
265
265
|
appendJsonlBestEffort(path.join(asyncDir, "events.jsonl"), {
|
|
266
266
|
type: "subagent.run.repaired_stale",
|
|
267
267
|
ts: now,
|
|
@@ -351,7 +351,7 @@ export function reconcileAsyncRun(asyncDir: string, options: ReconcileAsyncRunOp
|
|
|
351
351
|
: undefined;
|
|
352
352
|
if (terminalStatus) {
|
|
353
353
|
writeAtomicJson(path.join(asyncDir, "status.json"), terminalStatus);
|
|
354
|
-
|
|
354
|
+
if (terminalStatus.processTerminal?.state === "observed") releaseActiveRunIndex(asyncDir);
|
|
355
355
|
return { status: terminalStatus, repaired: true, resultPath, message: "Existing async result file was used to repair stale running status." };
|
|
356
356
|
}
|
|
357
357
|
if (effectiveStatus.displayDismissedAt === undefined) return { status: effectiveStatus, repaired: false, resultPath };
|
|
@@ -25,11 +25,14 @@ import {
|
|
|
25
25
|
type LaunchResolvedChildExtensionsV1,
|
|
26
26
|
type RuntimeAcknowledgedChildExtensionsV1,
|
|
27
27
|
type ModelAttempt,
|
|
28
|
+
type PiWriterProcessInstanceExitV1,
|
|
29
|
+
type ProcessTreeTerminalV1,
|
|
28
30
|
type NestedRouteInfo,
|
|
29
31
|
type NestedRunSummary,
|
|
30
32
|
type ResolvedControlConfig,
|
|
31
33
|
type ResolvedTurnBudget,
|
|
32
34
|
type ResolvedToolBudget,
|
|
35
|
+
type RunFanoutBudgetDescriptor,
|
|
33
36
|
type SubagentRunMode,
|
|
34
37
|
type SubagentOutputState,
|
|
35
38
|
type UsageBudgetConfig,
|
|
@@ -68,13 +71,14 @@ import {
|
|
|
68
71
|
DEFAULT_GLOBAL_CONCURRENCY_LIMIT,
|
|
69
72
|
Semaphore,
|
|
70
73
|
} from "../shared/parallel-utils.ts";
|
|
71
|
-
import { applyThinkingSuffix, buildPiArgs, cleanupTempDir, projectLaunchResolvedChildExtensions, resolvePiLaunchToolPlan } from "../shared/pi-args.ts";
|
|
74
|
+
import { applyThinkingSuffix, buildPiArgs, cleanupTempDir, projectLaunchResolvedChildExtensions, resolvePiLaunchToolPlan, type SubagentTaskDelivery } from "../shared/pi-args.ts";
|
|
72
75
|
import { readRuntimeAcknowledgedExtensions } from "../shared/runtime-acknowledged-extensions.ts";
|
|
73
76
|
import { outputEntryFromAsyncResult, resolveOutputReferences } from "../shared/chain-outputs.ts";
|
|
74
77
|
import { createStructuredOutputRuntime, MISSING_STRUCTURED_OUTPUT_CALL_ERROR, readStructuredOutput } from "../shared/structured-output.ts";
|
|
75
78
|
import { formatProcessSignalError, isUnexplainedProcessSignal } from "../shared/process-signal.ts";
|
|
76
79
|
import { readChildToolDiagnosticError } from "../shared/tool-availability.ts";
|
|
77
80
|
import { collectDynamicResults, DynamicFanoutError, materializeDynamicParallelStep, validateDynamicCollection } from "../shared/dynamic-fanout.ts";
|
|
81
|
+
import { claimRunFanoutBatch, getRunFanoutBudgetSnapshot } from "../shared/run-fanout-budget.ts";
|
|
78
82
|
import { nestedSummaryFromAsyncStatus, projectNestedEvents, resolveNestedAsyncDir, writeNestedEvent } from "../shared/nested-events.ts";
|
|
79
83
|
import { formatModelAttemptNote, isRetryableModelFailure } from "../shared/model-fallback.ts";
|
|
80
84
|
import {
|
|
@@ -85,9 +89,10 @@ import {
|
|
|
85
89
|
waitForSubagentStartupRetry,
|
|
86
90
|
} from "../shared/subagent-startup-retry.ts";
|
|
87
91
|
import { markProcessTerminalCandidateLeaseRelease, writeProcessTerminalCandidate, type ProcessTerminalCandidate } from "./process-terminal.ts";
|
|
92
|
+
import { createOwnedProcessTreeController, type OwnedProcessTreeController } from "./owned-process-tree.ts";
|
|
88
93
|
import { createSteeringStatus, recordSteeringRequest, steeringStatus, terminalSteeringNoticeState, updateSteeringTarget } from "./steering.ts";
|
|
89
94
|
import { attachPostExitStdioGuard, trySignalChild } from "../../shared/post-exit-stdio-guard.ts";
|
|
90
|
-
import { detectSubagentError, extractTextFromContent, extractToolArgsPreview, getFinalOutput, hasEmptyTerminalAssistantResponse, readStatus } from "../../shared/utils.ts";
|
|
95
|
+
import { PROMPT_REDACTED, detectSubagentError, extractTextFromContent, extractToolArgsPreview, getFinalOutput, hasEmptyTerminalAssistantResponse, readStatus } from "../../shared/utils.ts";
|
|
91
96
|
import { evaluateCompletionMutationGuard } from "../shared/completion-guard.ts";
|
|
92
97
|
import {
|
|
93
98
|
createMutatingFailureState,
|
|
@@ -177,6 +182,7 @@ interface SubagentRunConfig {
|
|
|
177
182
|
/** Global cap on simultaneously-running subagent tasks within this run. */
|
|
178
183
|
globalConcurrencyLimit?: number;
|
|
179
184
|
capabilityCeiling?: ResolvedSubagentCapabilityCeiling;
|
|
185
|
+
runFanoutBudget?: RunFanoutBudgetDescriptor;
|
|
180
186
|
launchContractDigest?: string;
|
|
181
187
|
launchResolvedExtensions?: LaunchResolvedChildExtensionsV1;
|
|
182
188
|
runtimeAcknowledgedExtensions?: RuntimeAcknowledgedChildExtensionsV1;
|
|
@@ -232,7 +238,7 @@ interface StepResult {
|
|
|
232
238
|
structuredOutputSchemaPath?: string;
|
|
233
239
|
acceptance?: import("../../shared/types.ts").AcceptanceLedger;
|
|
234
240
|
watchdog?: import("../../shared/types.ts").ChildWatchdogProgress;
|
|
235
|
-
writerProcesses?:
|
|
241
|
+
writerProcesses?: PiWriterProcessInstanceExitV1[];
|
|
236
242
|
writerAttemptCount?: number;
|
|
237
243
|
runner?: ExternalCliRunnerStatus;
|
|
238
244
|
externalProcess?: ExternalProcessStatus;
|
|
@@ -512,6 +518,7 @@ interface RunPiStreamingResult {
|
|
|
512
518
|
processInstanceId: string;
|
|
513
519
|
processCloseObservedAt?: number;
|
|
514
520
|
processSignal?: string | null;
|
|
521
|
+
processTree: ProcessTreeTerminalV1;
|
|
515
522
|
}
|
|
516
523
|
|
|
517
524
|
function runPiStreaming(
|
|
@@ -548,7 +555,9 @@ function runPiStreaming(
|
|
|
548
555
|
stdio: ["ignore", "pipe", "pipe"],
|
|
549
556
|
env: spawnEnv,
|
|
550
557
|
windowsHide: true,
|
|
558
|
+
detached: process.platform !== "win32",
|
|
551
559
|
});
|
|
560
|
+
let processTreeController: OwnedProcessTreeController | undefined;
|
|
552
561
|
const stderrTail = createBoundedByteTail();
|
|
553
562
|
const rawStdoutTail = createBoundedByteTail();
|
|
554
563
|
const messages: Message[] = [];
|
|
@@ -556,6 +565,7 @@ function runPiStreaming(
|
|
|
556
565
|
let model: string | undefined;
|
|
557
566
|
let writerRegistrationError: string | undefined;
|
|
558
567
|
if (typeof child.pid === "number") {
|
|
568
|
+
processTreeController = createOwnedProcessTreeController(child.pid);
|
|
559
569
|
try {
|
|
560
570
|
onWriterProcess?.({ state: "running", pid: child.pid });
|
|
561
571
|
} catch (writerError) {
|
|
@@ -703,7 +713,6 @@ function runPiStreaming(
|
|
|
703
713
|
// a lingering stdio holder after `exit`, or a child that never exits.
|
|
704
714
|
const FINAL_STOP_GRACE_MS = 1000;
|
|
705
715
|
const HARD_KILL_MS = 3000;
|
|
706
|
-
const TIMEOUT_HARD_KILL_MS = 3000;
|
|
707
716
|
let childExited = false;
|
|
708
717
|
let forcedTerminationSignal = false;
|
|
709
718
|
let cleanTerminalAssistantStopReceived = false;
|
|
@@ -711,7 +720,6 @@ function runPiStreaming(
|
|
|
711
720
|
let finalDrainTimer: NodeJS.Timeout | undefined;
|
|
712
721
|
let finalHardKillTimer: NodeJS.Timeout | undefined;
|
|
713
722
|
let watchdogTailTimer: NodeJS.Timeout | undefined;
|
|
714
|
-
let timeoutHardKillTimer: NodeJS.Timeout | undefined;
|
|
715
723
|
let turnBudgetTerminationTimer: NodeJS.Timeout | undefined;
|
|
716
724
|
let turnBudgetHardKillTimer: NodeJS.Timeout | undefined;
|
|
717
725
|
let protocolHardKillTimer: NodeJS.Timeout | undefined;
|
|
@@ -776,22 +784,16 @@ function runPiStreaming(
|
|
|
776
784
|
timedOut = true;
|
|
777
785
|
interrupted = false;
|
|
778
786
|
error = timeoutMessage ?? "Subagent timed out.";
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
if (!settled) trySignalChild(child, "SIGKILL");
|
|
782
|
-
}, TIMEOUT_HARD_KILL_MS);
|
|
783
|
-
timeoutHardKillTimer.unref?.();
|
|
787
|
+
if (processTreeController) void processTreeController.terminate();
|
|
788
|
+
else trySignalChild(child, "SIGTERM");
|
|
784
789
|
});
|
|
785
790
|
registerStop?.(() => {
|
|
786
791
|
if (settled || timedOut || stopped) return;
|
|
787
792
|
stopped = true;
|
|
788
793
|
interrupted = false;
|
|
789
794
|
error = stopMessage ?? "Subagent stopped by user.";
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
if (!settled) trySignalChild(child, "SIGKILL");
|
|
793
|
-
}, TIMEOUT_HARD_KILL_MS);
|
|
794
|
-
timeoutHardKillTimer.unref?.();
|
|
795
|
+
if (processTreeController) void processTreeController.terminate();
|
|
796
|
+
else trySignalChild(child, "SIGTERM");
|
|
795
797
|
});
|
|
796
798
|
registerTurnBudgetAbort?.((message, state) => {
|
|
797
799
|
if (settled || timedOut || stopped || turnBudgetExceeded) return;
|
|
@@ -820,10 +822,6 @@ function runPiStreaming(
|
|
|
820
822
|
finalHardKillTimer = undefined;
|
|
821
823
|
}
|
|
822
824
|
clearWatchdogTailTimer();
|
|
823
|
-
if (timeoutHardKillTimer) {
|
|
824
|
-
clearTimeout(timeoutHardKillTimer);
|
|
825
|
-
timeoutHardKillTimer = undefined;
|
|
826
|
-
}
|
|
827
825
|
if (turnBudgetTerminationTimer) {
|
|
828
826
|
clearTimeout(turnBudgetTerminationTimer);
|
|
829
827
|
turnBudgetTerminationTimer = undefined;
|
|
@@ -885,9 +883,12 @@ function runPiStreaming(
|
|
|
885
883
|
childExited = true;
|
|
886
884
|
clearDrainTimers();
|
|
887
885
|
});
|
|
888
|
-
child.on("close", (exitCode, signal) => {
|
|
886
|
+
child.on("close", async (exitCode, signal) => {
|
|
889
887
|
settled = true;
|
|
890
888
|
const processCloseObservedAt = Date.now();
|
|
889
|
+
const processTree = processTreeController
|
|
890
|
+
? await processTreeController.finishAfterWriterClose()
|
|
891
|
+
: { state: "unknown" as const, reason: "verification-failed" as const, diagnostic: "Writer PID was unavailable." };
|
|
891
892
|
try {
|
|
892
893
|
onWriterProcess?.({ state: "none" });
|
|
893
894
|
} catch {
|
|
@@ -939,6 +940,7 @@ function runPiStreaming(
|
|
|
939
940
|
processInstanceId,
|
|
940
941
|
processCloseObservedAt,
|
|
941
942
|
processSignal: signal,
|
|
943
|
+
processTree,
|
|
942
944
|
}));
|
|
943
945
|
});
|
|
944
946
|
|
|
@@ -961,7 +963,7 @@ function runPiStreaming(
|
|
|
961
963
|
const stderr = stderrTail.text();
|
|
962
964
|
const finalOutput = getFinalOutput(messages) || rawStdoutTail.text().trim();
|
|
963
965
|
const spawnErrorMessage = spawnError instanceof Error ? spawnError.message : String(spawnError);
|
|
964
|
-
resolve(omitUndefinedProperties({ stderr, exitCode: 1, messages, usage, toolCount, durationMs: Date.now() - startedAt, model, error: stopped ? (stopMessage ?? "Subagent stopped by user.") : timedOut ? (timeoutMessage ?? "Subagent timed out.") : turnBudgetExceeded ? turnBudgetMessage : error ?? assistantError ?? spawnErrorMessage, protocolError, finalOutput: (timedOut || stopped) && !finalOutput.trim() ? (stopped ? stopMessage ?? "Subagent stopped by user." : timeoutMessage ?? "Subagent timed out.") : finalOutput, outputState: finalOutput.trim() ? "present" : "absent", timedOut, stopped, turnBudget, turnBudgetExceeded, wrapUpRequested: turnBudget?.outcome === "wrap-up-requested" || turnBudget?.outcome === "termination-deferred" || turnBudgetExceeded || undefined, observedMutationAttempt, structuredOutputToolInvoked, structuredOutputMessageStartIndex, watchdog: childWatchdogState, processInstanceId }));
|
|
966
|
+
resolve(omitUndefinedProperties({ stderr, exitCode: 1, messages, usage, toolCount, durationMs: Date.now() - startedAt, model, error: stopped ? (stopMessage ?? "Subagent stopped by user.") : timedOut ? (timeoutMessage ?? "Subagent timed out.") : turnBudgetExceeded ? turnBudgetMessage : error ?? assistantError ?? spawnErrorMessage, protocolError, finalOutput: (timedOut || stopped) && !finalOutput.trim() ? (stopped ? stopMessage ?? "Subagent stopped by user." : timeoutMessage ?? "Subagent timed out.") : finalOutput, outputState: finalOutput.trim() ? "present" : "absent", timedOut, stopped, turnBudget, turnBudgetExceeded, wrapUpRequested: turnBudget?.outcome === "wrap-up-requested" || turnBudget?.outcome === "termination-deferred" || turnBudgetExceeded || undefined, observedMutationAttempt, structuredOutputToolInvoked, structuredOutputMessageStartIndex, watchdog: childWatchdogState, processInstanceId, processTree: { state: "unknown", reason: "verification-failed", diagnostic: spawnErrorMessage } }));
|
|
965
967
|
});
|
|
966
968
|
});
|
|
967
969
|
}
|
|
@@ -1105,6 +1107,7 @@ interface SingleStepContext {
|
|
|
1105
1107
|
orchestratorIntercomTarget?: string;
|
|
1106
1108
|
nestedRoute?: NestedRouteInfo;
|
|
1107
1109
|
capabilityCeiling?: ResolvedSubagentCapabilityCeiling;
|
|
1110
|
+
runFanoutBudget?: RunFanoutBudgetDescriptor;
|
|
1108
1111
|
onAttemptStart?: (attempt: { model?: string; thinking?: string }) => void;
|
|
1109
1112
|
onChildEvent?: (event: ChildEvent) => void;
|
|
1110
1113
|
onWriterProcess?: (writer: { state: "none" | "spawning" } | { state: "running"; pid: number }) => void;
|
|
@@ -1206,7 +1209,7 @@ async function runSingleStep(
|
|
|
1206
1209
|
artifactPaths = getArtifactPaths(ctx.artifactsDir, ctx.id, step.agent, index);
|
|
1207
1210
|
fs.mkdirSync(ctx.artifactsDir, { recursive: true });
|
|
1208
1211
|
if (ctx.artifactConfig?.includeInput !== false) {
|
|
1209
|
-
fs.writeFileSync(artifactPaths.inputPath, `# Task for ${step.agent}\n\n${
|
|
1212
|
+
fs.writeFileSync(artifactPaths.inputPath, `# Task for ${step.agent}\n\n${PROMPT_REDACTED}; live Prompt Audit only.\n`, "utf-8");
|
|
1210
1213
|
}
|
|
1211
1214
|
if (ctx.artifactConfig?.includeTranscript !== false) {
|
|
1212
1215
|
transcriptWriter = createChildTranscriptWriter({
|
|
@@ -1219,7 +1222,7 @@ async function runSingleStep(
|
|
|
1219
1222
|
});
|
|
1220
1223
|
}
|
|
1221
1224
|
}
|
|
1222
|
-
transcriptWriter?.writeInitialUserMessage(
|
|
1225
|
+
transcriptWriter?.writeInitialUserMessage(`${PROMPT_REDACTED}; live Prompt Audit only.`);
|
|
1223
1226
|
|
|
1224
1227
|
if (step.runner?.type === "external-cli") {
|
|
1225
1228
|
const runner: ExternalCliRunnerStatus = {
|
|
@@ -1262,7 +1265,7 @@ async function runSingleStep(
|
|
|
1262
1265
|
artifactPaths,
|
|
1263
1266
|
artifactConfig: ctx.artifactConfig,
|
|
1264
1267
|
output: formatOutputArtifactContent(omitUndefinedProperties({ output: resolvedOutput.fullOutput, error: external.error, metadataPath: ctx.artifactConfig?.includeMetadata === false ? undefined : artifactPaths.metadataPath })),
|
|
1265
|
-
metadata: { runId: ctx.id, agent: step.agent, task, runner, externalProcess: external.externalProcess, exitCode: external.exitCode, error: external.error, timestamp: Date.now() },
|
|
1268
|
+
metadata: { runId: ctx.id, agent: step.agent, task: PROMPT_REDACTED, runner, externalProcess: external.externalProcess, exitCode: external.exitCode, error: external.error, timestamp: Date.now() },
|
|
1266
1269
|
})
|
|
1267
1270
|
: {};
|
|
1268
1271
|
return omitUndefinedProperties({
|
|
@@ -1292,7 +1295,7 @@ async function runSingleStep(
|
|
|
1292
1295
|
let capabilityAudit: import("../shared/capability-ceiling.ts").SubagentCapabilityAudit | undefined;
|
|
1293
1296
|
let launchResolvedExtensions = step.launchResolvedExtensions;
|
|
1294
1297
|
const modelAttempts: ModelAttempt[] = [];
|
|
1295
|
-
const writerProcesses:
|
|
1298
|
+
const writerProcesses: PiWriterProcessInstanceExitV1[] = [];
|
|
1296
1299
|
let writerAttemptCount = 0;
|
|
1297
1300
|
const attemptNotes: string[] = [];
|
|
1298
1301
|
const eventsPath = path.join(path.dirname(ctx.outputFile), "events.jsonl");
|
|
@@ -1306,6 +1309,9 @@ async function runSingleStep(
|
|
|
1306
1309
|
|
|
1307
1310
|
let modelIndex = 0;
|
|
1308
1311
|
let startupAttemptIndex = 0;
|
|
1312
|
+
// Escalated to "file" after an unexplained zero-activity startup failure so
|
|
1313
|
+
// retries keep the task text out of argv (endpoint pre-exec scans may deny it).
|
|
1314
|
+
let taskDeliveryOverride: SubagentTaskDelivery | undefined;
|
|
1309
1315
|
modelAttemptsLoop: while (modelIndex < candidates.length) {
|
|
1310
1316
|
if (ctx.timeoutSignal?.aborted || ctx.stopSignal?.aborted || ctx.skipAcceptance?.()) break;
|
|
1311
1317
|
const candidate = candidates[modelIndex];
|
|
@@ -1331,6 +1337,7 @@ async function runSingleStep(
|
|
|
1331
1337
|
parentSessionId: step.parentSessionId,
|
|
1332
1338
|
baseArgs: ["--mode", "json", "-p"],
|
|
1333
1339
|
task,
|
|
1340
|
+
taskDelivery: taskDeliveryOverride,
|
|
1334
1341
|
sessionEnabled,
|
|
1335
1342
|
sessionDir,
|
|
1336
1343
|
sessionFile: step.sessionFile,
|
|
@@ -1356,6 +1363,10 @@ async function runSingleStep(
|
|
|
1356
1363
|
parentControlInbox: ctx.nestedRoute?.controlInbox,
|
|
1357
1364
|
parentRootRunId: ctx.nestedRoute?.rootRunId,
|
|
1358
1365
|
parentCapabilityToken: ctx.nestedRoute?.capabilityToken,
|
|
1366
|
+
runFanoutBudget: ctx.runFanoutBudget ? {
|
|
1367
|
+
...ctx.runFanoutBudget,
|
|
1368
|
+
...(step.runFanoutPath ? { parentPath: `${ctx.runFanoutBudget.parentPath ? `${ctx.runFanoutBudget.parentPath}/` : ""}${step.runFanoutPath}` } : {}),
|
|
1369
|
+
} : undefined,
|
|
1359
1370
|
steerInboxDir: ctx.steerInboxDir,
|
|
1360
1371
|
steerCapabilityPath: ctx.steerCapabilityPath,
|
|
1361
1372
|
steerAckDir: ctx.steerAckDir,
|
|
@@ -1429,6 +1440,7 @@ async function runSingleStep(
|
|
|
1429
1440
|
closeObservedAt: run.processCloseObservedAt,
|
|
1430
1441
|
exitCode: run.exitCode,
|
|
1431
1442
|
signal: run.processSignal ?? null,
|
|
1443
|
+
processTree: run.processTree,
|
|
1432
1444
|
});
|
|
1433
1445
|
}
|
|
1434
1446
|
if (run.turnBudget) turnBudget = run.turnBudget;
|
|
@@ -1583,6 +1595,10 @@ async function runSingleStep(
|
|
|
1583
1595
|
});
|
|
1584
1596
|
const shouldRetry = await waitForSubagentStartupRetry(retryDelayMs, [ctx.timeoutSignal, ctx.stopSignal]);
|
|
1585
1597
|
if (!shouldRetry || ctx.skipAcceptance?.()) break modelAttemptsLoop;
|
|
1598
|
+
if (!taskDeliveryOverride && run.processSignal === "SIGKILL") {
|
|
1599
|
+
taskDeliveryOverride = "file";
|
|
1600
|
+
attemptNotes.push("[startup-retry] retrying with file task delivery to keep the task text out of the child process argv.");
|
|
1601
|
+
}
|
|
1586
1602
|
attempt.error = retryNote;
|
|
1587
1603
|
attemptNotes.push(retryNote);
|
|
1588
1604
|
startupAttemptIndex += 1;
|
|
@@ -1700,7 +1716,7 @@ async function runSingleStep(
|
|
|
1700
1716
|
metadata: {
|
|
1701
1717
|
runId: ctx.id,
|
|
1702
1718
|
agent: step.agent,
|
|
1703
|
-
task,
|
|
1719
|
+
task: PROMPT_REDACTED,
|
|
1704
1720
|
exitCode: effectiveFinalExitCode,
|
|
1705
1721
|
model: finalResult?.model,
|
|
1706
1722
|
attemptedModels: attemptedModels.length > 0 ? attemptedModels : undefined,
|
|
@@ -2171,6 +2187,7 @@ async function runSubagent(
|
|
|
2171
2187
|
...(config.launchContractDigest ? { launchContractDigest: config.launchContractDigest } : {}),
|
|
2172
2188
|
...(config.launchResolvedExtensions ? { launchResolvedExtensions: config.launchResolvedExtensions } : {}),
|
|
2173
2189
|
...(config.capabilityCeiling ? { capabilityCeiling: config.capabilityCeiling } : {}),
|
|
2190
|
+
...(config.runFanoutBudget ? { runFanoutBudget: getRunFanoutBudgetSnapshot(config.runFanoutBudget) } : {}),
|
|
2174
2191
|
...(config.parentWorkflowRunId ? { parentWorkflowRunId: config.parentWorkflowRunId } : {}),
|
|
2175
2192
|
...(config.workflowKey ? { workflowKey: config.workflowKey } : {}),
|
|
2176
2193
|
...(config.runnerProcessInstanceId ? { processTerminal: { version: 1 as const, state: "pending" as const, runId: id, runnerProcessInstanceId: config.runnerProcessInstanceId } } : {}),
|
|
@@ -2260,10 +2277,10 @@ async function runSubagent(
|
|
|
2260
2277
|
refreshWorkflowGraph();
|
|
2261
2278
|
writeAtomicJson(statusPath, statusPayload);
|
|
2262
2279
|
const activeState = isActiveAsyncState(statusPayload.state);
|
|
2263
|
-
if (activeState !== lastIndexedActiveState) {
|
|
2280
|
+
if (activeState && activeState !== lastIndexedActiveState) {
|
|
2264
2281
|
updateActiveRunIndex(asyncDir, statusPayload.state);
|
|
2265
|
-
lastIndexedActiveState = activeState;
|
|
2266
2282
|
}
|
|
2283
|
+
lastIndexedActiveState = activeState;
|
|
2267
2284
|
emitNestedSelfEvent(statusPayload.state === "running" || statusPayload.state === "queued" ? "subagent.nested.updated" : "subagent.nested.completed");
|
|
2268
2285
|
};
|
|
2269
2286
|
const statusWriteCoalescer = createFileCoalescer(writeStatusPayloadNow, 100);
|
|
@@ -3035,7 +3052,6 @@ async function runSubagent(
|
|
|
3035
3052
|
if (stopped || timedOut || interrupted || statusPayload.state !== "running") return;
|
|
3036
3053
|
stopped = true;
|
|
3037
3054
|
const now = Date.now();
|
|
3038
|
-
statusPayload.state = "stopped";
|
|
3039
3055
|
statusPayload.stopped = true;
|
|
3040
3056
|
statusPayload.error = stopMessage;
|
|
3041
3057
|
currentActivityState = undefined;
|
|
@@ -3068,7 +3084,6 @@ async function runSubagent(
|
|
|
3068
3084
|
timedOut = true;
|
|
3069
3085
|
const now = Date.now();
|
|
3070
3086
|
const message = timeoutMessage ?? "Subagent timed out.";
|
|
3071
|
-
statusPayload.state = "failed";
|
|
3072
3087
|
statusPayload.timedOut = true;
|
|
3073
3088
|
statusPayload.error = message;
|
|
3074
3089
|
currentActivityState = undefined;
|
|
@@ -3258,6 +3273,9 @@ async function runSubagent(
|
|
|
3258
3273
|
throw new DynamicFanoutError(`Dynamic chain step ${stepIndex + 1} materialized ${materialized.parallel.length} items that resolve output to the same path: ${step.parallel.outputPath}. Remove the explicit output path or use an inherited relative agent output so each item can be isolated.`);
|
|
3259
3274
|
}
|
|
3260
3275
|
if (materialized.collectedOnEmpty) await validateDynamicCollection(step.collect.outputSchema, materialized.collectedOnEmpty);
|
|
3276
|
+
if (!config.runFanoutBudget) throw new Error("Async runner is missing its run fan-out budget identity.");
|
|
3277
|
+
const runFanoutBudget = claimRunFanoutBatch(config.runFanoutBudget, materialized.parallel.map((_, itemIndex) => `chain[${stepIndex}].expand[${itemIndex}]`));
|
|
3278
|
+
statusPayload.runFanoutBudget = runFanoutBudget;
|
|
3261
3279
|
} catch (error) {
|
|
3262
3280
|
const now = Date.now();
|
|
3263
3281
|
const message = error instanceof DynamicFanoutError ? error.message : error instanceof Error ? error.message : String(error);
|
|
@@ -3364,6 +3382,7 @@ async function runSubagent(
|
|
|
3364
3382
|
const materializedTask = step.parallel.namespaceOutputPath ? injectSingleOutputInstruction(taskText, outputPath, step.parallel) : taskText;
|
|
3365
3383
|
return omitUndefinedProperties({
|
|
3366
3384
|
...step.parallel,
|
|
3385
|
+
runFanoutPath: `chain[${stepIndex}].expand[${itemIndex}]`,
|
|
3367
3386
|
task: materializedTask,
|
|
3368
3387
|
effectiveAcceptance: resolveEffectiveAcceptance(omitUndefinedProperties({
|
|
3369
3388
|
explicit: step.parallel.acceptanceInput,
|
|
@@ -3523,6 +3542,7 @@ async function runSubagent(
|
|
|
3523
3542
|
orchestratorIntercomTarget: config.controlIntercomTarget,
|
|
3524
3543
|
nestedRoute: config.nestedRoute,
|
|
3525
3544
|
capabilityCeiling: config.capabilityCeiling,
|
|
3545
|
+
runFanoutBudget: config.runFanoutBudget,
|
|
3526
3546
|
registerInterrupt: (interrupt) => registerStepInterrupt(fi, interrupt),
|
|
3527
3547
|
registerTimeout: (interrupt) => registerStepTimeout(fi, interrupt),
|
|
3528
3548
|
registerStop: (stop) => registerStepStop(fi, stop),
|
|
@@ -3905,6 +3925,7 @@ async function runSubagent(
|
|
|
3905
3925
|
orchestratorIntercomTarget: config.controlIntercomTarget,
|
|
3906
3926
|
nestedRoute: config.nestedRoute,
|
|
3907
3927
|
capabilityCeiling: config.capabilityCeiling,
|
|
3928
|
+
runFanoutBudget: config.runFanoutBudget,
|
|
3908
3929
|
registerInterrupt: (interrupt) => registerStepInterrupt(fi, interrupt),
|
|
3909
3930
|
registerTimeout: (interrupt) => registerStepTimeout(fi, interrupt),
|
|
3910
3931
|
registerStop: (stop) => registerStepStop(fi, stop),
|
|
@@ -4194,6 +4215,7 @@ async function runSubagent(
|
|
|
4194
4215
|
orchestratorIntercomTarget: config.controlIntercomTarget,
|
|
4195
4216
|
nestedRoute: config.nestedRoute,
|
|
4196
4217
|
capabilityCeiling: config.capabilityCeiling,
|
|
4218
|
+
runFanoutBudget: config.runFanoutBudget,
|
|
4197
4219
|
registerInterrupt: (interrupt) => registerStepInterrupt(flatIndex, interrupt),
|
|
4198
4220
|
registerTimeout: (interrupt) => registerStepTimeout(flatIndex, interrupt),
|
|
4199
4221
|
registerStop: (stop) => registerStepStop(flatIndex, stop),
|
|
@@ -4438,6 +4460,9 @@ async function runSubagent(
|
|
|
4438
4460
|
clearTimeout(timeoutTimer);
|
|
4439
4461
|
timeoutTimer = undefined;
|
|
4440
4462
|
}
|
|
4463
|
+
if (!timedOut && !stopped && !interrupted && config.timeoutMs !== undefined && results.some((result) => result.timedOut === true && result.error === timeoutMessage)) {
|
|
4464
|
+
timedOut = true;
|
|
4465
|
+
}
|
|
4441
4466
|
const signalTerminated = !stopped && !timedOut && !turnBudgetExceeded && !interrupted && results.some((result) => result.exitCode !== 0 && isUnexplainedProcessSignal(omitUndefinedProperties({
|
|
4442
4467
|
processSignal: result.processSignal,
|
|
4443
4468
|
interrupted: result.interrupted,
|
|
@@ -4630,7 +4655,7 @@ async function runSubagent(
|
|
|
4630
4655
|
shareError,
|
|
4631
4656
|
}));
|
|
4632
4657
|
if (config.runnerProcessInstanceId) {
|
|
4633
|
-
const writers: Record<string,
|
|
4658
|
+
const writers: Record<string, PiWriterProcessInstanceExitV1[]> = {};
|
|
4634
4659
|
const expectedWriters: Record<string, number> = {};
|
|
4635
4660
|
for (const [index, result] of results.entries()) {
|
|
4636
4661
|
writers[String(index)] = result.writerProcesses ?? [];
|