pi-subagents 0.64.0 → 0.65.1
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 +65 -0
- package/README.md +2 -2
- package/agents/reviewer.md +1 -1
- package/agents/scout.md +1 -1
- package/docs/agents.md +19 -17
- package/docs/configuration.md +23 -15
- package/docs/extension-api.md +18 -6
- package/docs/missions.md +2 -0
- package/docs/observability.md +10 -12
- package/docs/tool-reference.md +15 -8
- package/docs/watchdog.md +1 -1
- package/docs/workflows.md +13 -3
- package/package.json +5 -4
- package/runner-server-preload.mjs +13 -0
- package/skills/pi-subagents/SKILL.md +2 -1
- package/skills/pi-subagents/references/constraints-and-recipes.md +1 -1
- package/skills/pi-subagents/references/execution-controls.md +7 -3
- package/skills/pi-subagents/references/multi-lane-orchestration.md +2 -0
- package/src/agents/agent-management.ts +47 -15
- package/src/api/capability-ceiling.ts +0 -1
- package/src/api/{pi-args.ts → child-tool-plan.ts} +1 -1
- package/src/api/preflight.ts +7 -4
- package/src/extension/config.ts +4 -2
- package/src/extension/doctor.ts +2 -10
- package/src/extension/fanout-child.ts +9 -11
- package/src/extension/index.ts +58 -7
- package/src/extension/public-execution.ts +14 -0
- package/src/extension/rpc.ts +3 -2
- package/src/extension/schemas.ts +3 -2
- package/src/extension/tool-description.ts +13 -8
- package/src/integrations/pi-web-session-liveness.ts +73 -0
- package/src/intercom/native-supervisor-channel.ts +159 -95
- package/src/intercom/supervisor-ui.ts +244 -0
- package/src/missions/workflow-state.ts +37 -16
- package/src/runs/background/async-execution.ts +51 -25
- package/src/runs/background/async-job-tracker.ts +11 -0
- package/src/runs/background/async-resume.ts +14 -3
- package/src/runs/background/async-retention.ts +9 -0
- package/src/runs/background/control-channel.ts +2 -204
- package/src/runs/background/notify.ts +2 -0
- package/src/runs/background/process-terminal.ts +1 -1
- package/src/runs/background/retained-nested-route-tracker.ts +96 -0
- package/src/runs/background/run-child-session.ts +614 -0
- package/src/runs/background/run-status.ts +0 -1
- package/src/runs/background/runner-aliases.ts +152 -0
- package/src/runs/background/runner-child-sessions.ts +31 -0
- package/src/runs/background/scheduled-runs.ts +18 -4
- package/src/runs/background/subagent-runner.ts +203 -901
- package/src/runs/foreground/async-steering-action.ts +1 -17
- package/src/runs/foreground/execution.ts +204 -378
- package/src/runs/foreground/foreground-control.ts +4 -0
- package/src/runs/foreground/foreground-history.ts +3 -1
- package/src/runs/foreground/prompt-audit.ts +9 -5
- package/src/runs/foreground/subagent-executor.ts +161 -91
- package/src/runs/foreground/workflow-foreground-steering.ts +24 -98
- package/src/runs/shared/abort-recovery.ts +3 -3
- package/src/runs/shared/acceptance.ts +14 -1
- package/src/runs/shared/capability-ceiling.ts +1 -2
- package/src/runs/shared/child-hooks.ts +25 -0
- package/src/runs/shared/child-identity.ts +13 -2
- package/src/runs/shared/child-launch.ts +314 -0
- package/src/runs/shared/child-lifecycle.ts +25 -0
- package/src/runs/shared/child-runtime-config.ts +126 -0
- package/src/runs/shared/child-session.ts +337 -0
- package/src/runs/shared/child-tool-plan.ts +530 -0
- package/src/runs/shared/claude-code-adapter.ts +5 -1
- package/src/runs/shared/completion-guard.ts +1 -1
- package/src/runs/shared/external-cli-preflight.ts +16 -0
- package/src/runs/shared/llm-intent-arbiter.ts +20 -11
- package/src/runs/shared/mcp-direct-tool-allowlist.ts +5 -4
- package/src/runs/shared/model-exclusions.ts +84 -15
- package/src/runs/shared/model-fallback.ts +78 -6
- package/src/runs/shared/nested-events.ts +32 -48
- package/src/runs/shared/nested-path.ts +0 -14
- package/src/runs/shared/orca-progress-tabs.ts +12 -7
- package/src/runs/shared/parallel-utils.ts +1 -2
- package/src/runs/shared/permissions.ts +0 -13
- package/src/runs/shared/process-signal.ts +4 -1
- package/src/runs/shared/run-fanout-budget.ts +0 -13
- package/src/runs/shared/runtime-acknowledged-extensions.ts +0 -27
- package/src/runs/shared/structured-output.ts +17 -4
- package/src/runs/shared/subagent-control.ts +2 -0
- package/src/runs/shared/subagent-prompt-runtime.ts +87 -384
- package/src/runs/shared/tool-availability.ts +18 -62
- package/src/runs/shared/tool-budget.ts +0 -14
- package/src/runs/shared/worktree-cleanup-plan.ts +31 -9
- package/src/runs/shared/worktree.ts +192 -40
- package/src/shared/child-session-name.ts +1 -1
- package/src/shared/jsonl-writer.ts +11 -0
- package/src/shared/model-response-aliases.ts +13 -0
- package/src/shared/thinking-ceiling.ts +0 -6
- package/src/shared/types.ts +63 -28
- package/src/shared/utils.ts +6 -4
- package/src/shared/watch-strategy.ts +2 -0
- package/src/slash/slash-commands.ts +0 -6
- package/src/tui/fleet-status.ts +1 -1
- package/src/tui/fleet.ts +0 -1
- package/src/tui/render.ts +254 -41
- package/src/watchdog/child-status.ts +0 -1
- package/src/watchdog/register-child.ts +12 -12
- package/src/workflows/scripted-workflow.ts +80 -7
- package/src/workflows/workflow-checklist.ts +4 -3
- package/src/runs/shared/child-protocol.ts +0 -415
- package/src/runs/shared/pi-args.ts +0 -1059
- package/src/runs/shared/subagent-startup-retry.ts +0 -116
- package/src/shared/post-exit-stdio-guard.ts +0 -85
|
@@ -1,9 +1,26 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
2
|
+
import { EventEmitter } from "node:events";
|
|
3
3
|
import * as fs from "node:fs";
|
|
4
|
+
import { createRequire } from "node:module";
|
|
4
5
|
import * as path from "node:path";
|
|
5
6
|
import { pathToFileURL } from "node:url";
|
|
6
7
|
import type { Message } from "@earendil-works/pi-ai";
|
|
8
|
+
|
|
9
|
+
// Detached runners skip Pi's CLI proxy setup. Keep fetch on the same Undici dispatcher.
|
|
10
|
+
function ensureProxyAwareHttpDispatcher(): void {
|
|
11
|
+
try {
|
|
12
|
+
// SAFETY: require loads the pinned direct dependency described by these types.
|
|
13
|
+
const undici = createRequire(import.meta.url)("undici") as typeof import("undici");
|
|
14
|
+
const dispatcher = new undici.EnvHttpProxyAgent({ allowH2: false });
|
|
15
|
+
// Fetch rejects stream errors; the listener prevents an unhandled EventEmitter error.
|
|
16
|
+
EventEmitter.prototype.on.call(dispatcher, "error", () => {});
|
|
17
|
+
undici.setGlobalDispatcher(dispatcher);
|
|
18
|
+
undici.install();
|
|
19
|
+
} catch (error) {
|
|
20
|
+
console.error(`[pi-subagents] proxy-aware HTTP dispatcher not installed: ${error instanceof Error ? error.message : String(error)}`);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
ensureProxyAwareHttpDispatcher();
|
|
7
24
|
import { writeAtomicJson } from "../../shared/atomic-json.ts";
|
|
8
25
|
import { writeAsyncResultFile, writePendingAsyncResultFile } from "./result-files.ts";
|
|
9
26
|
import { createFileCoalescer } from "../../shared/file-coalescer.ts";
|
|
@@ -11,9 +28,9 @@ import { createCapacityResilientJsonWriter } from "../../shared/capacity-resilie
|
|
|
11
28
|
import { isStorageCapacityError } from "../../shared/file-system-retry.ts";
|
|
12
29
|
import { updateActiveRunIndex } from "./active-run-index.ts";
|
|
13
30
|
import { createChildTranscriptWriter, type ChildTranscriptWriter } from "../../shared/child-transcript.ts";
|
|
14
|
-
import { closeSteerInbox, consumeInterruptRequest, consumeSteerRequests, deliverInterruptRequest, deliverStopRequest, deliverTimeoutRequest,
|
|
31
|
+
import { closeSteerInbox, consumeInterruptRequest, consumeSteerRequests, deliverInterruptRequest, deliverStopRequest, deliverTimeoutRequest, watchAsyncControlInbox, type SteerRequest, type StopRequest } from "./control-channel.ts";
|
|
15
32
|
import { appendJsonl as appendRawJsonl, formatOutputArtifactContent, getArtifactPaths, writeArtifact, writeMetadata } from "../../shared/artifacts.ts";
|
|
16
|
-
import { PI_CODING_AGENT_PACKAGE,
|
|
33
|
+
import { PI_CODING_AGENT_PACKAGE, resolveInstalledPiPackageRoot } from "../shared/pi-spawn.ts";
|
|
17
34
|
import { preflightLaunchCwd } from "../shared/launch-cwd.ts";
|
|
18
35
|
import { captureSingleOutputSnapshot, extractChildWrittenOutput, finalizeSingleOutput, formatSavedOutputReference, injectOutputPathSystemPrompt, injectSingleOutputInstruction, resolveSingleOutput, type SingleOutputSnapshot } from "../shared/single-output.ts";
|
|
19
36
|
import {
|
|
@@ -32,7 +49,6 @@ import {
|
|
|
32
49
|
type RuntimeAcknowledgedChildExtensionsV1,
|
|
33
50
|
type ModelAttempt,
|
|
34
51
|
type PiWriterProcessInstanceExitV1,
|
|
35
|
-
type ProcessTreeTerminalV1,
|
|
36
52
|
type NestedRouteInfo,
|
|
37
53
|
type NestedRunSummary,
|
|
38
54
|
type ResolvedControlConfig,
|
|
@@ -52,7 +68,6 @@ import {
|
|
|
52
68
|
type MaxOutputConfig,
|
|
53
69
|
SUBAGENT_LIFECYCLE_ARTIFACT_VERSION,
|
|
54
70
|
truncateOutput,
|
|
55
|
-
getSubagentDepthEnv,
|
|
56
71
|
} from "../../shared/types.ts";
|
|
57
72
|
import {
|
|
58
73
|
DEFAULT_CONTROL_CONFIG,
|
|
@@ -75,31 +90,25 @@ import {
|
|
|
75
90
|
DEFAULT_GLOBAL_CONCURRENCY_LIMIT,
|
|
76
91
|
Semaphore,
|
|
77
92
|
} from "../shared/parallel-utils.ts";
|
|
78
|
-
import { applyThinkingSuffix,
|
|
93
|
+
import { applyThinkingSuffix, deriveForkPromptCacheKey, projectLaunchResolvedChildExtensions, resolvePiLaunchToolPlan } from "../shared/child-tool-plan.ts";
|
|
94
|
+
import { buildInProcessChildLaunch, type InheritedChildRuntime } from "../shared/child-launch.ts";
|
|
95
|
+
import type { ChildSessionFactory } from "../shared/child-session.ts";
|
|
96
|
+
import { runChildSession, type ChildEvent, type RunChildSessionResult, type StepSteerHandler } from "./run-child-session.ts";
|
|
97
|
+
import { loadRunnerChildSessionFactory } from "./runner-child-sessions.ts";
|
|
98
|
+
import { SUBAGENT_CHILD_ENV } from "../shared/child-runtime-config.ts";
|
|
79
99
|
import { deriveChildSessionName } from "../../shared/child-session-name.ts";
|
|
80
100
|
import { alignForkedSessionCwd } from "../../shared/fork-session-cwd.ts";
|
|
81
|
-
import { readRuntimeAcknowledgedExtensions } from "../shared/runtime-acknowledged-extensions.ts";
|
|
82
101
|
import { outputEntryFromAsyncResult, resolveOutputReferences } from "../shared/chain-outputs.ts";
|
|
83
|
-
import { clearStructuredOutputCaptures, createStructuredOutputRuntime, MISSING_STRUCTURED_OUTPUT_CALL_ERROR, readStructuredOutput, readStructuredOutputAcceptanceReport } from "../shared/structured-output.ts";
|
|
84
|
-
import { formatMidToolExitError,
|
|
85
|
-
import {
|
|
102
|
+
import { clearStructuredOutputCaptures, createStructuredOutputFileCapture, createStructuredOutputRuntime, MISSING_STRUCTURED_OUTPUT_CALL_ERROR, readStructuredOutput, readStructuredOutputAcceptanceReport } from "../shared/structured-output.ts";
|
|
103
|
+
import { formatMidToolExitError, isOrdinaryToolForMidToolExit, isUnexplainedProcessSignal } from "../shared/process-signal.ts";
|
|
104
|
+
import { formatChildToolDiagnostic } from "../shared/tool-availability.ts";
|
|
86
105
|
import { buildTimeoutRecoverySummary, collectTrackedMutationEvidence, snapshotTrackedMutations } from "../shared/mutation-evidence.ts";
|
|
87
106
|
import { collectDynamicResults, DynamicFanoutError, materializeDynamicParallelStep, validateDynamicCollection } from "../shared/dynamic-fanout.ts";
|
|
88
107
|
import { claimRunFanoutBatch, getRunFanoutBudgetSnapshot } from "../shared/run-fanout-budget.ts";
|
|
89
108
|
import { nestedSummaryFromAsyncStatus, projectNestedEvents, resolveNestedAsyncDir, writeNestedEvent } from "../shared/nested-events.ts";
|
|
90
109
|
import { formatModelAttemptNote, formatSubagentModelVerificationError, isContextOverflow, isRetryableModelFailureAttempt, recordRetryableModelFailure } from "../shared/model-fallback.ts";
|
|
91
|
-
import {
|
|
92
|
-
SUBAGENT_STARTUP_RETRY_DELAYS_MS,
|
|
93
|
-
formatSubagentExtensionConflictError,
|
|
94
|
-
formatSubagentStartupRetryExhaustedError,
|
|
95
|
-
formatSubagentStartupRetryNote,
|
|
96
|
-
isRetryableSubagentStartupFailure,
|
|
97
|
-
waitForSubagentStartupRetry,
|
|
98
|
-
} from "../shared/subagent-startup-retry.ts";
|
|
99
110
|
import { markProcessTerminalCandidateLeaseRelease, writeProcessTerminalCandidate, type ProcessTerminalCandidate } from "./process-terminal.ts";
|
|
100
|
-
import { createOwnedProcessTreeController, type OwnedProcessTreeController } from "./owned-process-tree.ts";
|
|
101
111
|
import { createSteeringStatus, recordSteeringRequest, steeringStatus, terminalSteeringNoticeState, updateSteeringTarget } from "./steering.ts";
|
|
102
|
-
import { attachPostExitStdioGuard, trySignalChild } from "../../shared/post-exit-stdio-guard.ts";
|
|
103
112
|
import { PROMPT_REDACTED, detectSubagentError, extractTextFromContent, extractToolArgsPreview, formatEmptyTerminalAssistantResponseError, getFinalOutput, hasEmptyTerminalAssistantResponse, readStatus } from "../../shared/utils.ts";
|
|
104
113
|
import { evaluateCompletionMutationGuard, expectsImplementationMutation, hasMutationToolCapability, validateImplementationToolContract } from "../shared/completion-guard.ts";
|
|
105
114
|
import { planCompletionEvidence, projectSettlementDiagnostic } from "../shared/completion-evidence.ts";
|
|
@@ -128,7 +137,7 @@ import {
|
|
|
128
137
|
type WorktreeSetup,
|
|
129
138
|
} from "../shared/worktree.ts";
|
|
130
139
|
import { findModelInfo, resolveEffectiveThinking } from "../../shared/model-info.ts";
|
|
131
|
-
import { assertThinkingWithinCeiling
|
|
140
|
+
import { assertThinkingWithinCeiling } from "../../shared/thinking-ceiling.ts";
|
|
132
141
|
import { launchBindingDigest } from "../../shared/launch-contract.ts";
|
|
133
142
|
import { writeInitialProgressFile } from "../../shared/settings.ts";
|
|
134
143
|
import { resolveSubagentIntercomTarget } from "../../intercom/intercom-bridge.ts";
|
|
@@ -143,7 +152,6 @@ import { effectiveToolTimeoutMs, formatToolTimeoutMessage, toolTimeoutCallKey }
|
|
|
143
152
|
import { usageBudgetExceededMessage, usageBudgetState } from "../shared/usage-budget.ts";
|
|
144
153
|
import { formatParallelHandoffError, formatParallelHandoffReference, parallelHandoffPath, writeParallelHandoffGroup, writePendingParallelHandoff } from "../shared/parallel-handoff.ts";
|
|
145
154
|
import { resolveWatchdogConfig } from "../../watchdog/settings.ts";
|
|
146
|
-
import { createBoundedByteTail, createBoundedLineReader, formatProtocolOutputLimit, MAX_CHILD_STDERR_BYTES, PI_AGGREGATE_EVENT_PROJECTOR, projectChildLifecycle, type ChildLifecycleAction, type ChildLifecycleState, type ProtocolOutputLimit } from "../shared/child-protocol.ts";
|
|
147
155
|
import { acquireSessionLease, type SessionLeaseRequest } from "../shared/session-lease.ts";
|
|
148
156
|
import { buildExternalCliPrompt, runExternalCli } from "../shared/external-cli-runner.ts";
|
|
149
157
|
import { resolveClaudeCodeLaunch } from "../shared/claude-code-adapter.ts";
|
|
@@ -152,20 +160,22 @@ import { resolveCursorAgentLaunch } from "../shared/cursor-agent-adapter.ts";
|
|
|
152
160
|
import { resolveExternalCliRunnerStatus } from "../shared/external-cli-contract.ts";
|
|
153
161
|
import { runExternalJob } from "../shared/external-job-runner.ts";
|
|
154
162
|
import { createOrcaProgressTab, type OrcaProgressTab } from "../shared/orca-progress-tabs.ts";
|
|
155
|
-
import {
|
|
163
|
+
import type { ResolvedSubagentCapabilityCeiling } from "../shared/capability-ceiling.ts";
|
|
156
164
|
import {
|
|
157
|
-
CHILD_WATCHDOG_CONFIG_ENV,
|
|
158
165
|
acceptChildWatchdogEvent,
|
|
159
166
|
applyChildWatchdogMessage,
|
|
160
|
-
childWatchdogIsActive,
|
|
161
|
-
decodeChildWatchdogConfig,
|
|
162
167
|
isChildWatchdogStatusEvent,
|
|
163
168
|
resolveChildWatchdogConfig,
|
|
164
|
-
type
|
|
169
|
+
type ChildWatchdogStatusEvent,
|
|
165
170
|
} from "../../watchdog/child-status.ts";
|
|
166
171
|
|
|
167
172
|
const INTERCOM_DETACH_RECEIPT = "Detached for intercom coordination before task completion.";
|
|
168
173
|
|
|
174
|
+
// This process hosts child sessions. An ambient copy of pi-subagents loaded
|
|
175
|
+
// into one of them must register nothing; the variable marks the process as a
|
|
176
|
+
// child host.
|
|
177
|
+
process.env[SUBAGENT_CHILD_ENV] = "1";
|
|
178
|
+
|
|
169
179
|
interface SubagentRunConfig {
|
|
170
180
|
id: string;
|
|
171
181
|
steps: RunnerStep[];
|
|
@@ -183,10 +193,14 @@ interface SubagentRunConfig {
|
|
|
183
193
|
sessionId?: string | null;
|
|
184
194
|
completionOwnerId?: string;
|
|
185
195
|
piPackageRoot?: string;
|
|
186
|
-
|
|
196
|
+
/** Test seam: module the runner imports its `ChildSessionFactory` from. */
|
|
197
|
+
childSessionFactoryModule?: string;
|
|
198
|
+
/** The launching executor's own child runtime when it was itself an in-process child. */
|
|
199
|
+
inheritedChildRuntime?: InheritedChildRuntime;
|
|
187
200
|
worktreeSetupHook?: string;
|
|
188
201
|
worktreeSetupHookTimeoutMs?: number;
|
|
189
202
|
worktreeBaseDir?: string;
|
|
203
|
+
baseRef?: string;
|
|
190
204
|
worktreeProvider?: import("../../shared/types.ts").WorktreeProvider;
|
|
191
205
|
worktreeBranchPrefix?: string;
|
|
192
206
|
controlConfig?: ResolvedControlConfig;
|
|
@@ -232,7 +246,6 @@ interface StepResult {
|
|
|
232
246
|
output: string;
|
|
233
247
|
outputState?: SubagentOutputState;
|
|
234
248
|
error?: string;
|
|
235
|
-
protocolError?: ProtocolOutputLimit;
|
|
236
249
|
success?: boolean;
|
|
237
250
|
exitCode: number | null;
|
|
238
251
|
usage?: Usage;
|
|
@@ -271,8 +284,6 @@ interface StepResult {
|
|
|
271
284
|
structuredOutputSchemaPath?: string;
|
|
272
285
|
acceptance?: import("../../shared/types.ts").AcceptanceLedger;
|
|
273
286
|
watchdog?: import("../../shared/types.ts").ChildWatchdogProgress;
|
|
274
|
-
writerProcesses?: PiWriterProcessInstanceExitV1[];
|
|
275
|
-
writerAttemptCount?: number;
|
|
276
287
|
runner?: ExternalCliRunnerStatus | ExternalJobRunnerStatus;
|
|
277
288
|
externalProcess?: ExternalProcessStatus;
|
|
278
289
|
externalJob?: ExternalJobStatus;
|
|
@@ -373,10 +384,6 @@ function appendDiagnosticJsonl(filePath: string, line: string, droppedEventType?
|
|
|
373
384
|
state.diagnosticsTruncated = true;
|
|
374
385
|
}
|
|
375
386
|
|
|
376
|
-
function shouldPersistChildEvent(event: Record<string, unknown>): boolean {
|
|
377
|
-
return event.type !== "message_update";
|
|
378
|
-
}
|
|
379
|
-
|
|
380
387
|
function isBlockingSupervisorTool(toolName: string | undefined, args: unknown): boolean {
|
|
381
388
|
if (!args || typeof args !== "object" || Array.isArray(args)) return false;
|
|
382
389
|
if (toolName === "contact_supervisor") {
|
|
@@ -459,15 +466,6 @@ function appendRecentStepOutput(step: RunnerStatusStep, lines: string[]): void {
|
|
|
459
466
|
}
|
|
460
467
|
}
|
|
461
468
|
|
|
462
|
-
function assistantStartsToolCall(message: Message): boolean {
|
|
463
|
-
return Array.isArray(message.content)
|
|
464
|
-
&& message.content.some((part) => (part as { type?: string }).type === "toolCall");
|
|
465
|
-
}
|
|
466
|
-
|
|
467
|
-
function isTerminalAssistantStop(message: Message): boolean {
|
|
468
|
-
return (message as { stopReason?: string }).stopReason === "stop" && !assistantStartsToolCall(message);
|
|
469
|
-
}
|
|
470
|
-
|
|
471
469
|
type UndefinedOmitted<T extends object> = {
|
|
472
470
|
[K in keyof T]: Exclude<T[K], undefined>;
|
|
473
471
|
};
|
|
@@ -510,73 +508,6 @@ function resetStepLiveDetail(step: RunnerStatusStep): void {
|
|
|
510
508
|
step.recentOutput = [];
|
|
511
509
|
}
|
|
512
510
|
|
|
513
|
-
interface ChildEventContext {
|
|
514
|
-
eventsPath: string;
|
|
515
|
-
runId: string;
|
|
516
|
-
stepIndex: number;
|
|
517
|
-
agent: string;
|
|
518
|
-
}
|
|
519
|
-
|
|
520
|
-
interface ChildUsage {
|
|
521
|
-
input?: number;
|
|
522
|
-
inputTokens?: number;
|
|
523
|
-
output?: number;
|
|
524
|
-
outputTokens?: number;
|
|
525
|
-
cacheRead?: number;
|
|
526
|
-
cacheReadTokens?: number;
|
|
527
|
-
cacheWrite?: number;
|
|
528
|
-
cost?: { total?: number };
|
|
529
|
-
}
|
|
530
|
-
|
|
531
|
-
type ChildMessage = Message & {
|
|
532
|
-
model?: string;
|
|
533
|
-
errorMessage?: string;
|
|
534
|
-
usage?: ChildUsage;
|
|
535
|
-
};
|
|
536
|
-
|
|
537
|
-
interface ChildEvent {
|
|
538
|
-
type?: string;
|
|
539
|
-
message?: ChildMessage;
|
|
540
|
-
toolName?: string;
|
|
541
|
-
args?: Record<string, unknown>;
|
|
542
|
-
willRetry?: unknown;
|
|
543
|
-
}
|
|
544
|
-
|
|
545
|
-
interface RunPiStreamingResult {
|
|
546
|
-
stderr: string;
|
|
547
|
-
exitCode: number | null;
|
|
548
|
-
messages: Message[];
|
|
549
|
-
usage: Usage;
|
|
550
|
-
toolCount: number;
|
|
551
|
-
durationMs: number;
|
|
552
|
-
model?: string;
|
|
553
|
-
error?: string;
|
|
554
|
-
protocolError?: ProtocolOutputLimit;
|
|
555
|
-
finalOutput: string;
|
|
556
|
-
outputState: SubagentOutputState;
|
|
557
|
-
interrupted?: boolean;
|
|
558
|
-
timedOut?: boolean;
|
|
559
|
-
stopped?: boolean;
|
|
560
|
-
toolBudget?: ToolBudgetState;
|
|
561
|
-
toolBudgetBlocked?: boolean;
|
|
562
|
-
observedMutationAttempt?: boolean;
|
|
563
|
-
structuredOutputToolInvoked?: boolean;
|
|
564
|
-
structuredOutputMessageStartIndex?: number;
|
|
565
|
-
structuredOutput?: unknown;
|
|
566
|
-
watchdog?: ChildWatchdogStateSnapshot;
|
|
567
|
-
runtimeAcknowledgedExtensions?: RuntimeAcknowledgedChildExtensionsV1;
|
|
568
|
-
processInstanceId: string;
|
|
569
|
-
processCloseObservedAt?: number;
|
|
570
|
-
processSignal?: string | null;
|
|
571
|
-
processTree: ProcessTreeTerminalV1;
|
|
572
|
-
currentTool?: string;
|
|
573
|
-
currentToolArgs?: string;
|
|
574
|
-
currentPath?: string;
|
|
575
|
-
afterCompactionSettlement?: boolean;
|
|
576
|
-
abortRecoveryDiagnostic?: string;
|
|
577
|
-
effects?: import("../../shared/types.ts").EffectsProjection;
|
|
578
|
-
}
|
|
579
|
-
|
|
580
511
|
const MAX_CHILD_FAILURE_DIAGNOSTIC_CHARS = 8_192;
|
|
581
512
|
|
|
582
513
|
function formatRequiredOutputError(requiredOutput: {
|
|
@@ -611,568 +542,6 @@ function formatChildFailureDiagnostic(input: {
|
|
|
611
542
|
return `${baseError.slice(0, Math.max(0, errorLimit))}${context ? `\n${context}` : ""}`;
|
|
612
543
|
}
|
|
613
544
|
|
|
614
|
-
function runPiStreaming(
|
|
615
|
-
args: string[],
|
|
616
|
-
cwd: string,
|
|
617
|
-
outputFile: string,
|
|
618
|
-
env?: Record<string, string | undefined>,
|
|
619
|
-
piPackageRoot?: string,
|
|
620
|
-
piArgv1?: string,
|
|
621
|
-
maxSubagentDepth?: number,
|
|
622
|
-
childEventContext?: ChildEventContext,
|
|
623
|
-
registerInterrupt?: (interrupt: (() => void) | undefined) => void,
|
|
624
|
-
onChildEvent?: (event: ChildEvent) => void,
|
|
625
|
-
transcriptWriter?: ChildTranscriptWriter,
|
|
626
|
-
registerTimeout?: (interrupt: (() => void) | undefined) => void,
|
|
627
|
-
timeoutMessage?: string,
|
|
628
|
-
registerStop?: (stop: (() => void) | undefined) => void,
|
|
629
|
-
stopMessage?: string,
|
|
630
|
-
onWriterProcess?: (writer: { state: "none" | "spawning" } | { state: "running"; pid: number }) => void,
|
|
631
|
-
toolTimeoutMs?: number,
|
|
632
|
-
runDeadlineAt?: number,
|
|
633
|
-
orcaProgressTab?: OrcaProgressTab,
|
|
634
|
-
expectedModelForVerification?: string,
|
|
635
|
-
modelVerificationRegistry?: Array<{ provider: string; id: string; fullId: string }>,
|
|
636
|
-
mutationTools?: readonly string[],
|
|
637
|
-
): Promise<RunPiStreamingResult> {
|
|
638
|
-
return new Promise((resolve) => {
|
|
639
|
-
const startedAt = Date.now();
|
|
640
|
-
const processInstanceId = randomUUID();
|
|
641
|
-
onWriterProcess?.({ state: "spawning" });
|
|
642
|
-
const outputStream = fs.createWriteStream(outputFile, { flags: "w" });
|
|
643
|
-
const spawnEnv = { ...process.env, ...(env ?? {}), ...getSubagentDepthEnv(maxSubagentDepth) };
|
|
644
|
-
const spawnSpec = getPiSpawnCommand(args, {
|
|
645
|
-
...(piPackageRoot ? { piPackageRoot } : {}),
|
|
646
|
-
...(piArgv1 ? { argv1: piArgv1 } : {}),
|
|
647
|
-
});
|
|
648
|
-
const child = spawn(spawnSpec.command, spawnSpec.args, {
|
|
649
|
-
cwd,
|
|
650
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
651
|
-
env: spawnEnv,
|
|
652
|
-
windowsHide: true,
|
|
653
|
-
detached: process.platform !== "win32",
|
|
654
|
-
});
|
|
655
|
-
let processTreeController: OwnedProcessTreeController | undefined;
|
|
656
|
-
const stderrTail = createBoundedByteTail();
|
|
657
|
-
const rawStdoutTail = createBoundedByteTail();
|
|
658
|
-
const messages: Message[] = [];
|
|
659
|
-
const usage = emptyUsage();
|
|
660
|
-
let model: string | undefined;
|
|
661
|
-
let writerRegistrationError: string | undefined;
|
|
662
|
-
if (typeof child.pid === "number") {
|
|
663
|
-
processTreeController = createOwnedProcessTreeController(child.pid);
|
|
664
|
-
try {
|
|
665
|
-
onWriterProcess?.({ state: "running", pid: child.pid });
|
|
666
|
-
} catch (writerError) {
|
|
667
|
-
writerRegistrationError = `Failed to record revived Pi writer ownership: ${writerError instanceof Error ? writerError.message : String(writerError)}`;
|
|
668
|
-
trySignalChild(child, "SIGKILL");
|
|
669
|
-
}
|
|
670
|
-
}
|
|
671
|
-
let error: string | undefined = writerRegistrationError;
|
|
672
|
-
let assistantError: string | undefined;
|
|
673
|
-
let interrupted = false;
|
|
674
|
-
let timedOut = false;
|
|
675
|
-
let stopped = false;
|
|
676
|
-
let observedMutationAttempt = false;
|
|
677
|
-
let structuredOutputToolInvoked = false;
|
|
678
|
-
let structuredOutputMessageStartIndex: number | undefined;
|
|
679
|
-
let currentTool: string | undefined;
|
|
680
|
-
let currentToolArgs: string | undefined;
|
|
681
|
-
let currentPath: string | undefined;
|
|
682
|
-
let toolCount = 0;
|
|
683
|
-
type ActiveToolCall = { key: string; tool: string; args?: string; path?: string };
|
|
684
|
-
let activeToolSequence = 0;
|
|
685
|
-
const activeToolCalls = new Map<string, ActiveToolCall>();
|
|
686
|
-
const activeToolKeysByName = new Map<string, string[]>();
|
|
687
|
-
const refreshCurrentTool = (): void => {
|
|
688
|
-
const active = [...activeToolCalls.values()].at(-1);
|
|
689
|
-
currentTool = active?.tool;
|
|
690
|
-
currentToolArgs = active?.args;
|
|
691
|
-
currentPath = active?.path;
|
|
692
|
-
};
|
|
693
|
-
const recordActiveToolCall = (event: { toolCallId?: unknown; toolName: string; args?: Record<string, unknown> }): void => {
|
|
694
|
-
const key = toolTimeoutCallKey(event, ++activeToolSequence);
|
|
695
|
-
const active = omitUndefinedProperties({
|
|
696
|
-
key,
|
|
697
|
-
tool: event.toolName,
|
|
698
|
-
args: extractToolArgsPreview(event.args ?? {}),
|
|
699
|
-
path: resolveCurrentPath(event.toolName, event.args),
|
|
700
|
-
});
|
|
701
|
-
activeToolCalls.set(key, active);
|
|
702
|
-
const keys = activeToolKeysByName.get(active.tool) ?? [];
|
|
703
|
-
keys.push(key);
|
|
704
|
-
activeToolKeysByName.set(active.tool, keys);
|
|
705
|
-
refreshCurrentTool();
|
|
706
|
-
};
|
|
707
|
-
const removeActiveToolCall = (event: { toolCallId?: unknown; toolName?: unknown }): void => {
|
|
708
|
-
const key = typeof event.toolCallId === "string" && event.toolCallId.length > 0
|
|
709
|
-
? `id:${event.toolCallId}`
|
|
710
|
-
: typeof event.toolName === "string"
|
|
711
|
-
? activeToolKeysByName.get(event.toolName)?.[0]
|
|
712
|
-
: activeToolCalls.size === 1
|
|
713
|
-
? [...activeToolCalls.keys()][0]
|
|
714
|
-
: undefined;
|
|
715
|
-
if (!key) return;
|
|
716
|
-
const active = activeToolCalls.get(key);
|
|
717
|
-
if (!active) return;
|
|
718
|
-
activeToolCalls.delete(key);
|
|
719
|
-
const keys = activeToolKeysByName.get(active.tool)?.filter((candidate) => candidate !== key) ?? [];
|
|
720
|
-
if (keys.length > 0) activeToolKeysByName.set(active.tool, keys);
|
|
721
|
-
else activeToolKeysByName.delete(active.tool);
|
|
722
|
-
refreshCurrentTool();
|
|
723
|
-
};
|
|
724
|
-
const childWatchdogConfig = decodeChildWatchdogConfig(env?.[CHILD_WATCHDOG_CONFIG_ENV]);
|
|
725
|
-
let childWatchdogState: ChildWatchdogStateSnapshot | undefined;
|
|
726
|
-
const childLifecycleState: ChildLifecycleState = { compactionRetryActive: false };
|
|
727
|
-
let applyChildLifecycle = (_action: ChildLifecycleAction): void => {};
|
|
728
|
-
const updateChildWatchdogState = (snapshot: ChildWatchdogStateSnapshot): void => {
|
|
729
|
-
childWatchdogState = snapshot;
|
|
730
|
-
};
|
|
731
|
-
|
|
732
|
-
const writeOutputLine = (line: string) => {
|
|
733
|
-
if (!line.trim()) return;
|
|
734
|
-
outputStream.write(`${line}\n`);
|
|
735
|
-
orcaProgressTab?.append(`${line}\n`);
|
|
736
|
-
};
|
|
737
|
-
|
|
738
|
-
const writeOutputText = (text: string) => {
|
|
739
|
-
for (const line of text.split("\n")) {
|
|
740
|
-
writeOutputLine(line);
|
|
741
|
-
}
|
|
742
|
-
};
|
|
743
|
-
|
|
744
|
-
const appendChildEvent = (event: Record<string, unknown>) => {
|
|
745
|
-
if (!childEventContext) return;
|
|
746
|
-
if (!shouldPersistChildEvent(event)) return;
|
|
747
|
-
appendDiagnosticJsonl(childEventContext.eventsPath, JSON.stringify({
|
|
748
|
-
...event,
|
|
749
|
-
subagentSource: "child",
|
|
750
|
-
subagentRunId: childEventContext.runId,
|
|
751
|
-
subagentStepIndex: childEventContext.stepIndex,
|
|
752
|
-
subagentAgent: childEventContext.agent,
|
|
753
|
-
observedAt: Date.now(),
|
|
754
|
-
}), typeof event.type === "string" ? event.type : undefined);
|
|
755
|
-
};
|
|
756
|
-
|
|
757
|
-
const appendChildLine = (type: "subagent.child.stdout" | "subagent.child.stderr", line: string) => {
|
|
758
|
-
appendChildEvent({ type, line });
|
|
759
|
-
if (type === "subagent.child.stdout") transcriptWriter?.writeStdoutLine(line);
|
|
760
|
-
else transcriptWriter?.writeStderrLine(line);
|
|
761
|
-
};
|
|
762
|
-
|
|
763
|
-
const processStdoutLine = (line: string) => {
|
|
764
|
-
if (!line.trim()) return;
|
|
765
|
-
let event: ChildEvent;
|
|
766
|
-
try {
|
|
767
|
-
event = JSON.parse(line) as ChildEvent;
|
|
768
|
-
} catch {
|
|
769
|
-
rawStdoutTail.push(`${line}\n`);
|
|
770
|
-
writeOutputLine(line);
|
|
771
|
-
appendChildLine("subagent.child.stdout", line);
|
|
772
|
-
return;
|
|
773
|
-
}
|
|
774
|
-
|
|
775
|
-
appendChildEvent(event as unknown as Record<string, unknown>);
|
|
776
|
-
transcriptWriter?.writeChildEvent(event);
|
|
777
|
-
if (event.type === "compaction_start") compactionStartedReceived = true;
|
|
778
|
-
if (event.type === "compaction_end" && event.willRetry === true) {
|
|
779
|
-
compactionStartedReceived = false;
|
|
780
|
-
afterCompactionSettlement = false;
|
|
781
|
-
}
|
|
782
|
-
if (event.type === "agent_start" || event.type === "auto_retry_start") {
|
|
783
|
-
compactionStartedReceived = false;
|
|
784
|
-
afterCompactionSettlement = false;
|
|
785
|
-
}
|
|
786
|
-
const lifecycleAction = projectChildLifecycle(event, false, childLifecycleState);
|
|
787
|
-
if (event.type === "agent_settled" && lifecycleAction === "start-drain") {
|
|
788
|
-
agentSettledReceived = true;
|
|
789
|
-
afterCompactionSettlement = compactionStartedReceived;
|
|
790
|
-
}
|
|
791
|
-
applyChildLifecycle(lifecycleAction);
|
|
792
|
-
|
|
793
|
-
if (isChildWatchdogStatusEvent(event)) {
|
|
794
|
-
if (!childWatchdogConfig) return;
|
|
795
|
-
const next = acceptChildWatchdogEvent({
|
|
796
|
-
current: childWatchdogState,
|
|
797
|
-
event,
|
|
798
|
-
...(childEventContext ? {
|
|
799
|
-
runId: childEventContext.runId,
|
|
800
|
-
agent: childEventContext.agent,
|
|
801
|
-
childIndex: childEventContext.stepIndex,
|
|
802
|
-
} : {}),
|
|
803
|
-
});
|
|
804
|
-
if (!next) return;
|
|
805
|
-
updateChildWatchdogState(next);
|
|
806
|
-
onChildEvent?.(event);
|
|
807
|
-
if (childWatchdogIsActive(next)) {
|
|
808
|
-
if (finalDrainTimer) {
|
|
809
|
-
clearTimeout(finalDrainTimer);
|
|
810
|
-
finalDrainTimer = undefined;
|
|
811
|
-
}
|
|
812
|
-
if (finalHardKillTimer) {
|
|
813
|
-
clearTimeout(finalHardKillTimer);
|
|
814
|
-
finalHardKillTimer = undefined;
|
|
815
|
-
}
|
|
816
|
-
armWatchdogTail();
|
|
817
|
-
} else {
|
|
818
|
-
clearWatchdogTailTimer();
|
|
819
|
-
if (cleanTerminalAssistantStopReceived || agentSettledReceived) startFinalDrain();
|
|
820
|
-
}
|
|
821
|
-
return;
|
|
822
|
-
}
|
|
823
|
-
|
|
824
|
-
onChildEvent?.(event);
|
|
825
|
-
|
|
826
|
-
if (event.type === "tool_execution_end") {
|
|
827
|
-
clearActiveToolTimeout(event);
|
|
828
|
-
removeActiveToolCall(event);
|
|
829
|
-
return;
|
|
830
|
-
}
|
|
831
|
-
|
|
832
|
-
if (event.type === "tool_execution_start" && event.toolName) {
|
|
833
|
-
toolCount += 1;
|
|
834
|
-
armToolTimeout({ toolCallId: (event as { toolCallId?: unknown }).toolCallId, toolName: event.toolName });
|
|
835
|
-
recordActiveToolCall({ toolCallId: (event as { toolCallId?: unknown }).toolCallId, toolName: event.toolName, args: event.args });
|
|
836
|
-
if (event.toolName === "structured_output") {
|
|
837
|
-
structuredOutputToolInvoked = true;
|
|
838
|
-
structuredOutputMessageStartIndex = messages.length;
|
|
839
|
-
}
|
|
840
|
-
observedMutationAttempt = observedMutationAttempt || isMutatingTool(event.toolName, event.args, mutationTools);
|
|
841
|
-
const toolArgs = extractToolArgsPreview(event.args ?? {});
|
|
842
|
-
writeOutputLine(toolArgs ? `${event.toolName}: ${toolArgs}` : event.toolName);
|
|
843
|
-
return;
|
|
844
|
-
}
|
|
845
|
-
|
|
846
|
-
if ((event.type === "message_end" || event.type === "tool_result_end") && event.message) {
|
|
847
|
-
if (event.type === "tool_result_end") {
|
|
848
|
-
clearActiveToolTimeout(event);
|
|
849
|
-
removeActiveToolCall({
|
|
850
|
-
toolCallId: (event.message as { toolCallId?: unknown }).toolCallId ?? (event as { toolCallId?: unknown }).toolCallId,
|
|
851
|
-
toolName: (event.message as { toolName?: unknown }).toolName ?? event.toolName,
|
|
852
|
-
});
|
|
853
|
-
}
|
|
854
|
-
messages.push(event.message);
|
|
855
|
-
const text = extractTextFromContent(event.message.content);
|
|
856
|
-
if (text) writeOutputText(text);
|
|
857
|
-
|
|
858
|
-
if (childWatchdogConfig && event.type === "message_end") {
|
|
859
|
-
const next = applyChildWatchdogMessage(childWatchdogState, event.message);
|
|
860
|
-
if (next) updateChildWatchdogState(next);
|
|
861
|
-
}
|
|
862
|
-
if (event.type !== "message_end" || event.message.role !== "assistant") return;
|
|
863
|
-
const hasToolCall = assistantStartsToolCall(event.message);
|
|
864
|
-
if (event.message.model) {
|
|
865
|
-
model = event.message.model;
|
|
866
|
-
if (expectedModelForVerification && !hasToolCall) {
|
|
867
|
-
const modelVerificationError = formatSubagentModelVerificationError(expectedModelForVerification, event.message.model, modelVerificationRegistry);
|
|
868
|
-
if (modelVerificationError && !error) error = modelVerificationError;
|
|
869
|
-
}
|
|
870
|
-
}
|
|
871
|
-
if (event.message.errorMessage) assistantError = event.message.errorMessage;
|
|
872
|
-
const eventUsage = event.message.usage;
|
|
873
|
-
if (eventUsage) {
|
|
874
|
-
usage.turns++;
|
|
875
|
-
usage.input += eventUsage.input ?? eventUsage.inputTokens ?? 0;
|
|
876
|
-
usage.output += eventUsage.output ?? eventUsage.outputTokens ?? 0;
|
|
877
|
-
usage.cacheRead += eventUsage.cacheRead ?? 0;
|
|
878
|
-
usage.cacheWrite += eventUsage.cacheWrite ?? 0;
|
|
879
|
-
usage.cost += eventUsage.cost?.total ?? 0;
|
|
880
|
-
}
|
|
881
|
-
if (isTerminalAssistantStop(event.message)) {
|
|
882
|
-
if (!event.message.errorMessage && extractTextFromContent(event.message.content).trim()) assistantError = undefined;
|
|
883
|
-
cleanTerminalAssistantStopReceived ||= !event.message.errorMessage;
|
|
884
|
-
clearAllToolTimeouts();
|
|
885
|
-
activeToolCalls.clear();
|
|
886
|
-
activeToolKeysByName.clear();
|
|
887
|
-
refreshCurrentTool();
|
|
888
|
-
applyChildLifecycle(projectChildLifecycle(event, true, childLifecycleState));
|
|
889
|
-
}
|
|
890
|
-
}
|
|
891
|
-
};
|
|
892
|
-
|
|
893
|
-
// Guard both cases that can leave the parent waiting on `close` forever:
|
|
894
|
-
// a lingering stdio holder after `exit`, or a child that never exits.
|
|
895
|
-
const FINAL_STOP_GRACE_MS = 1000;
|
|
896
|
-
const HARD_KILL_MS = 3000;
|
|
897
|
-
let childExited = false;
|
|
898
|
-
let forcedTerminationSignal = false;
|
|
899
|
-
let cleanTerminalAssistantStopReceived = false;
|
|
900
|
-
let agentSettledReceived = false;
|
|
901
|
-
let compactionStartedReceived = false;
|
|
902
|
-
let afterCompactionSettlement = false;
|
|
903
|
-
let finalDrainTimer: NodeJS.Timeout | undefined;
|
|
904
|
-
let finalHardKillTimer: NodeJS.Timeout | undefined;
|
|
905
|
-
let watchdogTailTimer: NodeJS.Timeout | undefined;
|
|
906
|
-
let protocolHardKillTimer: NodeJS.Timeout | undefined;
|
|
907
|
-
let protocolError: ProtocolOutputLimit | undefined;
|
|
908
|
-
let settled = false;
|
|
909
|
-
applyChildLifecycle = (action: ChildLifecycleAction): void => {
|
|
910
|
-
if (action === "cancel-drain") {
|
|
911
|
-
if (finalDrainTimer) {
|
|
912
|
-
clearTimeout(finalDrainTimer);
|
|
913
|
-
finalDrainTimer = undefined;
|
|
914
|
-
}
|
|
915
|
-
if (finalHardKillTimer) {
|
|
916
|
-
clearTimeout(finalHardKillTimer);
|
|
917
|
-
finalHardKillTimer = undefined;
|
|
918
|
-
}
|
|
919
|
-
clearWatchdogTailTimer();
|
|
920
|
-
return;
|
|
921
|
-
}
|
|
922
|
-
if (action === "start-drain") startFinalDrain();
|
|
923
|
-
};
|
|
924
|
-
const failProtocol = (limit: ProtocolOutputLimit): void => {
|
|
925
|
-
if (protocolError) return;
|
|
926
|
-
protocolError = limit;
|
|
927
|
-
error = formatProtocolOutputLimit(limit);
|
|
928
|
-
if (!childExited) {
|
|
929
|
-
trySignalChild(child, "SIGTERM");
|
|
930
|
-
protocolHardKillTimer = setTimeout(() => {
|
|
931
|
-
if (!settled) trySignalChild(child, "SIGKILL");
|
|
932
|
-
}, 3000);
|
|
933
|
-
protocolHardKillTimer.unref?.();
|
|
934
|
-
}
|
|
935
|
-
};
|
|
936
|
-
const stdoutReader = createBoundedLineReader({
|
|
937
|
-
oversizedLineProjector: PI_AGGREGATE_EVENT_PROJECTOR,
|
|
938
|
-
onLine: processStdoutLine,
|
|
939
|
-
onLimit: failProtocol,
|
|
940
|
-
});
|
|
941
|
-
const stderrReader = createBoundedLineReader({
|
|
942
|
-
stream: "stderr",
|
|
943
|
-
maxPendingLineBytes: MAX_CHILD_STDERR_BYTES,
|
|
944
|
-
onLine: (line) => appendChildLine("subagent.child.stderr", line),
|
|
945
|
-
onLimit: (limit) => appendChildLine("subagent.child.stderr", formatProtocolOutputLimit(limit)),
|
|
946
|
-
});
|
|
947
|
-
const clearStdioGuard = attachPostExitStdioGuard(child, { idleMs: 2000, hardMs: 8000 });
|
|
948
|
-
child.stdout.on("data", (chunk: Buffer) => stdoutReader.push(chunk));
|
|
949
|
-
child.stderr.on("data", (chunk: Buffer) => {
|
|
950
|
-
stderrTail.push(chunk);
|
|
951
|
-
stderrReader.push(chunk);
|
|
952
|
-
outputStream.write(chunk);
|
|
953
|
-
orcaProgressTab?.append(chunk.toString("utf-8"));
|
|
954
|
-
});
|
|
955
|
-
registerInterrupt?.(() => {
|
|
956
|
-
if (settled || timedOut || stopped) return;
|
|
957
|
-
interrupted = true;
|
|
958
|
-
if (!error) error = "Interrupted. Waiting for explicit next action.";
|
|
959
|
-
trySignalChild(child, "SIGINT");
|
|
960
|
-
setTimeout(() => {
|
|
961
|
-
if (!settled && !timedOut && !stopped) trySignalChild(child, "SIGTERM");
|
|
962
|
-
}, 1000).unref?.();
|
|
963
|
-
});
|
|
964
|
-
const terminateForTimeout = (message: string): void => {
|
|
965
|
-
if (settled || timedOut || stopped) return;
|
|
966
|
-
timedOut = true;
|
|
967
|
-
// runPiStreaming's terminal result derives the timeout error from this
|
|
968
|
-
// message, so retain the tool-specific reason through finalization.
|
|
969
|
-
timeoutMessage = message;
|
|
970
|
-
interrupted = false;
|
|
971
|
-
error = message;
|
|
972
|
-
if (processTreeController) void processTreeController.terminate();
|
|
973
|
-
else trySignalChild(child, "SIGTERM");
|
|
974
|
-
};
|
|
975
|
-
let toolTimeoutSequence = 0;
|
|
976
|
-
const activeToolTimeouts = new Map<string, { toolName: string; timer: ReturnType<typeof setTimeout> }>();
|
|
977
|
-
const activeToolTimeoutKeysByName = new Map<string, string[]>();
|
|
978
|
-
const removeToolTimeoutKey = (key: string): void => {
|
|
979
|
-
const active = activeToolTimeouts.get(key);
|
|
980
|
-
if (!active) return;
|
|
981
|
-
clearTimeout(active.timer);
|
|
982
|
-
activeToolTimeouts.delete(key);
|
|
983
|
-
const keys = activeToolTimeoutKeysByName.get(active.toolName)?.filter((candidate) => candidate !== key) ?? [];
|
|
984
|
-
if (keys.length > 0) activeToolTimeoutKeysByName.set(active.toolName, keys);
|
|
985
|
-
else activeToolTimeoutKeysByName.delete(active.toolName);
|
|
986
|
-
};
|
|
987
|
-
const clearActiveToolTimeout = (event: { toolCallId?: unknown; toolName?: unknown }): void => {
|
|
988
|
-
const key = typeof event.toolCallId === "string" && event.toolCallId.length > 0
|
|
989
|
-
? `id:${event.toolCallId}`
|
|
990
|
-
: typeof event.toolName === "string"
|
|
991
|
-
? activeToolTimeoutKeysByName.get(event.toolName)?.[0]
|
|
992
|
-
: activeToolTimeouts.size === 1
|
|
993
|
-
? [...activeToolTimeouts.keys()][0]
|
|
994
|
-
: undefined;
|
|
995
|
-
if (key) removeToolTimeoutKey(key);
|
|
996
|
-
};
|
|
997
|
-
const clearAllToolTimeouts = (): void => {
|
|
998
|
-
for (const key of [...activeToolTimeouts.keys()]) removeToolTimeoutKey(key);
|
|
999
|
-
};
|
|
1000
|
-
const armToolTimeout = (event: { toolCallId?: unknown; toolName: string }): void => {
|
|
1001
|
-
const timeoutForTool = effectiveToolTimeoutMs(event.toolName, toolTimeoutMs);
|
|
1002
|
-
if (timeoutForTool === undefined) return;
|
|
1003
|
-
const runRemaining = runDeadlineAt === undefined ? undefined : Math.max(0, runDeadlineAt - Date.now());
|
|
1004
|
-
if (runRemaining !== undefined && timeoutForTool >= runRemaining) return;
|
|
1005
|
-
const key = toolTimeoutCallKey(event, ++toolTimeoutSequence);
|
|
1006
|
-
const toolName = event.toolName;
|
|
1007
|
-
const timer = setTimeout(() => {
|
|
1008
|
-
removeToolTimeoutKey(key);
|
|
1009
|
-
terminateForTimeout(formatToolTimeoutMessage(toolName, timeoutForTool));
|
|
1010
|
-
}, timeoutForTool);
|
|
1011
|
-
timer.unref?.();
|
|
1012
|
-
activeToolTimeouts.set(key, { toolName, timer });
|
|
1013
|
-
const keys = activeToolTimeoutKeysByName.get(toolName) ?? [];
|
|
1014
|
-
keys.push(key);
|
|
1015
|
-
activeToolTimeoutKeysByName.set(toolName, keys);
|
|
1016
|
-
};
|
|
1017
|
-
registerTimeout?.(() => terminateForTimeout(timeoutMessage ?? "Subagent timed out."));
|
|
1018
|
-
registerStop?.(() => {
|
|
1019
|
-
if (settled || timedOut || stopped) return;
|
|
1020
|
-
stopped = true;
|
|
1021
|
-
interrupted = false;
|
|
1022
|
-
error = stopMessage ?? "Subagent stopped by user.";
|
|
1023
|
-
if (processTreeController) void processTreeController.terminate();
|
|
1024
|
-
else trySignalChild(child, "SIGTERM");
|
|
1025
|
-
});
|
|
1026
|
-
const clearDrainTimers = () => {
|
|
1027
|
-
clearAllToolTimeouts();
|
|
1028
|
-
if (finalDrainTimer) {
|
|
1029
|
-
clearTimeout(finalDrainTimer);
|
|
1030
|
-
finalDrainTimer = undefined;
|
|
1031
|
-
}
|
|
1032
|
-
if (finalHardKillTimer) {
|
|
1033
|
-
clearTimeout(finalHardKillTimer);
|
|
1034
|
-
finalHardKillTimer = undefined;
|
|
1035
|
-
}
|
|
1036
|
-
clearWatchdogTailTimer();
|
|
1037
|
-
if (protocolHardKillTimer) {
|
|
1038
|
-
clearTimeout(protocolHardKillTimer);
|
|
1039
|
-
protocolHardKillTimer = undefined;
|
|
1040
|
-
}
|
|
1041
|
-
};
|
|
1042
|
-
function startFinalDrain(): void {
|
|
1043
|
-
if (childWatchdogIsActive(childWatchdogState)) {
|
|
1044
|
-
armWatchdogTail();
|
|
1045
|
-
return;
|
|
1046
|
-
}
|
|
1047
|
-
if (childExited || finalDrainTimer || settled) return;
|
|
1048
|
-
finalDrainTimer = setTimeout(() => {
|
|
1049
|
-
if (settled) return;
|
|
1050
|
-
const termSent = trySignalChild(child, "SIGTERM");
|
|
1051
|
-
if (!termSent) return;
|
|
1052
|
-
forcedTerminationSignal = true;
|
|
1053
|
-
if (!cleanTerminalAssistantStopReceived && !agentSettledReceived && !error && !assistantError) {
|
|
1054
|
-
error = `Subagent process did not exit within ${FINAL_STOP_GRACE_MS}ms after its terminal event. Forcing termination.`;
|
|
1055
|
-
}
|
|
1056
|
-
finalHardKillTimer = setTimeout(() => {
|
|
1057
|
-
if (settled) return;
|
|
1058
|
-
forcedTerminationSignal = trySignalChild(child, "SIGKILL") || forcedTerminationSignal;
|
|
1059
|
-
}, HARD_KILL_MS);
|
|
1060
|
-
finalHardKillTimer.unref?.();
|
|
1061
|
-
}, FINAL_STOP_GRACE_MS);
|
|
1062
|
-
finalDrainTimer.unref?.();
|
|
1063
|
-
}
|
|
1064
|
-
function clearWatchdogTailTimer(): void {
|
|
1065
|
-
if (watchdogTailTimer) {
|
|
1066
|
-
clearTimeout(watchdogTailTimer);
|
|
1067
|
-
watchdogTailTimer = undefined;
|
|
1068
|
-
}
|
|
1069
|
-
}
|
|
1070
|
-
function armWatchdogTail(): void {
|
|
1071
|
-
if ((!cleanTerminalAssistantStopReceived && !agentSettledReceived) || watchdogTailTimer || settled) return;
|
|
1072
|
-
watchdogTailTimer = setTimeout(() => {
|
|
1073
|
-
watchdogTailTimer = undefined;
|
|
1074
|
-
updateChildWatchdogState({
|
|
1075
|
-
phase: "stale",
|
|
1076
|
-
seq: (childWatchdogState?.seq ?? 0) + 1,
|
|
1077
|
-
lastUpdate: Date.now(),
|
|
1078
|
-
reason: "child watchdog tail timeout",
|
|
1079
|
-
timedOut: true,
|
|
1080
|
-
});
|
|
1081
|
-
startFinalDrain();
|
|
1082
|
-
}, childWatchdogConfig?.watchdogTailTimeoutMs ?? 120_000);
|
|
1083
|
-
watchdogTailTimer.unref?.();
|
|
1084
|
-
}
|
|
1085
|
-
child.on("exit", () => {
|
|
1086
|
-
childExited = true;
|
|
1087
|
-
clearDrainTimers();
|
|
1088
|
-
});
|
|
1089
|
-
child.on("close", async (exitCode, signal) => {
|
|
1090
|
-
settled = true;
|
|
1091
|
-
const processCloseObservedAt = Date.now();
|
|
1092
|
-
const processTree = processTreeController
|
|
1093
|
-
? await processTreeController.finishAfterWriterClose()
|
|
1094
|
-
: { state: "unknown" as const, reason: "verification-failed" as const, diagnostic: "Writer PID was unavailable." };
|
|
1095
|
-
try {
|
|
1096
|
-
onWriterProcess?.({ state: "none" });
|
|
1097
|
-
} catch {
|
|
1098
|
-
// The runner still owns and releases the lease during finalization.
|
|
1099
|
-
}
|
|
1100
|
-
registerInterrupt?.(undefined);
|
|
1101
|
-
registerTimeout?.(undefined);
|
|
1102
|
-
registerStop?.(undefined);
|
|
1103
|
-
clearDrainTimers();
|
|
1104
|
-
clearStdioGuard();
|
|
1105
|
-
stdoutReader.end();
|
|
1106
|
-
stderrReader.end();
|
|
1107
|
-
outputStream.end();
|
|
1108
|
-
const stderr = stderrTail.text();
|
|
1109
|
-
const finalOutput = getFinalOutput(messages) || rawStdoutTail.text().trim();
|
|
1110
|
-
const finalError = error ?? assistantError;
|
|
1111
|
-
const forcedDrainAfterFinalSuccess = Boolean(forcedTerminationSignal || signal) && (cleanTerminalAssistantStopReceived || agentSettledReceived) && !finalError;
|
|
1112
|
-
const forcedDrainAfterEmptyTerminal = forcedDrainAfterFinalSuccess && hasEmptyTerminalAssistantResponse(messages);
|
|
1113
|
-
const forcedDrainError = forcedDrainAfterEmptyTerminal && stderr.trim()
|
|
1114
|
-
? stderr.trim()
|
|
1115
|
-
: undefined;
|
|
1116
|
-
const signalError = isUnexplainedProcessSignal({
|
|
1117
|
-
processSignal: signal,
|
|
1118
|
-
interrupted,
|
|
1119
|
-
timedOut,
|
|
1120
|
-
stopped,
|
|
1121
|
-
forcedDrainAfterFinalSuccess: forcedDrainAfterFinalSuccess && !forcedDrainAfterEmptyTerminal,
|
|
1122
|
-
}) ? formatProcessSignalError(signal!) : undefined;
|
|
1123
|
-
resolve(omitUndefinedProperties({
|
|
1124
|
-
stderr,
|
|
1125
|
-
exitCode: timedOut || stopped ? 1 : interrupted || (forcedDrainAfterFinalSuccess && !forcedDrainAfterEmptyTerminal) ? 0 : forcedTerminationSignal || signal ? (exitCode ?? 1) : exitCode,
|
|
1126
|
-
messages,
|
|
1127
|
-
usage,
|
|
1128
|
-
toolCount,
|
|
1129
|
-
durationMs: Date.now() - startedAt,
|
|
1130
|
-
model,
|
|
1131
|
-
error: stopped ? (stopMessage ?? "Subagent stopped by user.") : timedOut ? (timeoutMessage ?? "Subagent timed out.") : interrupted || (forcedDrainAfterFinalSuccess && !forcedDrainAfterEmptyTerminal) ? undefined : finalError ?? forcedDrainError ?? signalError,
|
|
1132
|
-
protocolError,
|
|
1133
|
-
finalOutput: (timedOut || stopped) && !finalOutput.trim() ? (stopped ? stopMessage ?? "Subagent stopped by user." : timeoutMessage ?? "Subagent timed out.") : finalOutput,
|
|
1134
|
-
outputState: finalOutput.trim() ? "present" : "absent",
|
|
1135
|
-
interrupted,
|
|
1136
|
-
timedOut,
|
|
1137
|
-
stopped,
|
|
1138
|
-
observedMutationAttempt,
|
|
1139
|
-
structuredOutputToolInvoked,
|
|
1140
|
-
structuredOutputMessageStartIndex,
|
|
1141
|
-
watchdog: childWatchdogState,
|
|
1142
|
-
processInstanceId,
|
|
1143
|
-
processCloseObservedAt,
|
|
1144
|
-
processSignal: signal,
|
|
1145
|
-
processTree,
|
|
1146
|
-
currentTool,
|
|
1147
|
-
currentToolArgs,
|
|
1148
|
-
currentPath,
|
|
1149
|
-
afterCompactionSettlement: afterCompactionSettlement || undefined,
|
|
1150
|
-
}));
|
|
1151
|
-
});
|
|
1152
|
-
|
|
1153
|
-
child.on("error", (spawnError) => {
|
|
1154
|
-
settled = true;
|
|
1155
|
-
try {
|
|
1156
|
-
onWriterProcess?.({ state: "none" });
|
|
1157
|
-
} catch {
|
|
1158
|
-
// The runner still owns and releases the lease during finalization.
|
|
1159
|
-
}
|
|
1160
|
-
registerInterrupt?.(undefined);
|
|
1161
|
-
registerTimeout?.(undefined);
|
|
1162
|
-
registerStop?.(undefined);
|
|
1163
|
-
clearDrainTimers();
|
|
1164
|
-
clearStdioGuard();
|
|
1165
|
-
stdoutReader.end();
|
|
1166
|
-
stderrReader.end();
|
|
1167
|
-
outputStream.end();
|
|
1168
|
-
const stderr = stderrTail.text();
|
|
1169
|
-
const finalOutput = getFinalOutput(messages) || rawStdoutTail.text().trim();
|
|
1170
|
-
const spawnErrorMessage = spawnError instanceof Error ? spawnError.message : String(spawnError);
|
|
1171
|
-
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.") : 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, observedMutationAttempt, structuredOutputToolInvoked, structuredOutputMessageStartIndex, watchdog: childWatchdogState, processInstanceId, processTree: { state: "unknown", reason: "verification-failed", diagnostic: spawnErrorMessage } }));
|
|
1172
|
-
});
|
|
1173
|
-
});
|
|
1174
|
-
}
|
|
1175
|
-
|
|
1176
545
|
function resolvePiPackageRootFallback(): string {
|
|
1177
546
|
const root = resolveInstalledPiPackageRoot();
|
|
1178
547
|
if (root) return root;
|
|
@@ -1294,15 +663,17 @@ interface SingleStepContext {
|
|
|
1294
663
|
flatIndex: number;
|
|
1295
664
|
flatStepCount: number;
|
|
1296
665
|
outputFile: string;
|
|
1297
|
-
steerInboxDir?: string;
|
|
1298
|
-
steerCapabilityPath?: string;
|
|
1299
|
-
steerAckDir?: string;
|
|
1300
666
|
transcriptPath?: string;
|
|
1301
667
|
piPackageRoot?: string;
|
|
1302
|
-
|
|
668
|
+
/** Factory the runner creates this step's child session through. */
|
|
669
|
+
childSessions: ChildSessionFactory;
|
|
670
|
+
/** The launching executor's own child runtime; nested route, depth, and ceilings come from here. */
|
|
671
|
+
inheritedChildRuntime?: InheritedChildRuntime;
|
|
1303
672
|
registerInterrupt?: (interrupt: (() => void) | undefined) => void;
|
|
1304
673
|
registerTimeout?: (interrupt: (() => void) | undefined) => void;
|
|
1305
674
|
registerStop?: (stop: (() => void) | undefined) => void;
|
|
675
|
+
/** Receives the live child's steer handler while its session runs. */
|
|
676
|
+
registerSteer?: (steer: StepSteerHandler | undefined) => void;
|
|
1306
677
|
timeoutSignal?: AbortSignal;
|
|
1307
678
|
stopSignal?: AbortSignal;
|
|
1308
679
|
timeoutMessage?: string;
|
|
@@ -1318,7 +689,6 @@ interface SingleStepContext {
|
|
|
1318
689
|
runFanoutBudget?: RunFanoutBudgetDescriptor;
|
|
1319
690
|
onAttemptStart?: (attempt: { model?: string; thinking?: string; contextLimit?: number }) => void;
|
|
1320
691
|
onChildEvent?: (event: ChildEvent) => void;
|
|
1321
|
-
onWriterProcess?: (writer: { state: "none" | "spawning" } | { state: "running"; pid: number }) => void;
|
|
1322
692
|
onExternalProcess?: (process: ExternalProcessStatus) => void;
|
|
1323
693
|
onExternalJob?: (status: ExternalJobStatus) => void;
|
|
1324
694
|
skipAcceptance?: () => boolean;
|
|
@@ -1424,13 +794,11 @@ async function runSingleStepInner(
|
|
|
1424
794
|
model: step.model,
|
|
1425
795
|
modelCandidates: step.modelCandidates,
|
|
1426
796
|
mcpDirectTools: step.mcpDirectTools,
|
|
1427
|
-
mcpConfig: step.mcpConfig,
|
|
1428
|
-
runtimeServerNames: step.runtimeServerNames,
|
|
1429
797
|
cwd: step.cwd ?? ctx.cwd,
|
|
1430
798
|
requireReadTool: Boolean(step.skills?.length),
|
|
1431
799
|
structuredOutput: Boolean(effectiveStructuredOutput),
|
|
1432
800
|
capabilityCeiling: step.capabilityCeiling ?? ctx.capabilityCeiling,
|
|
1433
|
-
inheritedCapabilityCeiling:
|
|
801
|
+
inheritedCapabilityCeiling: ctx.inheritedChildRuntime?.capabilityCeiling,
|
|
1434
802
|
permissionRules: step.permissionRules,
|
|
1435
803
|
}));
|
|
1436
804
|
const contractTools = resolvedTaskToolPlan.explicitToolAllowlist ? resolvedTaskToolPlan.effectiveToolAllowlist : undefined;
|
|
@@ -1650,12 +1018,10 @@ async function runSingleStepInner(
|
|
|
1650
1018
|
let capabilityAudit: import("../shared/capability-ceiling.ts").SubagentCapabilityAudit | undefined;
|
|
1651
1019
|
let launchResolvedExtensions = step.launchResolvedExtensions;
|
|
1652
1020
|
const modelAttempts: ModelAttempt[] = [];
|
|
1653
|
-
const writerProcesses: PiWriterProcessInstanceExitV1[] = [];
|
|
1654
|
-
let writerAttemptCount = 0;
|
|
1655
1021
|
const attemptNotes: string[] = [];
|
|
1656
1022
|
let finalRequiredOutputMissing: boolean | undefined;
|
|
1657
1023
|
const eventsPath = path.join(path.dirname(ctx.outputFile), "events.jsonl");
|
|
1658
|
-
let finalResult:
|
|
1024
|
+
let finalResult: RunChildSessionResult | undefined;
|
|
1659
1025
|
let finalOutputSnapshot: SingleOutputSnapshot | undefined;
|
|
1660
1026
|
let structuredAcceptanceReport: unknown;
|
|
1661
1027
|
let structuredAcceptanceReportError: string | undefined;
|
|
@@ -1667,10 +1033,6 @@ async function runSingleStepInner(
|
|
|
1667
1033
|
let finalMutationEvidence = collectTrackedMutationEvidence(mutationSnapshot, step.cwd ?? ctx.cwd);
|
|
1668
1034
|
|
|
1669
1035
|
let modelIndex = 0;
|
|
1670
|
-
let startupAttemptIndex = 0;
|
|
1671
|
-
// Escalated to "file" after an unexplained zero-activity startup failure so
|
|
1672
|
-
// retries keep the task text out of argv (endpoint pre-exec scans may deny it).
|
|
1673
|
-
let taskDeliveryOverride: SubagentTaskDelivery | undefined;
|
|
1674
1036
|
let contextOverflow = false;
|
|
1675
1037
|
let launchWarningsEmitted = false;
|
|
1676
1038
|
let abortRecoveryAttempted = false;
|
|
@@ -1682,7 +1044,7 @@ async function runSingleStepInner(
|
|
|
1682
1044
|
const candidate = candidates[modelIndex];
|
|
1683
1045
|
const expectedModelForVerification = candidate && !(step.skipPrimaryModelVerification && modelIndex === 0) ? candidate : undefined;
|
|
1684
1046
|
try {
|
|
1685
|
-
assertThinkingWithinCeiling({ model: candidate, configThinking: step.thinking, ceiling: step.thinkingCeiling
|
|
1047
|
+
assertThinkingWithinCeiling({ model: candidate, configThinking: step.thinking, ceiling: step.thinkingCeiling, agent: step.agent, runId: ctx.id });
|
|
1686
1048
|
} catch (error) {
|
|
1687
1049
|
const message = error instanceof Error ? error.message : String(error);
|
|
1688
1050
|
return omitUndefinedProperties({ agent: step.agent, output: message, error: message, exitCode: 1, context: step.context, thinkingCeiling: step.thinkingCeiling });
|
|
@@ -1709,12 +1071,10 @@ async function runSingleStepInner(
|
|
|
1709
1071
|
childIndex: ctx.flatIndex,
|
|
1710
1072
|
})
|
|
1711
1073
|
: undefined;
|
|
1712
|
-
|
|
1074
|
+
let watchdogSink: ((event: ChildWatchdogStatusEvent) => void) | undefined;
|
|
1075
|
+
const launch = buildInProcessChildLaunch(omitUndefinedProperties({
|
|
1713
1076
|
parentSessionId: step.parentSessionId,
|
|
1714
1077
|
forkCacheKey: step.context === "fork" ? deriveForkPromptCacheKey(step.parentSessionId) : undefined,
|
|
1715
|
-
baseArgs: ["--mode", "json", "-p"],
|
|
1716
|
-
task: attemptTask,
|
|
1717
|
-
taskDelivery: taskDeliveryOverride,
|
|
1718
1078
|
sessionEnabled,
|
|
1719
1079
|
sessionDir,
|
|
1720
1080
|
sessionFile: step.sessionFile,
|
|
@@ -1733,28 +1093,20 @@ async function runSingleStepInner(
|
|
|
1733
1093
|
systemPrompt: step.systemPrompt ?? "",
|
|
1734
1094
|
systemPromptMode: step.systemPromptMode,
|
|
1735
1095
|
mcpDirectTools: step.mcpDirectTools,
|
|
1736
|
-
|
|
1737
|
-
runtimeServerNames: step.runtimeServerNames,
|
|
1096
|
+
extensionBindings,
|
|
1738
1097
|
capabilityCeiling: step.capabilityCeiling ?? ctx.capabilityCeiling,
|
|
1739
1098
|
cwd: step.cwd ?? ctx.cwd,
|
|
1740
|
-
promptFileStem: step.agent,
|
|
1741
1099
|
intercomSessionName: ctx.childIntercomTarget,
|
|
1742
1100
|
sessionName: childSessionName,
|
|
1743
1101
|
orchestratorIntercomTarget: ctx.orchestratorIntercomTarget,
|
|
1744
1102
|
runId: ctx.id,
|
|
1745
1103
|
childAgentName: step.agent,
|
|
1746
1104
|
childIndex: ctx.flatIndex,
|
|
1747
|
-
|
|
1748
|
-
parentControlInbox: ctx.nestedRoute?.controlInbox,
|
|
1749
|
-
parentRootRunId: ctx.nestedRoute?.rootRunId,
|
|
1750
|
-
parentCapabilityToken: ctx.nestedRoute?.capabilityToken,
|
|
1105
|
+
nestedRoute: ctx.nestedRoute,
|
|
1751
1106
|
runFanoutBudget: ctx.runFanoutBudget ? {
|
|
1752
1107
|
...ctx.runFanoutBudget,
|
|
1753
1108
|
...(step.runFanoutPath ? { parentPath: `${ctx.runFanoutBudget.parentPath ? `${ctx.runFanoutBudget.parentPath}/` : ""}${step.runFanoutPath}` } : {}),
|
|
1754
1109
|
} : undefined,
|
|
1755
|
-
steerInboxDir: ctx.steerInboxDir,
|
|
1756
|
-
steerCapabilityPath: ctx.steerCapabilityPath,
|
|
1757
|
-
steerAckDir: ctx.steerAckDir,
|
|
1758
1110
|
structuredOutput: effectiveStructuredOutput,
|
|
1759
1111
|
toolBudget: step.toolBudget,
|
|
1760
1112
|
permissionRules: step.permissionRules,
|
|
@@ -1762,11 +1114,19 @@ async function runSingleStepInner(
|
|
|
1762
1114
|
? path.join(ctx.artifactsDir, "permission-audit", `${ctx.id}-${ctx.flatIndex}.jsonl`)
|
|
1763
1115
|
: undefined,
|
|
1764
1116
|
childWatchdog,
|
|
1117
|
+
watchdogStatus: (event) => watchdogSink?.(event),
|
|
1765
1118
|
waitToolEnabled: step.waitToolEnabled,
|
|
1766
1119
|
waitToolDefaultTimeoutMs: step.waitToolDefaultTimeoutMs,
|
|
1767
1120
|
thinkingCeiling: step.thinkingCeiling,
|
|
1768
|
-
|
|
1121
|
+
maxSubagentDepth: step.maxSubagentDepth,
|
|
1122
|
+
inherited: ctx.inheritedChildRuntime,
|
|
1123
|
+
host: "runner",
|
|
1769
1124
|
}));
|
|
1125
|
+
if (effectiveStructuredOutput && launch.config.structuredOutput) {
|
|
1126
|
+
// The runner reads the value back from the runtime's files after the run.
|
|
1127
|
+
launch.config.structuredOutput.capture = createStructuredOutputFileCapture(effectiveStructuredOutput);
|
|
1128
|
+
}
|
|
1129
|
+
const { warnings, capabilityAudit: attemptCapabilityAudit } = launch;
|
|
1770
1130
|
if (!launchWarningsEmitted && warnings.length > 0) {
|
|
1771
1131
|
for (const warning of warnings) console.warn(`[pi-subagents] ${warning}`);
|
|
1772
1132
|
launchWarningsEmitted = true;
|
|
@@ -1782,13 +1142,11 @@ async function runSingleStepInner(
|
|
|
1782
1142
|
model: step.model,
|
|
1783
1143
|
modelCandidates: step.modelCandidates,
|
|
1784
1144
|
mcpDirectTools: step.mcpDirectTools,
|
|
1785
|
-
mcpConfig: step.mcpConfig,
|
|
1786
|
-
runtimeServerNames: step.runtimeServerNames,
|
|
1787
1145
|
cwd: step.cwd ?? ctx.cwd,
|
|
1788
1146
|
requireReadTool: Boolean(step.skills?.length),
|
|
1789
1147
|
structuredOutput: Boolean(effectiveStructuredOutput),
|
|
1790
1148
|
capabilityCeiling: step.capabilityCeiling ?? ctx.capabilityCeiling,
|
|
1791
|
-
inheritedCapabilityCeiling:
|
|
1149
|
+
inheritedCapabilityCeiling: ctx.inheritedChildRuntime?.capabilityCeiling,
|
|
1792
1150
|
permissionRules: step.permissionRules,
|
|
1793
1151
|
}));
|
|
1794
1152
|
launchResolvedExtensions = projectLaunchResolvedChildExtensions(toolPlan);
|
|
@@ -1817,59 +1175,50 @@ async function runSingleStepInner(
|
|
|
1817
1175
|
}));
|
|
1818
1176
|
}
|
|
1819
1177
|
capabilityAudit = attemptCapabilityAudit;
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
ctx.
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
step.
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1178
|
+
// Each attempt rewrites the step output log; synchronous appends keep a
|
|
1179
|
+
// retried attempt from interleaving with the previous attempt's flush.
|
|
1180
|
+
fs.writeFileSync(ctx.outputFile, "", "utf-8");
|
|
1181
|
+
const run = await runChildSession(omitUndefinedProperties({
|
|
1182
|
+
factory: ctx.childSessions,
|
|
1183
|
+
launch,
|
|
1184
|
+
prompt: `Task: ${attemptTask}`,
|
|
1185
|
+
childWatchdog,
|
|
1186
|
+
childEventContext: { runId: ctx.id, stepIndex: ctx.flatIndex, agent: step.agent },
|
|
1187
|
+
appendChildEvent: (event) => appendDiagnosticJsonl(eventsPath, JSON.stringify(event), typeof event.type === "string" ? event.type : undefined),
|
|
1188
|
+
writeOutputLine: (line) => {
|
|
1189
|
+
try {
|
|
1190
|
+
fs.appendFileSync(ctx.outputFile, `${line}\n`, "utf-8");
|
|
1191
|
+
} catch {
|
|
1192
|
+
// The output log is observability only.
|
|
1193
|
+
}
|
|
1194
|
+
ctx.orcaProgressTab?.append(`${line}\n`);
|
|
1195
|
+
},
|
|
1196
|
+
registerInterrupt: ctx.registerInterrupt,
|
|
1197
|
+
registerTimeout: ctx.registerTimeout,
|
|
1198
|
+
registerStop: ctx.registerStop,
|
|
1199
|
+
registerSteer: ctx.registerSteer,
|
|
1200
|
+
registerWatchdogStatus: (sink) => { watchdogSink = sink; },
|
|
1201
|
+
timeoutMessage: ctx.timeoutMessage,
|
|
1202
|
+
stopMessage: ctx.stopMessage,
|
|
1203
|
+
onChildEvent: ctx.onChildEvent,
|
|
1832
1204
|
transcriptWriter,
|
|
1833
|
-
ctx.
|
|
1834
|
-
ctx.
|
|
1835
|
-
ctx.registerStop,
|
|
1836
|
-
ctx.stopMessage,
|
|
1837
|
-
ctx.onWriterProcess,
|
|
1838
|
-
ctx.toolTimeoutMs,
|
|
1839
|
-
ctx.deadlineAt,
|
|
1840
|
-
ctx.orcaProgressTab,
|
|
1205
|
+
toolTimeoutMs: ctx.toolTimeoutMs,
|
|
1206
|
+
runDeadlineAt: ctx.deadlineAt,
|
|
1841
1207
|
expectedModelForVerification,
|
|
1842
|
-
step.modelVerificationRegistry,
|
|
1843
|
-
step.
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
attempt: writerAttemptCount - 1,
|
|
1850
|
-
closeObservedAt: run.processCloseObservedAt,
|
|
1851
|
-
exitCode: run.exitCode,
|
|
1852
|
-
signal: run.processSignal ?? null,
|
|
1853
|
-
processTree: run.processTree,
|
|
1854
|
-
});
|
|
1855
|
-
}
|
|
1856
|
-
const toolAvailabilityError = run.exitCode === 0 && !run.error
|
|
1857
|
-
? readChildToolDiagnosticError(toolDiagnosticPath)
|
|
1858
|
-
: undefined;
|
|
1859
|
-
const runtimeAcknowledgedExtensions = readRuntimeAcknowledgedExtensions(runtimeAcknowledgedExtensionsPath);
|
|
1860
|
-
cleanupTempDir(tempDir);
|
|
1208
|
+
modelVerificationRegistry: step.modelVerificationRegistry,
|
|
1209
|
+
modelResponseAliases: step.modelResponseAliases,
|
|
1210
|
+
mutationTools: step.mutationTools,
|
|
1211
|
+
}));
|
|
1212
|
+
const toolDiagnostic = run.exitCode === 0 && !run.error ? launch.capture.toolDiagnostic() : undefined;
|
|
1213
|
+
const toolAvailabilityError = toolDiagnostic ? formatChildToolDiagnostic(toolDiagnostic) : undefined;
|
|
1214
|
+
const runtimeAcknowledgedExtensions = launch.capture.runtimeAcknowledgedExtensions();
|
|
1861
1215
|
const midToolExitError = run.currentTool
|
|
1862
1216
|
&& isOrdinaryToolForMidToolExit(run.currentTool)
|
|
1863
1217
|
&& !run.interrupted
|
|
1864
1218
|
&& !run.timedOut
|
|
1865
1219
|
&& !run.stopped
|
|
1866
|
-
&& !run.protocolError
|
|
1867
1220
|
&& !toolAvailabilityError
|
|
1868
|
-
? formatMidToolExitError({
|
|
1869
|
-
toolName: run.currentTool,
|
|
1870
|
-
exitCode: run.exitCode,
|
|
1871
|
-
processSignal: run.processSignal,
|
|
1872
|
-
})
|
|
1221
|
+
? formatMidToolExitError({ toolName: run.currentTool })
|
|
1873
1222
|
: undefined;
|
|
1874
1223
|
|
|
1875
1224
|
let structuredOutput: unknown;
|
|
@@ -1956,19 +1305,10 @@ async function runSingleStepInner(
|
|
|
1956
1305
|
: run.error && run.exitCode === 0
|
|
1957
1306
|
? 1
|
|
1958
1307
|
: run.exitCode;
|
|
1959
|
-
const signalError = run.exitCode !== 0 && isUnexplainedProcessSignal(omitUndefinedProperties({
|
|
1960
|
-
processSignal: run.processSignal,
|
|
1961
|
-
interrupted: run.interrupted,
|
|
1962
|
-
timedOut: run.timedOut,
|
|
1963
|
-
stopped: run.stopped,
|
|
1964
|
-
})) ? formatProcessSignalError(run.processSignal!) : undefined;
|
|
1965
1308
|
const underlyingError = toolAvailabilityError
|
|
1966
1309
|
?? midToolExitError
|
|
1967
1310
|
?? structuredError
|
|
1968
1311
|
?? run.error
|
|
1969
|
-
?? signalError
|
|
1970
|
-
?? (run.exitCode !== 0 && run.stderr.trim() ? run.stderr.trim() : undefined)
|
|
1971
|
-
?? ((emptyOutputError || missingRequiredOutputError) && run.stderr.trim() ? run.stderr.trim() : undefined)
|
|
1972
1312
|
?? emptyOutputError
|
|
1973
1313
|
?? (missingRequiredOutputAfterMutation ? missingRequiredOutputError : undefined)
|
|
1974
1314
|
?? (hiddenError?.hasError
|
|
@@ -1976,13 +1316,7 @@ async function runSingleStepInner(
|
|
|
1976
1316
|
? `${hiddenError.errorType} failed (exit ${effectiveExitCode}): ${hiddenError.details}`
|
|
1977
1317
|
: `${hiddenError.errorType} failed with exit code ${effectiveExitCode}`
|
|
1978
1318
|
: undefined);
|
|
1979
|
-
const error =
|
|
1980
|
-
underlyingError ?? missingRequiredOutputError ?? completionEvidence.legacyFailureError,
|
|
1981
|
-
{
|
|
1982
|
-
agent: step.agent,
|
|
1983
|
-
ambientExtensionsEnabled: launchResolvedExtensions?.disableAmbientExtensions === false,
|
|
1984
|
-
},
|
|
1985
|
-
);
|
|
1319
|
+
const error = underlyingError ?? missingRequiredOutputError ?? completionEvidence.legacyFailureError;
|
|
1986
1320
|
const attempt: ModelAttempt = omitUndefinedProperties({
|
|
1987
1321
|
model: candidate ?? run.model ?? step.model ?? "default",
|
|
1988
1322
|
success: effectiveExitCode === 0 && !error,
|
|
@@ -1991,7 +1325,7 @@ async function runSingleStepInner(
|
|
|
1991
1325
|
usage: run.usage,
|
|
1992
1326
|
});
|
|
1993
1327
|
modelAttempts.push(attempt);
|
|
1994
|
-
if (!recoveringAbort && candidate
|
|
1328
|
+
if (!recoveringAbort && candidate) attemptedModels.push(candidate);
|
|
1995
1329
|
completionGuardTriggeredFinal = completionEvidence.guardTriggered && !underlyingError && !missingRequiredOutputError;
|
|
1996
1330
|
finalOutputSnapshot = outputSnapshot;
|
|
1997
1331
|
if (step.toolBudget) {
|
|
@@ -2008,11 +1342,10 @@ async function runSingleStepInner(
|
|
|
2008
1342
|
afterCompactionSettlement: run.afterCompactionSettlement === true,
|
|
2009
1343
|
});
|
|
2010
1344
|
const fileMutationEffect = completionEvidence.fileMutation ?? (missingRequiredOutputAfterMutation ? { status: "observed" as const, expected: completionEvidence.mutationExpected, attempted: true, evidence: mutationEvidence } : undefined);
|
|
2011
|
-
finalResult = { ...run, exitCode: effectiveExitCode, model: candidate ?? run.model, error, structuredOutput, runtimeAcknowledgedExtensions, ...(step.agentContract ? { agentContract: step.agentContract } : {}), ...(fileMutationEffect || settlementDiagnostic ? { effects: { ...(fileMutationEffect ? { fileMutation: fileMutationEffect } : {}), ...(settlementDiagnostic ? { settlementDiagnostic } : {}) } } : {}) } as
|
|
1345
|
+
finalResult = { ...run, exitCode: effectiveExitCode, model: candidate ?? run.model, error, structuredOutput, runtimeAcknowledgedExtensions, ...(step.agentContract ? { agentContract: step.agentContract } : {}), ...(fileMutationEffect || settlementDiagnostic ? { effects: { ...(fileMutationEffect ? { fileMutation: fileMutationEffect } : {}), ...(settlementDiagnostic ? { settlementDiagnostic } : {}) } } : {}) } as RunChildSessionResult;
|
|
2012
1346
|
const abortRecovery = !attempt.success ? planAbortRecovery({
|
|
2013
1347
|
messages: run.messages,
|
|
2014
1348
|
error,
|
|
2015
|
-
processSignal: run.processSignal,
|
|
2016
1349
|
sessionAvailable: Boolean(step.sessionFile && fs.existsSync(step.sessionFile)),
|
|
2017
1350
|
alreadyResumed: abortRecoveryAttempted,
|
|
2018
1351
|
stopped: run.stopped || ctx.stopSignal?.aborted || ctx.skipAcceptance?.(),
|
|
@@ -2043,50 +1376,6 @@ async function runSingleStepInner(
|
|
|
2043
1376
|
}
|
|
2044
1377
|
if (completionEvidence.guardTriggered) break modelAttemptsLoop;
|
|
2045
1378
|
|
|
2046
|
-
const startupFailure = isRetryableSubagentStartupFailure(omitUndefinedProperties({
|
|
2047
|
-
exitCode: effectiveExitCode,
|
|
2048
|
-
error,
|
|
2049
|
-
finalOutput: run.finalOutput,
|
|
2050
|
-
messageCount: run.messages.length,
|
|
2051
|
-
toolCount: run.toolCount,
|
|
2052
|
-
usage: run.usage,
|
|
2053
|
-
durationMs: run.durationMs,
|
|
2054
|
-
protocolError: run.protocolError,
|
|
2055
|
-
processSignal: run.processSignal,
|
|
2056
|
-
observedMutationAttempt: run.observedMutationAttempt,
|
|
2057
|
-
interrupted: run.interrupted,
|
|
2058
|
-
timedOut: run.timedOut,
|
|
2059
|
-
stopped: run.stopped,
|
|
2060
|
-
}));
|
|
2061
|
-
const retryDelayMs = SUBAGENT_STARTUP_RETRY_DELAYS_MS[startupAttemptIndex];
|
|
2062
|
-
if (startupFailure && retryDelayMs !== undefined) {
|
|
2063
|
-
const retryNote = formatSubagentStartupRetryNote({
|
|
2064
|
-
model: attempt.model,
|
|
2065
|
-
attempt: startupAttemptIndex + 1,
|
|
2066
|
-
maxAttempts: SUBAGENT_STARTUP_RETRY_DELAYS_MS.length + 1,
|
|
2067
|
-
delayMs: retryDelayMs,
|
|
2068
|
-
});
|
|
2069
|
-
const shouldRetry = await waitForSubagentStartupRetry(retryDelayMs, [ctx.timeoutSignal, ctx.stopSignal]);
|
|
2070
|
-
if (!shouldRetry || ctx.skipAcceptance?.()) break modelAttemptsLoop;
|
|
2071
|
-
if (!taskDeliveryOverride && run.processSignal === "SIGKILL") {
|
|
2072
|
-
taskDeliveryOverride = "file";
|
|
2073
|
-
attemptNotes.push("[startup-retry] retrying with file task delivery to keep the task text out of the child process argv.");
|
|
2074
|
-
}
|
|
2075
|
-
attempt.error = retryNote;
|
|
2076
|
-
attemptNotes.push(retryNote);
|
|
2077
|
-
startupAttemptIndex += 1;
|
|
2078
|
-
continue;
|
|
2079
|
-
}
|
|
2080
|
-
if (startupFailure) {
|
|
2081
|
-
const startupError = formatSubagentStartupRetryExhaustedError({
|
|
2082
|
-
model: attempt.model,
|
|
2083
|
-
attempts: startupAttemptIndex + 1,
|
|
2084
|
-
});
|
|
2085
|
-
attempt.error = startupError;
|
|
2086
|
-
finalResult.error = startupError;
|
|
2087
|
-
finalResult.finalOutput = startupError;
|
|
2088
|
-
break modelAttemptsLoop;
|
|
2089
|
-
}
|
|
2090
1379
|
const retryableModelFailure = isRetryableModelFailureAttempt({ error, messages: run.messages, toolCount: run.toolCount });
|
|
2091
1380
|
if (retryableModelFailure) recordRetryableModelFailure(candidate ?? run.model ?? step.model, error);
|
|
2092
1381
|
if (isContextOverflow(error)) {
|
|
@@ -2097,7 +1386,6 @@ async function runSingleStepInner(
|
|
|
2097
1386
|
if (!retryableModelFailure || modelIndex === candidates.length - 1) break modelAttemptsLoop;
|
|
2098
1387
|
attemptNotes.push(formatModelAttemptNote(attempt, candidates[modelIndex + 1]));
|
|
2099
1388
|
modelIndex += 1;
|
|
2100
|
-
startupAttemptIndex = 0;
|
|
2101
1389
|
}
|
|
2102
1390
|
|
|
2103
1391
|
const rawOutput = finalResult?.finalOutput ?? "";
|
|
@@ -2125,7 +1413,7 @@ async function runSingleStepInner(
|
|
|
2125
1413
|
? extractChildWrittenOutput(finalResult?.messages, step.outputPath, step.cwd ?? ctx.cwd)
|
|
2126
1414
|
: undefined;
|
|
2127
1415
|
const outputState: SubagentOutputState = finalResult?.outputState === "present"
|
|
2128
|
-
|| (finalResult as (
|
|
1416
|
+
|| (finalResult as (RunChildSessionResult & { structuredOutput?: unknown }) | undefined)?.structuredOutput !== undefined
|
|
2129
1417
|
|| Boolean(childWrittenOutput?.trim())
|
|
2130
1418
|
? "present"
|
|
2131
1419
|
: resolvedOutput.savedPath
|
|
@@ -2227,7 +1515,7 @@ async function runSingleStepInner(
|
|
|
2227
1515
|
...(capabilityAudit ? { capabilityCeiling: capabilityAudit.ceiling, capabilityAudit } : {}),
|
|
2228
1516
|
launchContractDigest: actualLaunchContractDigest,
|
|
2229
1517
|
launchResolvedExtensions,
|
|
2230
|
-
...((finalResult as (
|
|
1518
|
+
...((finalResult as (RunChildSessionResult & { runtimeAcknowledgedExtensions?: RuntimeAcknowledgedChildExtensionsV1 }) | undefined)?.runtimeAcknowledgedExtensions ? { runtimeAcknowledgedExtensions: (finalResult as RunChildSessionResult & { runtimeAcknowledgedExtensions?: RuntimeAcknowledgedChildExtensionsV1 }).runtimeAcknowledgedExtensions } : {}),
|
|
2231
1519
|
...(transcriptWriter ? { transcriptPath: artifactPaths.transcriptPath } : {}),
|
|
2232
1520
|
transcriptError: transcriptWriter?.getError(),
|
|
2233
1521
|
skills: step.skills,
|
|
@@ -2246,7 +1534,6 @@ async function runSingleStepInner(
|
|
|
2246
1534
|
outputState,
|
|
2247
1535
|
exitCode: effectiveFinalExitCode,
|
|
2248
1536
|
error: effectiveFinalError,
|
|
2249
|
-
protocolError: finalResult?.protocolError,
|
|
2250
1537
|
sessionFile: step.sessionFile,
|
|
2251
1538
|
intercomTarget: ctx.childIntercomTarget,
|
|
2252
1539
|
model: finalResult?.model,
|
|
@@ -2264,22 +1551,19 @@ async function runSingleStepInner(
|
|
|
2264
1551
|
interrupted: timedOutAfterAcceptance || stoppedAfterAcceptance ? false : finalResult?.interrupted,
|
|
2265
1552
|
timedOut: timedOutAfterAcceptance ? true : finalResult?.timedOut,
|
|
2266
1553
|
stopped: stoppedAfterAcceptance ? true : finalResult?.stopped,
|
|
2267
|
-
processSignal: finalResult?.processSignal,
|
|
2268
1554
|
timeoutRecovery,
|
|
2269
1555
|
toolBudget,
|
|
2270
1556
|
toolBudgetBlocked: toolBudgetBlocked || undefined,
|
|
2271
1557
|
completionGuardTriggered: completionGuardTriggeredFinal,
|
|
2272
|
-
...((finalResult as (
|
|
2273
|
-
structuredOutput: timedOutAfterAcceptance || stoppedAfterAcceptance ? undefined : (finalResult as (
|
|
1558
|
+
...((finalResult as (RunChildSessionResult & { effects?: import("../../shared/types.ts").EffectsProjection }) | undefined)?.effects ? { effects: (finalResult as RunChildSessionResult & { effects?: import("../../shared/types.ts").EffectsProjection }).effects } : {}),
|
|
1559
|
+
structuredOutput: timedOutAfterAcceptance || stoppedAfterAcceptance ? undefined : (finalResult as (RunChildSessionResult & { structuredOutput?: unknown }) | undefined)?.structuredOutput,
|
|
2274
1560
|
structuredOutputPath: timedOutAfterAcceptance || stoppedAfterAcceptance ? undefined : effectiveStructuredOutput?.outputPath,
|
|
2275
1561
|
structuredOutputSchemaPath: timedOutAfterAcceptance || stoppedAfterAcceptance ? undefined : effectiveStructuredOutput?.schemaPath,
|
|
2276
1562
|
acceptance: effectiveAcceptance,
|
|
2277
1563
|
watchdog: finalResult?.watchdog,
|
|
2278
1564
|
...(capabilityAudit ? { capabilityCeiling: capabilityAudit.ceiling, capabilityAudit } : {}),
|
|
2279
1565
|
launchResolvedExtensions,
|
|
2280
|
-
...((finalResult as (
|
|
2281
|
-
writerProcesses,
|
|
2282
|
-
writerAttemptCount,
|
|
1566
|
+
...((finalResult as (RunChildSessionResult & { runtimeAcknowledgedExtensions?: RuntimeAcknowledgedChildExtensionsV1 }) | undefined)?.runtimeAcknowledgedExtensions ? { runtimeAcknowledgedExtensions: (finalResult as RunChildSessionResult & { runtimeAcknowledgedExtensions?: RuntimeAcknowledgedChildExtensionsV1 }).runtimeAcknowledgedExtensions } : {}),
|
|
2283
1567
|
});
|
|
2284
1568
|
return isAgentContractV1(step.agentContract) ? attachContractProjections(result as unknown as import("../../shared/types.ts").SingleResult) as unknown as typeof result : result;
|
|
2285
1569
|
}
|
|
@@ -2573,7 +1857,7 @@ async function runSingleStepWithTimeout(
|
|
|
2573
1857
|
|
|
2574
1858
|
async function runSubagent(
|
|
2575
1859
|
config: SubagentRunConfig,
|
|
2576
|
-
|
|
1860
|
+
childSessions: ChildSessionFactory,
|
|
2577
1861
|
): Promise<void> {
|
|
2578
1862
|
const { id, steps, resultPath, cwd, placeholder, taskIndex, totalTasks, maxOutput, artifactsDir, artifactConfig } =
|
|
2579
1863
|
config;
|
|
@@ -2593,9 +1877,11 @@ async function runSubagent(
|
|
|
2593
1877
|
const activeChildInterrupts = new Map<number, () => void>();
|
|
2594
1878
|
const activeChildTimeouts = new Map<number, () => void>();
|
|
2595
1879
|
const activeChildStops = new Map<number, () => void>();
|
|
1880
|
+
const activeChildSteers = new Map<number, StepSteerHandler>();
|
|
1881
|
+
/** Steers routed to a running step before its session was created. */
|
|
1882
|
+
const queuedStepSteers = new Map<number, SteerRequest[]>();
|
|
2596
1883
|
const childStopRequests = new Map<number, { childId: string; requestedAt: number }>();
|
|
2597
1884
|
const pendingStepSteers: SteerRequest[] = [];
|
|
2598
|
-
const steeringCapabilities = new Map<number, SteerCapability>();
|
|
2599
1885
|
let interrupted = false;
|
|
2600
1886
|
let currentActivityState: ActivityState | undefined;
|
|
2601
1887
|
let activityTimer: NodeJS.Timeout | undefined;
|
|
@@ -3053,6 +2339,16 @@ async function runSubagent(
|
|
|
3053
2339
|
activeChildStops.set(flatIndex, stop);
|
|
3054
2340
|
if (stopped || childStopRequests.has(flatIndex)) stop();
|
|
3055
2341
|
};
|
|
2342
|
+
const registerStepSteer = (flatIndex: number, steer: StepSteerHandler | undefined): void => {
|
|
2343
|
+
if (!steer) {
|
|
2344
|
+
activeChildSteers.delete(flatIndex);
|
|
2345
|
+
return;
|
|
2346
|
+
}
|
|
2347
|
+
activeChildSteers.set(flatIndex, steer);
|
|
2348
|
+
const queued = queuedStepSteers.get(flatIndex);
|
|
2349
|
+
queuedStepSteers.delete(flatIndex);
|
|
2350
|
+
for (const request of queued ?? []) steerLiveChild(flatIndex, request);
|
|
2351
|
+
};
|
|
3056
2352
|
const interruptActiveChildren = (): void => {
|
|
3057
2353
|
for (const interrupt of [...activeChildInterrupts.values()]) interrupt();
|
|
3058
2354
|
};
|
|
@@ -3505,22 +2801,15 @@ async function runSubagent(
|
|
|
3505
2801
|
if (!step) return { index, state: "failed" as const, reason: "child index out of range" };
|
|
3506
2802
|
if (step.status === "pending") return { index, state: "scheduled" as const };
|
|
3507
2803
|
if (step.status !== "running") return { index, state: "failed" as const, reason: `child is ${step.status}` };
|
|
3508
|
-
if (steeringCapabilities.get(index)?.supported === false) return { index, state: "failed" as const, reason: "child Pi session does not support steering" };
|
|
3509
2804
|
return { index, state: "routed" as const };
|
|
3510
2805
|
});
|
|
3511
2806
|
recordSteeringLifecycle(request, targetStates);
|
|
3512
2807
|
emitSteeringEvent("subagent.steer.requested", request, undefined, { targets: targetStates });
|
|
3513
2808
|
for (const target of targetStates) {
|
|
3514
2809
|
if (target.state === "routed") {
|
|
3515
|
-
|
|
3516
|
-
|
|
3517
|
-
|
|
3518
|
-
emitSteeringEvent("subagent.steer.routed", request, target.index);
|
|
3519
|
-
} catch (error) {
|
|
3520
|
-
markSteeringAttention(target.index);
|
|
3521
|
-
updateSteeringLifecycleTarget(request.id, target.index, "failed", now, { reason: error instanceof Error ? error.message : String(error) });
|
|
3522
|
-
emitSteeringEvent("subagent.steer.failed", request, target.index, { reason: error instanceof Error ? error.message : String(error) });
|
|
3523
|
-
}
|
|
2810
|
+
updateSteeringLifecycleTarget(request.id, target.index, "routed", now);
|
|
2811
|
+
emitSteeringEvent("subagent.steer.routed", request, target.index);
|
|
2812
|
+
steerLiveChild(target.index, request);
|
|
3524
2813
|
} else if (target.state === "failed") {
|
|
3525
2814
|
markSteeringAttention(target.index);
|
|
3526
2815
|
emitSteeringEvent("subagent.steer.failed", request, target.index, { reason: target.reason });
|
|
@@ -3532,27 +2821,42 @@ async function runSubagent(
|
|
|
3532
2821
|
statusPayload.lastUpdate = now;
|
|
3533
2822
|
writeStatusPayload();
|
|
3534
2823
|
};
|
|
3535
|
-
|
|
2824
|
+
/** Record the outcome of handing a routed steer to the live child session. */
|
|
2825
|
+
const applySteerDelivery = (requestId: string, index: number, delivery: { state: "delivered" | "queued" | "failed"; message: string }): void => {
|
|
3536
2826
|
const lifecycle = steeringStatus(statusPayload);
|
|
3537
|
-
const request = lifecycle.recent.find((candidate) => candidate.id ===
|
|
3538
|
-
if (!request || !request.targets.some((target) => target.index ===
|
|
3539
|
-
const late = fs.existsSync(steeringMarkerPath(
|
|
2827
|
+
const request = lifecycle.recent.find((candidate) => candidate.id === requestId);
|
|
2828
|
+
if (!request || !request.targets.some((target) => target.index === index)) return;
|
|
2829
|
+
const late = fs.existsSync(steeringMarkerPath(requestId));
|
|
3540
2830
|
const now = Date.now();
|
|
3541
|
-
if (
|
|
3542
|
-
updateSteeringLifecycleTarget(
|
|
3543
|
-
emitSteeringEvent("subagent.steer.delivered", { type: "steer", id:
|
|
3544
|
-
} else if (
|
|
3545
|
-
updateSteeringLifecycleTarget(
|
|
3546
|
-
emitSteeringEvent("subagent.steer.queued", { type: "steer", id:
|
|
2831
|
+
if (delivery.state === "delivered") {
|
|
2832
|
+
updateSteeringLifecycleTarget(requestId, index, late ? "late" : "delivered", now, omitUndefinedProperties({ reason: late ? "acknowledged after recovery commit" : undefined }));
|
|
2833
|
+
emitSteeringEvent("subagent.steer.delivered", { type: "steer", id: requestId, ts: now, message: delivery.message }, index, { late, deliveryStatus: "delivered", message: delivery.message });
|
|
2834
|
+
} else if (delivery.state === "queued") {
|
|
2835
|
+
updateSteeringLifecycleTarget(requestId, index, "queued", now);
|
|
2836
|
+
emitSteeringEvent("subagent.steer.queued", { type: "steer", id: requestId, ts: now, message: delivery.message }, index, { deliveryStatus: "queued", message: delivery.message });
|
|
3547
2837
|
} else {
|
|
3548
|
-
markSteeringAttention(
|
|
3549
|
-
updateSteeringLifecycleTarget(
|
|
3550
|
-
emitSteeringEvent("subagent.steer.failed", { type: "steer", id:
|
|
2838
|
+
markSteeringAttention(index);
|
|
2839
|
+
updateSteeringLifecycleTarget(requestId, index, "failed", now, { reason: delivery.message });
|
|
2840
|
+
emitSteeringEvent("subagent.steer.failed", { type: "steer", id: requestId, ts: now, message: delivery.message }, index, { reason: delivery.message });
|
|
3551
2841
|
}
|
|
3552
|
-
emitTerminalSteeringNotice(
|
|
2842
|
+
emitTerminalSteeringNotice(requestId, `Steering failed for run ${id}: ${delivery.message}`);
|
|
3553
2843
|
statusPayload.lastUpdate = now;
|
|
3554
2844
|
writeStatusPayload();
|
|
3555
2845
|
};
|
|
2846
|
+
/** Hand a routed steer to the step's live session, or hold it until the session exists. */
|
|
2847
|
+
const steerLiveChild = (index: number, request: SteerRequest): void => {
|
|
2848
|
+
const steer = activeChildSteers.get(index);
|
|
2849
|
+
if (!steer) {
|
|
2850
|
+
const queued = queuedStepSteers.get(index) ?? [];
|
|
2851
|
+
queued.push(request);
|
|
2852
|
+
queuedStepSteers.set(index, queued);
|
|
2853
|
+
return;
|
|
2854
|
+
}
|
|
2855
|
+
void steer(request).then(
|
|
2856
|
+
(delivery) => applySteerDelivery(request.id, index, delivery),
|
|
2857
|
+
(error) => applySteerDelivery(request.id, index, { state: "failed", message: error instanceof Error ? error.message : String(error) }),
|
|
2858
|
+
);
|
|
2859
|
+
};
|
|
3556
2860
|
const flushPendingStepSteers = (flatIndex: number): void => {
|
|
3557
2861
|
const remaining: SteerRequest[] = [];
|
|
3558
2862
|
for (const request of pendingStepSteers.splice(0)) {
|
|
@@ -3636,6 +2940,7 @@ async function runSubagent(
|
|
|
3636
2940
|
tokens: step.tokens?.total,
|
|
3637
2941
|
toolCount: step.toolCount,
|
|
3638
2942
|
currentTool: step.currentTool,
|
|
2943
|
+
toolCallId: event.toolCallId,
|
|
3639
2944
|
currentToolDurationMs: 0,
|
|
3640
2945
|
currentPath: step.currentPath,
|
|
3641
2946
|
})));
|
|
@@ -3910,23 +3215,6 @@ async function runSubagent(
|
|
|
3910
3215
|
pendingStepSteers.push(request);
|
|
3911
3216
|
}
|
|
3912
3217
|
},
|
|
3913
|
-
onSteerCapability: (capability) => {
|
|
3914
|
-
steeringCapabilities.set(capability.index, capability);
|
|
3915
|
-
if (!capability.supported) {
|
|
3916
|
-
const now = Date.now();
|
|
3917
|
-
const lifecycle = steeringStatus(statusPayload);
|
|
3918
|
-
for (const request of lifecycle.recent) {
|
|
3919
|
-
if (!request.targets.some((target) => target.index === capability.index && (target.state === "routed" || target.state === "scheduled"))) continue;
|
|
3920
|
-
markSteeringAttention(capability.index);
|
|
3921
|
-
updateSteeringLifecycleTarget(request.id, capability.index, "failed", now, { reason: "child Pi session does not support steering" });
|
|
3922
|
-
emitSteeringEvent("subagent.steer.failed", { type: "steer", id: request.id, ts: request.requestedAt, message: "child Pi session does not support steering" }, capability.index, { reason: "child Pi session does not support steering" });
|
|
3923
|
-
emitTerminalSteeringNotice(request.id, `Steering failed for run ${id}: child ${capability.index} does not support steering.`);
|
|
3924
|
-
}
|
|
3925
|
-
statusPayload.lastUpdate = now;
|
|
3926
|
-
writeStatusPayload();
|
|
3927
|
-
}
|
|
3928
|
-
},
|
|
3929
|
-
onSteerAck: consumeSteerAck,
|
|
3930
3218
|
});
|
|
3931
3219
|
if (config.deadlineAt !== undefined) {
|
|
3932
3220
|
const remainingMs = Math.max(0, config.deadlineAt - Date.now());
|
|
@@ -3983,7 +3271,7 @@ async function runSubagent(
|
|
|
3983
3271
|
: [undefined]
|
|
3984
3272
|
: model ? [model] : [undefined];
|
|
3985
3273
|
for (const candidate of candidates) {
|
|
3986
|
-
assertThinkingWithinCeiling({ model: candidate, configThinking, ceiling: step.parallel.thinkingCeiling
|
|
3274
|
+
assertThinkingWithinCeiling({ model: candidate, configThinking, ceiling: step.parallel.thinkingCeiling, agent: step.parallel.agent, runId: id });
|
|
3987
3275
|
}
|
|
3988
3276
|
}
|
|
3989
3277
|
if (materialized.collectedOnEmpty) await validateDynamicCollection(step.collect.outputSchema, materialized.collectedOnEmpty);
|
|
@@ -4253,11 +3541,9 @@ async function runSubagent(
|
|
|
4253
3541
|
artifactsDir, artifactConfig, id,
|
|
4254
3542
|
flatIndex: fi, flatStepCount: Math.max(statusPayload.steps.length, 1),
|
|
4255
3543
|
outputFile: path.join(asyncDir, `output-${fi}.log`),
|
|
4256
|
-
steerInboxDir: stepSteerInboxDir(asyncDir, fi),
|
|
4257
|
-
steerCapabilityPath: steerCapabilityPath(asyncDir, fi),
|
|
4258
|
-
steerAckDir: steerAcksDir(asyncDir, fi),
|
|
4259
3544
|
piPackageRoot: config.piPackageRoot,
|
|
4260
|
-
|
|
3545
|
+
childSessions,
|
|
3546
|
+
inheritedChildRuntime: config.inheritedChildRuntime,
|
|
4261
3547
|
childIntercomTarget: config.childIntercomTargets?.[fi],
|
|
4262
3548
|
orchestratorIntercomTarget: config.controlIntercomTarget,
|
|
4263
3549
|
nestedRoute: config.nestedRoute,
|
|
@@ -4266,6 +3552,7 @@ async function runSubagent(
|
|
|
4266
3552
|
registerInterrupt: (interrupt) => registerStepInterrupt(fi, interrupt),
|
|
4267
3553
|
registerTimeout: (interrupt) => registerStepTimeout(fi, interrupt),
|
|
4268
3554
|
registerStop: (stop) => registerStepStop(fi, stop),
|
|
3555
|
+
registerSteer: (steer) => registerStepSteer(fi, steer),
|
|
4269
3556
|
timeoutSignal: timeoutAbortController.signal,
|
|
4270
3557
|
stopSignal: stopAbortController.signal,
|
|
4271
3558
|
trackedMutationEvidenceForCompletionGuard: false,
|
|
@@ -4274,7 +3561,6 @@ async function runSubagent(
|
|
|
4274
3561
|
toolTimeoutMs: task.toolTimeoutMs ?? config.toolTimeoutMs,
|
|
4275
3562
|
onAttemptStart: (attempt) => updateStepModel(fi, attempt.model, attempt.thinking, attempt.contextLimit),
|
|
4276
3563
|
onChildEvent: (event) => updateStepFromChildEvent(fi, event),
|
|
4277
|
-
onWriterProcess,
|
|
4278
3564
|
onExternalProcess: (process) => updateExternalProcess(fi, process),
|
|
4279
3565
|
onExternalJob: (externalJob) => updateExternalJob(fi, externalJob),
|
|
4280
3566
|
skipAcceptance: () => timedOut || stopped || childStopRequests.has(fi),
|
|
@@ -4355,7 +3641,6 @@ async function runSubagent(
|
|
|
4355
3641
|
output: pr.output,
|
|
4356
3642
|
outputState: pr.outputState,
|
|
4357
3643
|
error: pr.error,
|
|
4358
|
-
protocolError: pr.protocolError,
|
|
4359
3644
|
success: pr.stopped !== true && pr.interrupted !== true && pr.exitCode === 0,
|
|
4360
3645
|
exitCode: pr.interrupted === true ? 0 : pr.exitCode,
|
|
4361
3646
|
skipped: pr.skipped,
|
|
@@ -4515,6 +3800,7 @@ async function runSubagent(
|
|
|
4515
3800
|
labels: group.parallel.map((task) => task.lane?.key ?? config.workflowKey ?? task.outputName ?? task.label),
|
|
4516
3801
|
tasks: group.parallel.map((task) => task.task),
|
|
4517
3802
|
provider: config.worktreeProvider,
|
|
3803
|
+
baseRef: config.baseRef,
|
|
4518
3804
|
branchPrefix: config.worktreeBranchPrefix,
|
|
4519
3805
|
setupHook: config.worktreeSetupHook
|
|
4520
3806
|
? omitUndefinedProperties({ hookPath: config.worktreeSetupHook, timeoutMs: config.worktreeSetupHookTimeoutMs })
|
|
@@ -4652,11 +3938,9 @@ async function runSubagent(
|
|
|
4652
3938
|
artifactsDir, artifactConfig, id,
|
|
4653
3939
|
flatIndex: fi, flatStepCount: Math.max(statusPayload.steps.length, 1),
|
|
4654
3940
|
outputFile: path.join(asyncDir, `output-${fi}.log`),
|
|
4655
|
-
steerInboxDir: stepSteerInboxDir(asyncDir, fi),
|
|
4656
|
-
steerCapabilityPath: steerCapabilityPath(asyncDir, fi),
|
|
4657
|
-
steerAckDir: steerAcksDir(asyncDir, fi),
|
|
4658
3941
|
piPackageRoot: config.piPackageRoot,
|
|
4659
|
-
|
|
3942
|
+
childSessions,
|
|
3943
|
+
inheritedChildRuntime: config.inheritedChildRuntime,
|
|
4660
3944
|
childIntercomTarget: config.childIntercomTargets?.[fi],
|
|
4661
3945
|
orchestratorIntercomTarget: config.controlIntercomTarget,
|
|
4662
3946
|
nestedRoute: config.nestedRoute,
|
|
@@ -4665,6 +3949,7 @@ async function runSubagent(
|
|
|
4665
3949
|
registerInterrupt: (interrupt) => registerStepInterrupt(fi, interrupt),
|
|
4666
3950
|
registerTimeout: (interrupt) => registerStepTimeout(fi, interrupt),
|
|
4667
3951
|
registerStop: (stop) => registerStepStop(fi, stop),
|
|
3952
|
+
registerSteer: (steer) => registerStepSteer(fi, steer),
|
|
4668
3953
|
timeoutSignal: timeoutAbortController.signal,
|
|
4669
3954
|
stopSignal: stopAbortController.signal,
|
|
4670
3955
|
trackedMutationEvidenceForCompletionGuard: Boolean(worktreeSetup),
|
|
@@ -4673,7 +3958,6 @@ async function runSubagent(
|
|
|
4673
3958
|
toolTimeoutMs: taskForRun.toolTimeoutMs ?? config.toolTimeoutMs,
|
|
4674
3959
|
onAttemptStart: (attempt) => updateStepModel(fi, attempt.model, attempt.thinking, attempt.contextLimit),
|
|
4675
3960
|
onChildEvent: (event) => updateStepFromChildEvent(fi, event),
|
|
4676
|
-
onWriterProcess,
|
|
4677
3961
|
onExternalProcess: (process) => updateExternalProcess(fi, process),
|
|
4678
3962
|
onExternalJob: (externalJob) => updateExternalJob(fi, externalJob),
|
|
4679
3963
|
skipAcceptance: () => timedOut || stopped || childStopRequests.has(fi),
|
|
@@ -4801,7 +4085,6 @@ async function runSubagent(
|
|
|
4801
4085
|
output: pr.output,
|
|
4802
4086
|
outputState: pr.outputState,
|
|
4803
4087
|
error: pr.error,
|
|
4804
|
-
protocolError: pr.protocolError,
|
|
4805
4088
|
success: pr.stopped !== true && pr.interrupted !== true && pr.exitCode === 0,
|
|
4806
4089
|
exitCode: pr.interrupted === true ? 0 : pr.exitCode,
|
|
4807
4090
|
skipped: pr.skipped,
|
|
@@ -4952,6 +4235,7 @@ async function runSubagent(
|
|
|
4952
4235
|
labels: [seqStep.lane?.key ?? config.workflowKey ?? seqStep.outputName ?? seqStep.label],
|
|
4953
4236
|
tasks: [seqStep.task],
|
|
4954
4237
|
provider: config.worktreeProvider,
|
|
4238
|
+
baseRef: config.baseRef,
|
|
4955
4239
|
branchPrefix: config.worktreeBranchPrefix,
|
|
4956
4240
|
setupHook: config.worktreeSetupHook
|
|
4957
4241
|
? omitUndefinedProperties({ hookPath: config.worktreeSetupHook, timeoutMs: config.worktreeSetupHookTimeoutMs })
|
|
@@ -5019,11 +4303,9 @@ async function runSubagent(
|
|
|
5019
4303
|
artifactsDir, artifactConfig, id,
|
|
5020
4304
|
flatIndex, flatStepCount: Math.max(statusPayload.steps.length, 1),
|
|
5021
4305
|
outputFile: path.join(asyncDir, `output-${flatIndex}.log`),
|
|
5022
|
-
steerInboxDir: stepSteerInboxDir(asyncDir, flatIndex),
|
|
5023
|
-
steerCapabilityPath: steerCapabilityPath(asyncDir, flatIndex),
|
|
5024
|
-
steerAckDir: steerAcksDir(asyncDir, flatIndex),
|
|
5025
4306
|
piPackageRoot: config.piPackageRoot,
|
|
5026
|
-
|
|
4307
|
+
childSessions,
|
|
4308
|
+
inheritedChildRuntime: config.inheritedChildRuntime,
|
|
5027
4309
|
childIntercomTarget: config.childIntercomTargets?.[flatIndex],
|
|
5028
4310
|
orchestratorIntercomTarget: config.controlIntercomTarget,
|
|
5029
4311
|
nestedRoute: config.nestedRoute,
|
|
@@ -5032,6 +4314,7 @@ async function runSubagent(
|
|
|
5032
4314
|
registerInterrupt: (interrupt) => registerStepInterrupt(flatIndex, interrupt),
|
|
5033
4315
|
registerTimeout: (interrupt) => registerStepTimeout(flatIndex, interrupt),
|
|
5034
4316
|
registerStop: (stop) => registerStepStop(flatIndex, stop),
|
|
4317
|
+
registerSteer: (steer) => registerStepSteer(flatIndex, steer),
|
|
5035
4318
|
timeoutSignal: timeoutAbortController.signal,
|
|
5036
4319
|
stopSignal: stopAbortController.signal,
|
|
5037
4320
|
timeoutMessage,
|
|
@@ -5039,7 +4322,6 @@ async function runSubagent(
|
|
|
5039
4322
|
toolTimeoutMs: seqStep.toolTimeoutMs ?? config.toolTimeoutMs,
|
|
5040
4323
|
onAttemptStart: (attempt) => updateStepModel(flatIndex, attempt.model, attempt.thinking, attempt.contextLimit),
|
|
5041
4324
|
onChildEvent: (event) => updateStepFromChildEvent(flatIndex, event),
|
|
5042
|
-
onWriterProcess,
|
|
5043
4325
|
onExternalProcess: (process) => updateExternalProcess(flatIndex, process),
|
|
5044
4326
|
onExternalJob: (externalJob) => updateExternalJob(flatIndex, externalJob),
|
|
5045
4327
|
skipAcceptance: () => timedOut || stopped || childStopRequests.has(flatIndex),
|
|
@@ -5067,7 +4349,6 @@ async function runSubagent(
|
|
|
5067
4349
|
output: stopped || childStopped ? stopMessage : timedOut ? singleResult.output || (timeoutMessage ?? "Subagent timed out.") : singleResult.output,
|
|
5068
4350
|
outputState: singleResult.outputState,
|
|
5069
4351
|
error: stopped || childStopped ? stopMessage : timedOut ? (timeoutMessage ?? "Subagent timed out.") : singleResult.error,
|
|
5070
|
-
protocolError: singleResult.protocolError,
|
|
5071
4352
|
success: !stopped && !childStopped && !timedOut && singleResult.interrupted !== true && singleResult.exitCode === 0,
|
|
5072
4353
|
exitCode: stopped || childStopped ? 1 : timedOut ? 1 : singleResult.interrupted === true ? 0 : singleResult.exitCode,
|
|
5073
4354
|
sessionFile: singleResult.sessionFile,
|
|
@@ -5335,7 +4616,7 @@ async function runSubagent(
|
|
|
5335
4616
|
clearTimeout(timeoutTimer);
|
|
5336
4617
|
timeoutTimer = undefined;
|
|
5337
4618
|
}
|
|
5338
|
-
if (!timedOut && !stopped && !interrupted && config.timeoutMs !== undefined && results.some((result) => result.timedOut === true && result.error
|
|
4619
|
+
if (!timedOut && !stopped && !interrupted && config.timeoutMs !== undefined && timeoutMessage !== undefined && results.some((result) => result.timedOut === true && result.error?.startsWith(timeoutMessage))) {
|
|
5339
4620
|
timedOut = true;
|
|
5340
4621
|
}
|
|
5341
4622
|
const signalTerminated = !stopped && !timedOut && !interrupted && results.some((result) => result.exitCode !== 0 && isUnexplainedProcessSignal(omitUndefinedProperties({
|
|
@@ -5425,7 +4706,6 @@ async function runSubagent(
|
|
|
5425
4706
|
output: r.output,
|
|
5426
4707
|
outputState: r.outputState,
|
|
5427
4708
|
error: r.error,
|
|
5428
|
-
protocolError: r.protocolError,
|
|
5429
4709
|
success: r.success,
|
|
5430
4710
|
skipped: r.skipped || undefined,
|
|
5431
4711
|
interrupted: r.interrupted || undefined,
|
|
@@ -5507,7 +4787,7 @@ async function runSubagent(
|
|
|
5507
4787
|
statusPayload.lastUpdate = Date.now();
|
|
5508
4788
|
}
|
|
5509
4789
|
writeStatusPayload();
|
|
5510
|
-
orcaProgressTab?.finish(statusPayload.state === "complete" ? "completed" : statusPayload.state === "stopped" ? "stopped" : "failed", effectiveSessionFile);
|
|
4790
|
+
await orcaProgressTab?.finish(statusPayload.state === "complete" ? "completed" : statusPayload.state === "stopped" ? "stopped" : "failed", effectiveSessionFile);
|
|
5511
4791
|
appendJsonl(
|
|
5512
4792
|
eventsPath,
|
|
5513
4793
|
JSON.stringify({
|
|
@@ -5542,14 +4822,21 @@ async function runSubagent(
|
|
|
5542
4822
|
}), (filePath, content) => runPersistence.write(filePath, { content }, (_path, payload) => {
|
|
5543
4823
|
fs.writeFileSync(_path, (payload as { content: string }).content, "utf-8");
|
|
5544
4824
|
}));
|
|
5545
|
-
|
|
5546
|
-
|
|
4825
|
+
// The run is committed: release the child sessions first, then wait for the
|
|
4826
|
+
// storage-capacity retries that may still hold the final status or result.
|
|
4827
|
+
await childSessions.dispose().catch((error: unknown) => console.error("Failed to dispose runner child sessions:", error));
|
|
4828
|
+
while (runPersistence.pendingCount() + indexPersistence.pendingCount() > 0) {
|
|
4829
|
+
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
4830
|
+
}
|
|
4831
|
+
runPersistence.dispose();
|
|
4832
|
+
indexPersistence.dispose();
|
|
5547
4833
|
if (config.runnerProcessInstanceId) {
|
|
4834
|
+
// Children run inside this process, so no step has writer processes to prove terminal.
|
|
5548
4835
|
const writers: Record<string, PiWriterProcessInstanceExitV1[]> = {};
|
|
5549
4836
|
const expectedWriters: Record<string, number> = {};
|
|
5550
|
-
for (const
|
|
5551
|
-
writers[String(index)] =
|
|
5552
|
-
expectedWriters[String(index)] =
|
|
4837
|
+
for (const index of results.keys()) {
|
|
4838
|
+
writers[String(index)] = [];
|
|
4839
|
+
expectedWriters[String(index)] = 0;
|
|
5553
4840
|
}
|
|
5554
4841
|
const candidate: ProcessTerminalCandidate = {
|
|
5555
4842
|
version: 1,
|
|
@@ -5631,7 +4918,16 @@ async function runConfiguredSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
5631
4918
|
}
|
|
5632
4919
|
}
|
|
5633
4920
|
}
|
|
5634
|
-
|
|
4921
|
+
const childSessions = await loadRunnerChildSessionFactory(config);
|
|
4922
|
+
try {
|
|
4923
|
+
await runSubagent(config, childSessions);
|
|
4924
|
+
} finally {
|
|
4925
|
+
try {
|
|
4926
|
+
await childSessions.dispose();
|
|
4927
|
+
} catch (error) {
|
|
4928
|
+
console.error("Failed to dispose runner child sessions:", error);
|
|
4929
|
+
}
|
|
4930
|
+
}
|
|
5635
4931
|
} catch (error) {
|
|
5636
4932
|
if (!startupCommitted) {
|
|
5637
4933
|
try {
|
|
@@ -5660,10 +4956,16 @@ async function runConfiguredSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
5660
4956
|
}
|
|
5661
4957
|
|
|
5662
4958
|
function startConfiguredSubagent(config: SubagentRunConfig): void {
|
|
5663
|
-
|
|
5664
|
-
|
|
5665
|
-
|
|
5666
|
-
|
|
4959
|
+
// Child sessions and the extensions loaded into them may leave handles
|
|
4960
|
+
// behind even after shutdown; the run is fully persisted by now, so exit
|
|
4961
|
+
// explicitly instead of waiting for the event loop to drain.
|
|
4962
|
+
runConfiguredSubagent(config).then(
|
|
4963
|
+
() => process.exit(0),
|
|
4964
|
+
(runErr) => {
|
|
4965
|
+
console.error("Subagent runner error:", runErr);
|
|
4966
|
+
process.exit(1);
|
|
4967
|
+
},
|
|
4968
|
+
);
|
|
5667
4969
|
}
|
|
5668
4970
|
|
|
5669
4971
|
const configArg = process.argv[2];
|