pi-subagents 0.64.0 → 0.65.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +33 -0
- package/README.md +2 -2
- package/agents/reviewer.md +1 -1
- package/agents/scout.md +1 -1
- package/docs/agents.md +18 -16
- package/docs/configuration.md +7 -15
- package/docs/extension-api.md +15 -6
- package/docs/missions.md +2 -0
- package/docs/observability.md +10 -12
- package/docs/tool-reference.md +7 -6
- package/docs/watchdog.md +1 -1
- package/docs/workflows.md +5 -3
- package/package.json +3 -4
- package/skills/pi-subagents/references/constraints-and-recipes.md +1 -1
- package/skills/pi-subagents/references/execution-controls.md +2 -2
- 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 +2 -3
- package/src/extension/doctor.ts +2 -10
- package/src/extension/fanout-child.ts +9 -11
- package/src/extension/index.ts +27 -5
- package/src/extension/public-execution.ts +14 -0
- package/src/extension/rpc.ts +3 -2
- package/src/extension/schemas.ts +2 -1
- package/src/extension/tool-description.ts +9 -8
- package/src/intercom/native-supervisor-channel.ts +138 -60
- package/src/intercom/supervisor-ui.ts +243 -0
- package/src/runs/background/async-execution.ts +43 -24
- package/src/runs/background/async-job-tracker.ts +11 -0
- package/src/runs/background/async-resume.ts +11 -2
- package/src/runs/background/control-channel.ts +2 -204
- package/src/runs/background/process-terminal.ts +1 -1
- package/src/runs/background/run-child-session.ts +613 -0
- package/src/runs/background/run-status.ts +0 -1
- package/src/runs/background/runner-aliases.ts +125 -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 +184 -901
- package/src/runs/foreground/async-steering-action.ts +1 -17
- package/src/runs/foreground/execution.ts +189 -377
- package/src/runs/foreground/foreground-control.ts +4 -0
- package/src/runs/foreground/subagent-executor.ts +100 -57
- package/src/runs/foreground/workflow-foreground-steering.ts +24 -98
- package/src/runs/shared/abort-recovery.ts +3 -3
- 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 +342 -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/mcp-direct-tool-allowlist.ts +5 -4
- package/src/runs/shared/model-exclusions.ts +82 -14
- package/src/runs/shared/model-fallback.ts +47 -4
- package/src/runs/shared/nested-events.ts +29 -45
- 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 +0 -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 +25 -6
- package/src/runs/shared/worktree.ts +117 -30
- package/src/shared/child-session-name.ts +1 -1
- package/src/shared/jsonl-writer.ts +11 -0
- package/src/shared/thinking-ceiling.ts +0 -6
- package/src/shared/types.ts +55 -28
- package/src/shared/utils.ts +3 -4
- package/src/slash/slash-commands.ts +0 -6
- package/src/tui/fleet.ts +0 -1
- package/src/tui/render.ts +233 -31
- package/src/watchdog/child-status.ts +0 -1
- package/src/watchdog/register-child.ts +12 -12
- package/src/workflows/scripted-workflow.ts +48 -1
- 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,5 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { randomUUID } from "node:crypto";
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
3
2
|
import * as fs from "node:fs";
|
|
4
3
|
import * as path from "node:path";
|
|
5
4
|
import { pathToFileURL } from "node:url";
|
|
@@ -11,9 +10,9 @@ import { createCapacityResilientJsonWriter } from "../../shared/capacity-resilie
|
|
|
11
10
|
import { isStorageCapacityError } from "../../shared/file-system-retry.ts";
|
|
12
11
|
import { updateActiveRunIndex } from "./active-run-index.ts";
|
|
13
12
|
import { createChildTranscriptWriter, type ChildTranscriptWriter } from "../../shared/child-transcript.ts";
|
|
14
|
-
import { closeSteerInbox, consumeInterruptRequest, consumeSteerRequests, deliverInterruptRequest, deliverStopRequest, deliverTimeoutRequest,
|
|
13
|
+
import { closeSteerInbox, consumeInterruptRequest, consumeSteerRequests, deliverInterruptRequest, deliverStopRequest, deliverTimeoutRequest, watchAsyncControlInbox, type SteerRequest, type StopRequest } from "./control-channel.ts";
|
|
15
14
|
import { appendJsonl as appendRawJsonl, formatOutputArtifactContent, getArtifactPaths, writeArtifact, writeMetadata } from "../../shared/artifacts.ts";
|
|
16
|
-
import { PI_CODING_AGENT_PACKAGE,
|
|
15
|
+
import { PI_CODING_AGENT_PACKAGE, resolveInstalledPiPackageRoot } from "../shared/pi-spawn.ts";
|
|
17
16
|
import { preflightLaunchCwd } from "../shared/launch-cwd.ts";
|
|
18
17
|
import { captureSingleOutputSnapshot, extractChildWrittenOutput, finalizeSingleOutput, formatSavedOutputReference, injectOutputPathSystemPrompt, injectSingleOutputInstruction, resolveSingleOutput, type SingleOutputSnapshot } from "../shared/single-output.ts";
|
|
19
18
|
import {
|
|
@@ -32,7 +31,6 @@ import {
|
|
|
32
31
|
type RuntimeAcknowledgedChildExtensionsV1,
|
|
33
32
|
type ModelAttempt,
|
|
34
33
|
type PiWriterProcessInstanceExitV1,
|
|
35
|
-
type ProcessTreeTerminalV1,
|
|
36
34
|
type NestedRouteInfo,
|
|
37
35
|
type NestedRunSummary,
|
|
38
36
|
type ResolvedControlConfig,
|
|
@@ -52,7 +50,6 @@ import {
|
|
|
52
50
|
type MaxOutputConfig,
|
|
53
51
|
SUBAGENT_LIFECYCLE_ARTIFACT_VERSION,
|
|
54
52
|
truncateOutput,
|
|
55
|
-
getSubagentDepthEnv,
|
|
56
53
|
} from "../../shared/types.ts";
|
|
57
54
|
import {
|
|
58
55
|
DEFAULT_CONTROL_CONFIG,
|
|
@@ -75,31 +72,25 @@ import {
|
|
|
75
72
|
DEFAULT_GLOBAL_CONCURRENCY_LIMIT,
|
|
76
73
|
Semaphore,
|
|
77
74
|
} from "../shared/parallel-utils.ts";
|
|
78
|
-
import { applyThinkingSuffix,
|
|
75
|
+
import { applyThinkingSuffix, deriveForkPromptCacheKey, projectLaunchResolvedChildExtensions, resolvePiLaunchToolPlan } from "../shared/child-tool-plan.ts";
|
|
76
|
+
import { buildInProcessChildLaunch, type InheritedChildRuntime } from "../shared/child-launch.ts";
|
|
77
|
+
import type { ChildSessionFactory } from "../shared/child-session.ts";
|
|
78
|
+
import { runChildSession, type ChildEvent, type RunChildSessionResult, type StepSteerHandler } from "./run-child-session.ts";
|
|
79
|
+
import { loadRunnerChildSessionFactory } from "./runner-child-sessions.ts";
|
|
80
|
+
import { SUBAGENT_CHILD_ENV } from "../shared/child-runtime-config.ts";
|
|
79
81
|
import { deriveChildSessionName } from "../../shared/child-session-name.ts";
|
|
80
82
|
import { alignForkedSessionCwd } from "../../shared/fork-session-cwd.ts";
|
|
81
|
-
import { readRuntimeAcknowledgedExtensions } from "../shared/runtime-acknowledged-extensions.ts";
|
|
82
83
|
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 {
|
|
84
|
+
import { clearStructuredOutputCaptures, createStructuredOutputFileCapture, createStructuredOutputRuntime, MISSING_STRUCTURED_OUTPUT_CALL_ERROR, readStructuredOutput, readStructuredOutputAcceptanceReport } from "../shared/structured-output.ts";
|
|
85
|
+
import { formatMidToolExitError, isOrdinaryToolForMidToolExit, isUnexplainedProcessSignal } from "../shared/process-signal.ts";
|
|
86
|
+
import { formatChildToolDiagnostic } from "../shared/tool-availability.ts";
|
|
86
87
|
import { buildTimeoutRecoverySummary, collectTrackedMutationEvidence, snapshotTrackedMutations } from "../shared/mutation-evidence.ts";
|
|
87
88
|
import { collectDynamicResults, DynamicFanoutError, materializeDynamicParallelStep, validateDynamicCollection } from "../shared/dynamic-fanout.ts";
|
|
88
89
|
import { claimRunFanoutBatch, getRunFanoutBudgetSnapshot } from "../shared/run-fanout-budget.ts";
|
|
89
90
|
import { nestedSummaryFromAsyncStatus, projectNestedEvents, resolveNestedAsyncDir, writeNestedEvent } from "../shared/nested-events.ts";
|
|
90
91
|
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
92
|
import { markProcessTerminalCandidateLeaseRelease, writeProcessTerminalCandidate, type ProcessTerminalCandidate } from "./process-terminal.ts";
|
|
100
|
-
import { createOwnedProcessTreeController, type OwnedProcessTreeController } from "./owned-process-tree.ts";
|
|
101
93
|
import { createSteeringStatus, recordSteeringRequest, steeringStatus, terminalSteeringNoticeState, updateSteeringTarget } from "./steering.ts";
|
|
102
|
-
import { attachPostExitStdioGuard, trySignalChild } from "../../shared/post-exit-stdio-guard.ts";
|
|
103
94
|
import { PROMPT_REDACTED, detectSubagentError, extractTextFromContent, extractToolArgsPreview, formatEmptyTerminalAssistantResponseError, getFinalOutput, hasEmptyTerminalAssistantResponse, readStatus } from "../../shared/utils.ts";
|
|
104
95
|
import { evaluateCompletionMutationGuard, expectsImplementationMutation, hasMutationToolCapability, validateImplementationToolContract } from "../shared/completion-guard.ts";
|
|
105
96
|
import { planCompletionEvidence, projectSettlementDiagnostic } from "../shared/completion-evidence.ts";
|
|
@@ -128,7 +119,7 @@ import {
|
|
|
128
119
|
type WorktreeSetup,
|
|
129
120
|
} from "../shared/worktree.ts";
|
|
130
121
|
import { findModelInfo, resolveEffectiveThinking } from "../../shared/model-info.ts";
|
|
131
|
-
import { assertThinkingWithinCeiling
|
|
122
|
+
import { assertThinkingWithinCeiling } from "../../shared/thinking-ceiling.ts";
|
|
132
123
|
import { launchBindingDigest } from "../../shared/launch-contract.ts";
|
|
133
124
|
import { writeInitialProgressFile } from "../../shared/settings.ts";
|
|
134
125
|
import { resolveSubagentIntercomTarget } from "../../intercom/intercom-bridge.ts";
|
|
@@ -143,7 +134,6 @@ import { effectiveToolTimeoutMs, formatToolTimeoutMessage, toolTimeoutCallKey }
|
|
|
143
134
|
import { usageBudgetExceededMessage, usageBudgetState } from "../shared/usage-budget.ts";
|
|
144
135
|
import { formatParallelHandoffError, formatParallelHandoffReference, parallelHandoffPath, writeParallelHandoffGroup, writePendingParallelHandoff } from "../shared/parallel-handoff.ts";
|
|
145
136
|
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
137
|
import { acquireSessionLease, type SessionLeaseRequest } from "../shared/session-lease.ts";
|
|
148
138
|
import { buildExternalCliPrompt, runExternalCli } from "../shared/external-cli-runner.ts";
|
|
149
139
|
import { resolveClaudeCodeLaunch } from "../shared/claude-code-adapter.ts";
|
|
@@ -152,20 +142,22 @@ import { resolveCursorAgentLaunch } from "../shared/cursor-agent-adapter.ts";
|
|
|
152
142
|
import { resolveExternalCliRunnerStatus } from "../shared/external-cli-contract.ts";
|
|
153
143
|
import { runExternalJob } from "../shared/external-job-runner.ts";
|
|
154
144
|
import { createOrcaProgressTab, type OrcaProgressTab } from "../shared/orca-progress-tabs.ts";
|
|
155
|
-
import {
|
|
145
|
+
import type { ResolvedSubagentCapabilityCeiling } from "../shared/capability-ceiling.ts";
|
|
156
146
|
import {
|
|
157
|
-
CHILD_WATCHDOG_CONFIG_ENV,
|
|
158
147
|
acceptChildWatchdogEvent,
|
|
159
148
|
applyChildWatchdogMessage,
|
|
160
|
-
childWatchdogIsActive,
|
|
161
|
-
decodeChildWatchdogConfig,
|
|
162
149
|
isChildWatchdogStatusEvent,
|
|
163
150
|
resolveChildWatchdogConfig,
|
|
164
|
-
type
|
|
151
|
+
type ChildWatchdogStatusEvent,
|
|
165
152
|
} from "../../watchdog/child-status.ts";
|
|
166
153
|
|
|
167
154
|
const INTERCOM_DETACH_RECEIPT = "Detached for intercom coordination before task completion.";
|
|
168
155
|
|
|
156
|
+
// This process hosts child sessions. An ambient copy of pi-subagents loaded
|
|
157
|
+
// into one of them must register nothing; the variable marks the process as a
|
|
158
|
+
// child host.
|
|
159
|
+
process.env[SUBAGENT_CHILD_ENV] = "1";
|
|
160
|
+
|
|
169
161
|
interface SubagentRunConfig {
|
|
170
162
|
id: string;
|
|
171
163
|
steps: RunnerStep[];
|
|
@@ -183,10 +175,14 @@ interface SubagentRunConfig {
|
|
|
183
175
|
sessionId?: string | null;
|
|
184
176
|
completionOwnerId?: string;
|
|
185
177
|
piPackageRoot?: string;
|
|
186
|
-
|
|
178
|
+
/** Test seam: module the runner imports its `ChildSessionFactory` from. */
|
|
179
|
+
childSessionFactoryModule?: string;
|
|
180
|
+
/** The launching executor's own child runtime when it was itself an in-process child. */
|
|
181
|
+
inheritedChildRuntime?: InheritedChildRuntime;
|
|
187
182
|
worktreeSetupHook?: string;
|
|
188
183
|
worktreeSetupHookTimeoutMs?: number;
|
|
189
184
|
worktreeBaseDir?: string;
|
|
185
|
+
baseRef?: string;
|
|
190
186
|
worktreeProvider?: import("../../shared/types.ts").WorktreeProvider;
|
|
191
187
|
worktreeBranchPrefix?: string;
|
|
192
188
|
controlConfig?: ResolvedControlConfig;
|
|
@@ -232,7 +228,6 @@ interface StepResult {
|
|
|
232
228
|
output: string;
|
|
233
229
|
outputState?: SubagentOutputState;
|
|
234
230
|
error?: string;
|
|
235
|
-
protocolError?: ProtocolOutputLimit;
|
|
236
231
|
success?: boolean;
|
|
237
232
|
exitCode: number | null;
|
|
238
233
|
usage?: Usage;
|
|
@@ -271,8 +266,6 @@ interface StepResult {
|
|
|
271
266
|
structuredOutputSchemaPath?: string;
|
|
272
267
|
acceptance?: import("../../shared/types.ts").AcceptanceLedger;
|
|
273
268
|
watchdog?: import("../../shared/types.ts").ChildWatchdogProgress;
|
|
274
|
-
writerProcesses?: PiWriterProcessInstanceExitV1[];
|
|
275
|
-
writerAttemptCount?: number;
|
|
276
269
|
runner?: ExternalCliRunnerStatus | ExternalJobRunnerStatus;
|
|
277
270
|
externalProcess?: ExternalProcessStatus;
|
|
278
271
|
externalJob?: ExternalJobStatus;
|
|
@@ -373,10 +366,6 @@ function appendDiagnosticJsonl(filePath: string, line: string, droppedEventType?
|
|
|
373
366
|
state.diagnosticsTruncated = true;
|
|
374
367
|
}
|
|
375
368
|
|
|
376
|
-
function shouldPersistChildEvent(event: Record<string, unknown>): boolean {
|
|
377
|
-
return event.type !== "message_update";
|
|
378
|
-
}
|
|
379
|
-
|
|
380
369
|
function isBlockingSupervisorTool(toolName: string | undefined, args: unknown): boolean {
|
|
381
370
|
if (!args || typeof args !== "object" || Array.isArray(args)) return false;
|
|
382
371
|
if (toolName === "contact_supervisor") {
|
|
@@ -459,15 +448,6 @@ function appendRecentStepOutput(step: RunnerStatusStep, lines: string[]): void {
|
|
|
459
448
|
}
|
|
460
449
|
}
|
|
461
450
|
|
|
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
451
|
type UndefinedOmitted<T extends object> = {
|
|
472
452
|
[K in keyof T]: Exclude<T[K], undefined>;
|
|
473
453
|
};
|
|
@@ -510,73 +490,6 @@ function resetStepLiveDetail(step: RunnerStatusStep): void {
|
|
|
510
490
|
step.recentOutput = [];
|
|
511
491
|
}
|
|
512
492
|
|
|
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
493
|
const MAX_CHILD_FAILURE_DIAGNOSTIC_CHARS = 8_192;
|
|
581
494
|
|
|
582
495
|
function formatRequiredOutputError(requiredOutput: {
|
|
@@ -611,568 +524,6 @@ function formatChildFailureDiagnostic(input: {
|
|
|
611
524
|
return `${baseError.slice(0, Math.max(0, errorLimit))}${context ? `\n${context}` : ""}`;
|
|
612
525
|
}
|
|
613
526
|
|
|
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
527
|
function resolvePiPackageRootFallback(): string {
|
|
1177
528
|
const root = resolveInstalledPiPackageRoot();
|
|
1178
529
|
if (root) return root;
|
|
@@ -1294,15 +645,17 @@ interface SingleStepContext {
|
|
|
1294
645
|
flatIndex: number;
|
|
1295
646
|
flatStepCount: number;
|
|
1296
647
|
outputFile: string;
|
|
1297
|
-
steerInboxDir?: string;
|
|
1298
|
-
steerCapabilityPath?: string;
|
|
1299
|
-
steerAckDir?: string;
|
|
1300
648
|
transcriptPath?: string;
|
|
1301
649
|
piPackageRoot?: string;
|
|
1302
|
-
|
|
650
|
+
/** Factory the runner creates this step's child session through. */
|
|
651
|
+
childSessions: ChildSessionFactory;
|
|
652
|
+
/** The launching executor's own child runtime; nested route, depth, and ceilings come from here. */
|
|
653
|
+
inheritedChildRuntime?: InheritedChildRuntime;
|
|
1303
654
|
registerInterrupt?: (interrupt: (() => void) | undefined) => void;
|
|
1304
655
|
registerTimeout?: (interrupt: (() => void) | undefined) => void;
|
|
1305
656
|
registerStop?: (stop: (() => void) | undefined) => void;
|
|
657
|
+
/** Receives the live child's steer handler while its session runs. */
|
|
658
|
+
registerSteer?: (steer: StepSteerHandler | undefined) => void;
|
|
1306
659
|
timeoutSignal?: AbortSignal;
|
|
1307
660
|
stopSignal?: AbortSignal;
|
|
1308
661
|
timeoutMessage?: string;
|
|
@@ -1318,7 +671,6 @@ interface SingleStepContext {
|
|
|
1318
671
|
runFanoutBudget?: RunFanoutBudgetDescriptor;
|
|
1319
672
|
onAttemptStart?: (attempt: { model?: string; thinking?: string; contextLimit?: number }) => void;
|
|
1320
673
|
onChildEvent?: (event: ChildEvent) => void;
|
|
1321
|
-
onWriterProcess?: (writer: { state: "none" | "spawning" } | { state: "running"; pid: number }) => void;
|
|
1322
674
|
onExternalProcess?: (process: ExternalProcessStatus) => void;
|
|
1323
675
|
onExternalJob?: (status: ExternalJobStatus) => void;
|
|
1324
676
|
skipAcceptance?: () => boolean;
|
|
@@ -1424,13 +776,11 @@ async function runSingleStepInner(
|
|
|
1424
776
|
model: step.model,
|
|
1425
777
|
modelCandidates: step.modelCandidates,
|
|
1426
778
|
mcpDirectTools: step.mcpDirectTools,
|
|
1427
|
-
mcpConfig: step.mcpConfig,
|
|
1428
|
-
runtimeServerNames: step.runtimeServerNames,
|
|
1429
779
|
cwd: step.cwd ?? ctx.cwd,
|
|
1430
780
|
requireReadTool: Boolean(step.skills?.length),
|
|
1431
781
|
structuredOutput: Boolean(effectiveStructuredOutput),
|
|
1432
782
|
capabilityCeiling: step.capabilityCeiling ?? ctx.capabilityCeiling,
|
|
1433
|
-
inheritedCapabilityCeiling:
|
|
783
|
+
inheritedCapabilityCeiling: ctx.inheritedChildRuntime?.capabilityCeiling,
|
|
1434
784
|
permissionRules: step.permissionRules,
|
|
1435
785
|
}));
|
|
1436
786
|
const contractTools = resolvedTaskToolPlan.explicitToolAllowlist ? resolvedTaskToolPlan.effectiveToolAllowlist : undefined;
|
|
@@ -1650,12 +1000,10 @@ async function runSingleStepInner(
|
|
|
1650
1000
|
let capabilityAudit: import("../shared/capability-ceiling.ts").SubagentCapabilityAudit | undefined;
|
|
1651
1001
|
let launchResolvedExtensions = step.launchResolvedExtensions;
|
|
1652
1002
|
const modelAttempts: ModelAttempt[] = [];
|
|
1653
|
-
const writerProcesses: PiWriterProcessInstanceExitV1[] = [];
|
|
1654
|
-
let writerAttemptCount = 0;
|
|
1655
1003
|
const attemptNotes: string[] = [];
|
|
1656
1004
|
let finalRequiredOutputMissing: boolean | undefined;
|
|
1657
1005
|
const eventsPath = path.join(path.dirname(ctx.outputFile), "events.jsonl");
|
|
1658
|
-
let finalResult:
|
|
1006
|
+
let finalResult: RunChildSessionResult | undefined;
|
|
1659
1007
|
let finalOutputSnapshot: SingleOutputSnapshot | undefined;
|
|
1660
1008
|
let structuredAcceptanceReport: unknown;
|
|
1661
1009
|
let structuredAcceptanceReportError: string | undefined;
|
|
@@ -1667,10 +1015,6 @@ async function runSingleStepInner(
|
|
|
1667
1015
|
let finalMutationEvidence = collectTrackedMutationEvidence(mutationSnapshot, step.cwd ?? ctx.cwd);
|
|
1668
1016
|
|
|
1669
1017
|
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
1018
|
let contextOverflow = false;
|
|
1675
1019
|
let launchWarningsEmitted = false;
|
|
1676
1020
|
let abortRecoveryAttempted = false;
|
|
@@ -1682,7 +1026,7 @@ async function runSingleStepInner(
|
|
|
1682
1026
|
const candidate = candidates[modelIndex];
|
|
1683
1027
|
const expectedModelForVerification = candidate && !(step.skipPrimaryModelVerification && modelIndex === 0) ? candidate : undefined;
|
|
1684
1028
|
try {
|
|
1685
|
-
assertThinkingWithinCeiling({ model: candidate, configThinking: step.thinking, ceiling: step.thinkingCeiling
|
|
1029
|
+
assertThinkingWithinCeiling({ model: candidate, configThinking: step.thinking, ceiling: step.thinkingCeiling, agent: step.agent, runId: ctx.id });
|
|
1686
1030
|
} catch (error) {
|
|
1687
1031
|
const message = error instanceof Error ? error.message : String(error);
|
|
1688
1032
|
return omitUndefinedProperties({ agent: step.agent, output: message, error: message, exitCode: 1, context: step.context, thinkingCeiling: step.thinkingCeiling });
|
|
@@ -1709,12 +1053,10 @@ async function runSingleStepInner(
|
|
|
1709
1053
|
childIndex: ctx.flatIndex,
|
|
1710
1054
|
})
|
|
1711
1055
|
: undefined;
|
|
1712
|
-
|
|
1056
|
+
let watchdogSink: ((event: ChildWatchdogStatusEvent) => void) | undefined;
|
|
1057
|
+
const launch = buildInProcessChildLaunch(omitUndefinedProperties({
|
|
1713
1058
|
parentSessionId: step.parentSessionId,
|
|
1714
1059
|
forkCacheKey: step.context === "fork" ? deriveForkPromptCacheKey(step.parentSessionId) : undefined,
|
|
1715
|
-
baseArgs: ["--mode", "json", "-p"],
|
|
1716
|
-
task: attemptTask,
|
|
1717
|
-
taskDelivery: taskDeliveryOverride,
|
|
1718
1060
|
sessionEnabled,
|
|
1719
1061
|
sessionDir,
|
|
1720
1062
|
sessionFile: step.sessionFile,
|
|
@@ -1733,28 +1075,20 @@ async function runSingleStepInner(
|
|
|
1733
1075
|
systemPrompt: step.systemPrompt ?? "",
|
|
1734
1076
|
systemPromptMode: step.systemPromptMode,
|
|
1735
1077
|
mcpDirectTools: step.mcpDirectTools,
|
|
1736
|
-
|
|
1737
|
-
runtimeServerNames: step.runtimeServerNames,
|
|
1078
|
+
extensionBindings,
|
|
1738
1079
|
capabilityCeiling: step.capabilityCeiling ?? ctx.capabilityCeiling,
|
|
1739
1080
|
cwd: step.cwd ?? ctx.cwd,
|
|
1740
|
-
promptFileStem: step.agent,
|
|
1741
1081
|
intercomSessionName: ctx.childIntercomTarget,
|
|
1742
1082
|
sessionName: childSessionName,
|
|
1743
1083
|
orchestratorIntercomTarget: ctx.orchestratorIntercomTarget,
|
|
1744
1084
|
runId: ctx.id,
|
|
1745
1085
|
childAgentName: step.agent,
|
|
1746
1086
|
childIndex: ctx.flatIndex,
|
|
1747
|
-
|
|
1748
|
-
parentControlInbox: ctx.nestedRoute?.controlInbox,
|
|
1749
|
-
parentRootRunId: ctx.nestedRoute?.rootRunId,
|
|
1750
|
-
parentCapabilityToken: ctx.nestedRoute?.capabilityToken,
|
|
1087
|
+
nestedRoute: ctx.nestedRoute,
|
|
1751
1088
|
runFanoutBudget: ctx.runFanoutBudget ? {
|
|
1752
1089
|
...ctx.runFanoutBudget,
|
|
1753
1090
|
...(step.runFanoutPath ? { parentPath: `${ctx.runFanoutBudget.parentPath ? `${ctx.runFanoutBudget.parentPath}/` : ""}${step.runFanoutPath}` } : {}),
|
|
1754
1091
|
} : undefined,
|
|
1755
|
-
steerInboxDir: ctx.steerInboxDir,
|
|
1756
|
-
steerCapabilityPath: ctx.steerCapabilityPath,
|
|
1757
|
-
steerAckDir: ctx.steerAckDir,
|
|
1758
1092
|
structuredOutput: effectiveStructuredOutput,
|
|
1759
1093
|
toolBudget: step.toolBudget,
|
|
1760
1094
|
permissionRules: step.permissionRules,
|
|
@@ -1762,11 +1096,19 @@ async function runSingleStepInner(
|
|
|
1762
1096
|
? path.join(ctx.artifactsDir, "permission-audit", `${ctx.id}-${ctx.flatIndex}.jsonl`)
|
|
1763
1097
|
: undefined,
|
|
1764
1098
|
childWatchdog,
|
|
1099
|
+
watchdogStatus: (event) => watchdogSink?.(event),
|
|
1765
1100
|
waitToolEnabled: step.waitToolEnabled,
|
|
1766
1101
|
waitToolDefaultTimeoutMs: step.waitToolDefaultTimeoutMs,
|
|
1767
1102
|
thinkingCeiling: step.thinkingCeiling,
|
|
1768
|
-
|
|
1103
|
+
maxSubagentDepth: step.maxSubagentDepth,
|
|
1104
|
+
inherited: ctx.inheritedChildRuntime,
|
|
1105
|
+
host: "runner",
|
|
1769
1106
|
}));
|
|
1107
|
+
if (effectiveStructuredOutput && launch.config.structuredOutput) {
|
|
1108
|
+
// The runner reads the value back from the runtime's files after the run.
|
|
1109
|
+
launch.config.structuredOutput.capture = createStructuredOutputFileCapture(effectiveStructuredOutput);
|
|
1110
|
+
}
|
|
1111
|
+
const { warnings, capabilityAudit: attemptCapabilityAudit } = launch;
|
|
1770
1112
|
if (!launchWarningsEmitted && warnings.length > 0) {
|
|
1771
1113
|
for (const warning of warnings) console.warn(`[pi-subagents] ${warning}`);
|
|
1772
1114
|
launchWarningsEmitted = true;
|
|
@@ -1782,13 +1124,11 @@ async function runSingleStepInner(
|
|
|
1782
1124
|
model: step.model,
|
|
1783
1125
|
modelCandidates: step.modelCandidates,
|
|
1784
1126
|
mcpDirectTools: step.mcpDirectTools,
|
|
1785
|
-
mcpConfig: step.mcpConfig,
|
|
1786
|
-
runtimeServerNames: step.runtimeServerNames,
|
|
1787
1127
|
cwd: step.cwd ?? ctx.cwd,
|
|
1788
1128
|
requireReadTool: Boolean(step.skills?.length),
|
|
1789
1129
|
structuredOutput: Boolean(effectiveStructuredOutput),
|
|
1790
1130
|
capabilityCeiling: step.capabilityCeiling ?? ctx.capabilityCeiling,
|
|
1791
|
-
inheritedCapabilityCeiling:
|
|
1131
|
+
inheritedCapabilityCeiling: ctx.inheritedChildRuntime?.capabilityCeiling,
|
|
1792
1132
|
permissionRules: step.permissionRules,
|
|
1793
1133
|
}));
|
|
1794
1134
|
launchResolvedExtensions = projectLaunchResolvedChildExtensions(toolPlan);
|
|
@@ -1817,59 +1157,49 @@ async function runSingleStepInner(
|
|
|
1817
1157
|
}));
|
|
1818
1158
|
}
|
|
1819
1159
|
capabilityAudit = attemptCapabilityAudit;
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
ctx.
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
step.
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1160
|
+
// Each attempt rewrites the step output log; synchronous appends keep a
|
|
1161
|
+
// retried attempt from interleaving with the previous attempt's flush.
|
|
1162
|
+
fs.writeFileSync(ctx.outputFile, "", "utf-8");
|
|
1163
|
+
const run = await runChildSession(omitUndefinedProperties({
|
|
1164
|
+
factory: ctx.childSessions,
|
|
1165
|
+
launch,
|
|
1166
|
+
prompt: `Task: ${attemptTask}`,
|
|
1167
|
+
childWatchdog,
|
|
1168
|
+
childEventContext: { runId: ctx.id, stepIndex: ctx.flatIndex, agent: step.agent },
|
|
1169
|
+
appendChildEvent: (event) => appendDiagnosticJsonl(eventsPath, JSON.stringify(event), typeof event.type === "string" ? event.type : undefined),
|
|
1170
|
+
writeOutputLine: (line) => {
|
|
1171
|
+
try {
|
|
1172
|
+
fs.appendFileSync(ctx.outputFile, `${line}\n`, "utf-8");
|
|
1173
|
+
} catch {
|
|
1174
|
+
// The output log is observability only.
|
|
1175
|
+
}
|
|
1176
|
+
ctx.orcaProgressTab?.append(`${line}\n`);
|
|
1177
|
+
},
|
|
1178
|
+
registerInterrupt: ctx.registerInterrupt,
|
|
1179
|
+
registerTimeout: ctx.registerTimeout,
|
|
1180
|
+
registerStop: ctx.registerStop,
|
|
1181
|
+
registerSteer: ctx.registerSteer,
|
|
1182
|
+
registerWatchdogStatus: (sink) => { watchdogSink = sink; },
|
|
1183
|
+
timeoutMessage: ctx.timeoutMessage,
|
|
1184
|
+
stopMessage: ctx.stopMessage,
|
|
1185
|
+
onChildEvent: ctx.onChildEvent,
|
|
1832
1186
|
transcriptWriter,
|
|
1833
|
-
ctx.
|
|
1834
|
-
ctx.
|
|
1835
|
-
ctx.registerStop,
|
|
1836
|
-
ctx.stopMessage,
|
|
1837
|
-
ctx.onWriterProcess,
|
|
1838
|
-
ctx.toolTimeoutMs,
|
|
1839
|
-
ctx.deadlineAt,
|
|
1840
|
-
ctx.orcaProgressTab,
|
|
1187
|
+
toolTimeoutMs: ctx.toolTimeoutMs,
|
|
1188
|
+
runDeadlineAt: ctx.deadlineAt,
|
|
1841
1189
|
expectedModelForVerification,
|
|
1842
|
-
step.modelVerificationRegistry,
|
|
1843
|
-
step.mutationTools,
|
|
1844
|
-
);
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
kind: "pi-writer",
|
|
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);
|
|
1190
|
+
modelVerificationRegistry: step.modelVerificationRegistry,
|
|
1191
|
+
mutationTools: step.mutationTools,
|
|
1192
|
+
}));
|
|
1193
|
+
const toolDiagnostic = run.exitCode === 0 && !run.error ? launch.capture.toolDiagnostic() : undefined;
|
|
1194
|
+
const toolAvailabilityError = toolDiagnostic ? formatChildToolDiagnostic(toolDiagnostic) : undefined;
|
|
1195
|
+
const runtimeAcknowledgedExtensions = launch.capture.runtimeAcknowledgedExtensions();
|
|
1861
1196
|
const midToolExitError = run.currentTool
|
|
1862
1197
|
&& isOrdinaryToolForMidToolExit(run.currentTool)
|
|
1863
1198
|
&& !run.interrupted
|
|
1864
1199
|
&& !run.timedOut
|
|
1865
1200
|
&& !run.stopped
|
|
1866
|
-
&& !run.protocolError
|
|
1867
1201
|
&& !toolAvailabilityError
|
|
1868
|
-
? formatMidToolExitError({
|
|
1869
|
-
toolName: run.currentTool,
|
|
1870
|
-
exitCode: run.exitCode,
|
|
1871
|
-
processSignal: run.processSignal,
|
|
1872
|
-
})
|
|
1202
|
+
? formatMidToolExitError({ toolName: run.currentTool })
|
|
1873
1203
|
: undefined;
|
|
1874
1204
|
|
|
1875
1205
|
let structuredOutput: unknown;
|
|
@@ -1956,19 +1286,10 @@ async function runSingleStepInner(
|
|
|
1956
1286
|
: run.error && run.exitCode === 0
|
|
1957
1287
|
? 1
|
|
1958
1288
|
: 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
1289
|
const underlyingError = toolAvailabilityError
|
|
1966
1290
|
?? midToolExitError
|
|
1967
1291
|
?? structuredError
|
|
1968
1292
|
?? 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
1293
|
?? emptyOutputError
|
|
1973
1294
|
?? (missingRequiredOutputAfterMutation ? missingRequiredOutputError : undefined)
|
|
1974
1295
|
?? (hiddenError?.hasError
|
|
@@ -1976,13 +1297,7 @@ async function runSingleStepInner(
|
|
|
1976
1297
|
? `${hiddenError.errorType} failed (exit ${effectiveExitCode}): ${hiddenError.details}`
|
|
1977
1298
|
: `${hiddenError.errorType} failed with exit code ${effectiveExitCode}`
|
|
1978
1299
|
: undefined);
|
|
1979
|
-
const error =
|
|
1980
|
-
underlyingError ?? missingRequiredOutputError ?? completionEvidence.legacyFailureError,
|
|
1981
|
-
{
|
|
1982
|
-
agent: step.agent,
|
|
1983
|
-
ambientExtensionsEnabled: launchResolvedExtensions?.disableAmbientExtensions === false,
|
|
1984
|
-
},
|
|
1985
|
-
);
|
|
1300
|
+
const error = underlyingError ?? missingRequiredOutputError ?? completionEvidence.legacyFailureError;
|
|
1986
1301
|
const attempt: ModelAttempt = omitUndefinedProperties({
|
|
1987
1302
|
model: candidate ?? run.model ?? step.model ?? "default",
|
|
1988
1303
|
success: effectiveExitCode === 0 && !error,
|
|
@@ -1991,7 +1306,7 @@ async function runSingleStepInner(
|
|
|
1991
1306
|
usage: run.usage,
|
|
1992
1307
|
});
|
|
1993
1308
|
modelAttempts.push(attempt);
|
|
1994
|
-
if (!recoveringAbort && candidate
|
|
1309
|
+
if (!recoveringAbort && candidate) attemptedModels.push(candidate);
|
|
1995
1310
|
completionGuardTriggeredFinal = completionEvidence.guardTriggered && !underlyingError && !missingRequiredOutputError;
|
|
1996
1311
|
finalOutputSnapshot = outputSnapshot;
|
|
1997
1312
|
if (step.toolBudget) {
|
|
@@ -2008,11 +1323,10 @@ async function runSingleStepInner(
|
|
|
2008
1323
|
afterCompactionSettlement: run.afterCompactionSettlement === true,
|
|
2009
1324
|
});
|
|
2010
1325
|
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
|
|
1326
|
+
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
1327
|
const abortRecovery = !attempt.success ? planAbortRecovery({
|
|
2013
1328
|
messages: run.messages,
|
|
2014
1329
|
error,
|
|
2015
|
-
processSignal: run.processSignal,
|
|
2016
1330
|
sessionAvailable: Boolean(step.sessionFile && fs.existsSync(step.sessionFile)),
|
|
2017
1331
|
alreadyResumed: abortRecoveryAttempted,
|
|
2018
1332
|
stopped: run.stopped || ctx.stopSignal?.aborted || ctx.skipAcceptance?.(),
|
|
@@ -2043,50 +1357,6 @@ async function runSingleStepInner(
|
|
|
2043
1357
|
}
|
|
2044
1358
|
if (completionEvidence.guardTriggered) break modelAttemptsLoop;
|
|
2045
1359
|
|
|
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
1360
|
const retryableModelFailure = isRetryableModelFailureAttempt({ error, messages: run.messages, toolCount: run.toolCount });
|
|
2091
1361
|
if (retryableModelFailure) recordRetryableModelFailure(candidate ?? run.model ?? step.model, error);
|
|
2092
1362
|
if (isContextOverflow(error)) {
|
|
@@ -2097,7 +1367,6 @@ async function runSingleStepInner(
|
|
|
2097
1367
|
if (!retryableModelFailure || modelIndex === candidates.length - 1) break modelAttemptsLoop;
|
|
2098
1368
|
attemptNotes.push(formatModelAttemptNote(attempt, candidates[modelIndex + 1]));
|
|
2099
1369
|
modelIndex += 1;
|
|
2100
|
-
startupAttemptIndex = 0;
|
|
2101
1370
|
}
|
|
2102
1371
|
|
|
2103
1372
|
const rawOutput = finalResult?.finalOutput ?? "";
|
|
@@ -2125,7 +1394,7 @@ async function runSingleStepInner(
|
|
|
2125
1394
|
? extractChildWrittenOutput(finalResult?.messages, step.outputPath, step.cwd ?? ctx.cwd)
|
|
2126
1395
|
: undefined;
|
|
2127
1396
|
const outputState: SubagentOutputState = finalResult?.outputState === "present"
|
|
2128
|
-
|| (finalResult as (
|
|
1397
|
+
|| (finalResult as (RunChildSessionResult & { structuredOutput?: unknown }) | undefined)?.structuredOutput !== undefined
|
|
2129
1398
|
|| Boolean(childWrittenOutput?.trim())
|
|
2130
1399
|
? "present"
|
|
2131
1400
|
: resolvedOutput.savedPath
|
|
@@ -2227,7 +1496,7 @@ async function runSingleStepInner(
|
|
|
2227
1496
|
...(capabilityAudit ? { capabilityCeiling: capabilityAudit.ceiling, capabilityAudit } : {}),
|
|
2228
1497
|
launchContractDigest: actualLaunchContractDigest,
|
|
2229
1498
|
launchResolvedExtensions,
|
|
2230
|
-
...((finalResult as (
|
|
1499
|
+
...((finalResult as (RunChildSessionResult & { runtimeAcknowledgedExtensions?: RuntimeAcknowledgedChildExtensionsV1 }) | undefined)?.runtimeAcknowledgedExtensions ? { runtimeAcknowledgedExtensions: (finalResult as RunChildSessionResult & { runtimeAcknowledgedExtensions?: RuntimeAcknowledgedChildExtensionsV1 }).runtimeAcknowledgedExtensions } : {}),
|
|
2231
1500
|
...(transcriptWriter ? { transcriptPath: artifactPaths.transcriptPath } : {}),
|
|
2232
1501
|
transcriptError: transcriptWriter?.getError(),
|
|
2233
1502
|
skills: step.skills,
|
|
@@ -2246,7 +1515,6 @@ async function runSingleStepInner(
|
|
|
2246
1515
|
outputState,
|
|
2247
1516
|
exitCode: effectiveFinalExitCode,
|
|
2248
1517
|
error: effectiveFinalError,
|
|
2249
|
-
protocolError: finalResult?.protocolError,
|
|
2250
1518
|
sessionFile: step.sessionFile,
|
|
2251
1519
|
intercomTarget: ctx.childIntercomTarget,
|
|
2252
1520
|
model: finalResult?.model,
|
|
@@ -2264,22 +1532,19 @@ async function runSingleStepInner(
|
|
|
2264
1532
|
interrupted: timedOutAfterAcceptance || stoppedAfterAcceptance ? false : finalResult?.interrupted,
|
|
2265
1533
|
timedOut: timedOutAfterAcceptance ? true : finalResult?.timedOut,
|
|
2266
1534
|
stopped: stoppedAfterAcceptance ? true : finalResult?.stopped,
|
|
2267
|
-
processSignal: finalResult?.processSignal,
|
|
2268
1535
|
timeoutRecovery,
|
|
2269
1536
|
toolBudget,
|
|
2270
1537
|
toolBudgetBlocked: toolBudgetBlocked || undefined,
|
|
2271
1538
|
completionGuardTriggered: completionGuardTriggeredFinal,
|
|
2272
|
-
...((finalResult as (
|
|
2273
|
-
structuredOutput: timedOutAfterAcceptance || stoppedAfterAcceptance ? undefined : (finalResult as (
|
|
1539
|
+
...((finalResult as (RunChildSessionResult & { effects?: import("../../shared/types.ts").EffectsProjection }) | undefined)?.effects ? { effects: (finalResult as RunChildSessionResult & { effects?: import("../../shared/types.ts").EffectsProjection }).effects } : {}),
|
|
1540
|
+
structuredOutput: timedOutAfterAcceptance || stoppedAfterAcceptance ? undefined : (finalResult as (RunChildSessionResult & { structuredOutput?: unknown }) | undefined)?.structuredOutput,
|
|
2274
1541
|
structuredOutputPath: timedOutAfterAcceptance || stoppedAfterAcceptance ? undefined : effectiveStructuredOutput?.outputPath,
|
|
2275
1542
|
structuredOutputSchemaPath: timedOutAfterAcceptance || stoppedAfterAcceptance ? undefined : effectiveStructuredOutput?.schemaPath,
|
|
2276
1543
|
acceptance: effectiveAcceptance,
|
|
2277
1544
|
watchdog: finalResult?.watchdog,
|
|
2278
1545
|
...(capabilityAudit ? { capabilityCeiling: capabilityAudit.ceiling, capabilityAudit } : {}),
|
|
2279
1546
|
launchResolvedExtensions,
|
|
2280
|
-
...((finalResult as (
|
|
2281
|
-
writerProcesses,
|
|
2282
|
-
writerAttemptCount,
|
|
1547
|
+
...((finalResult as (RunChildSessionResult & { runtimeAcknowledgedExtensions?: RuntimeAcknowledgedChildExtensionsV1 }) | undefined)?.runtimeAcknowledgedExtensions ? { runtimeAcknowledgedExtensions: (finalResult as RunChildSessionResult & { runtimeAcknowledgedExtensions?: RuntimeAcknowledgedChildExtensionsV1 }).runtimeAcknowledgedExtensions } : {}),
|
|
2283
1548
|
});
|
|
2284
1549
|
return isAgentContractV1(step.agentContract) ? attachContractProjections(result as unknown as import("../../shared/types.ts").SingleResult) as unknown as typeof result : result;
|
|
2285
1550
|
}
|
|
@@ -2573,7 +1838,7 @@ async function runSingleStepWithTimeout(
|
|
|
2573
1838
|
|
|
2574
1839
|
async function runSubagent(
|
|
2575
1840
|
config: SubagentRunConfig,
|
|
2576
|
-
|
|
1841
|
+
childSessions: ChildSessionFactory,
|
|
2577
1842
|
): Promise<void> {
|
|
2578
1843
|
const { id, steps, resultPath, cwd, placeholder, taskIndex, totalTasks, maxOutput, artifactsDir, artifactConfig } =
|
|
2579
1844
|
config;
|
|
@@ -2593,9 +1858,11 @@ async function runSubagent(
|
|
|
2593
1858
|
const activeChildInterrupts = new Map<number, () => void>();
|
|
2594
1859
|
const activeChildTimeouts = new Map<number, () => void>();
|
|
2595
1860
|
const activeChildStops = new Map<number, () => void>();
|
|
1861
|
+
const activeChildSteers = new Map<number, StepSteerHandler>();
|
|
1862
|
+
/** Steers routed to a running step before its session was created. */
|
|
1863
|
+
const queuedStepSteers = new Map<number, SteerRequest[]>();
|
|
2596
1864
|
const childStopRequests = new Map<number, { childId: string; requestedAt: number }>();
|
|
2597
1865
|
const pendingStepSteers: SteerRequest[] = [];
|
|
2598
|
-
const steeringCapabilities = new Map<number, SteerCapability>();
|
|
2599
1866
|
let interrupted = false;
|
|
2600
1867
|
let currentActivityState: ActivityState | undefined;
|
|
2601
1868
|
let activityTimer: NodeJS.Timeout | undefined;
|
|
@@ -3053,6 +2320,16 @@ async function runSubagent(
|
|
|
3053
2320
|
activeChildStops.set(flatIndex, stop);
|
|
3054
2321
|
if (stopped || childStopRequests.has(flatIndex)) stop();
|
|
3055
2322
|
};
|
|
2323
|
+
const registerStepSteer = (flatIndex: number, steer: StepSteerHandler | undefined): void => {
|
|
2324
|
+
if (!steer) {
|
|
2325
|
+
activeChildSteers.delete(flatIndex);
|
|
2326
|
+
return;
|
|
2327
|
+
}
|
|
2328
|
+
activeChildSteers.set(flatIndex, steer);
|
|
2329
|
+
const queued = queuedStepSteers.get(flatIndex);
|
|
2330
|
+
queuedStepSteers.delete(flatIndex);
|
|
2331
|
+
for (const request of queued ?? []) steerLiveChild(flatIndex, request);
|
|
2332
|
+
};
|
|
3056
2333
|
const interruptActiveChildren = (): void => {
|
|
3057
2334
|
for (const interrupt of [...activeChildInterrupts.values()]) interrupt();
|
|
3058
2335
|
};
|
|
@@ -3505,22 +2782,15 @@ async function runSubagent(
|
|
|
3505
2782
|
if (!step) return { index, state: "failed" as const, reason: "child index out of range" };
|
|
3506
2783
|
if (step.status === "pending") return { index, state: "scheduled" as const };
|
|
3507
2784
|
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
2785
|
return { index, state: "routed" as const };
|
|
3510
2786
|
});
|
|
3511
2787
|
recordSteeringLifecycle(request, targetStates);
|
|
3512
2788
|
emitSteeringEvent("subagent.steer.requested", request, undefined, { targets: targetStates });
|
|
3513
2789
|
for (const target of targetStates) {
|
|
3514
2790
|
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
|
-
}
|
|
2791
|
+
updateSteeringLifecycleTarget(request.id, target.index, "routed", now);
|
|
2792
|
+
emitSteeringEvent("subagent.steer.routed", request, target.index);
|
|
2793
|
+
steerLiveChild(target.index, request);
|
|
3524
2794
|
} else if (target.state === "failed") {
|
|
3525
2795
|
markSteeringAttention(target.index);
|
|
3526
2796
|
emitSteeringEvent("subagent.steer.failed", request, target.index, { reason: target.reason });
|
|
@@ -3532,27 +2802,42 @@ async function runSubagent(
|
|
|
3532
2802
|
statusPayload.lastUpdate = now;
|
|
3533
2803
|
writeStatusPayload();
|
|
3534
2804
|
};
|
|
3535
|
-
|
|
2805
|
+
/** Record the outcome of handing a routed steer to the live child session. */
|
|
2806
|
+
const applySteerDelivery = (requestId: string, index: number, delivery: { state: "delivered" | "queued" | "failed"; message: string }): void => {
|
|
3536
2807
|
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(
|
|
2808
|
+
const request = lifecycle.recent.find((candidate) => candidate.id === requestId);
|
|
2809
|
+
if (!request || !request.targets.some((target) => target.index === index)) return;
|
|
2810
|
+
const late = fs.existsSync(steeringMarkerPath(requestId));
|
|
3540
2811
|
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:
|
|
2812
|
+
if (delivery.state === "delivered") {
|
|
2813
|
+
updateSteeringLifecycleTarget(requestId, index, late ? "late" : "delivered", now, omitUndefinedProperties({ reason: late ? "acknowledged after recovery commit" : undefined }));
|
|
2814
|
+
emitSteeringEvent("subagent.steer.delivered", { type: "steer", id: requestId, ts: now, message: delivery.message }, index, { late, deliveryStatus: "delivered", message: delivery.message });
|
|
2815
|
+
} else if (delivery.state === "queued") {
|
|
2816
|
+
updateSteeringLifecycleTarget(requestId, index, "queued", now);
|
|
2817
|
+
emitSteeringEvent("subagent.steer.queued", { type: "steer", id: requestId, ts: now, message: delivery.message }, index, { deliveryStatus: "queued", message: delivery.message });
|
|
3547
2818
|
} else {
|
|
3548
|
-
markSteeringAttention(
|
|
3549
|
-
updateSteeringLifecycleTarget(
|
|
3550
|
-
emitSteeringEvent("subagent.steer.failed", { type: "steer", id:
|
|
2819
|
+
markSteeringAttention(index);
|
|
2820
|
+
updateSteeringLifecycleTarget(requestId, index, "failed", now, { reason: delivery.message });
|
|
2821
|
+
emitSteeringEvent("subagent.steer.failed", { type: "steer", id: requestId, ts: now, message: delivery.message }, index, { reason: delivery.message });
|
|
3551
2822
|
}
|
|
3552
|
-
emitTerminalSteeringNotice(
|
|
2823
|
+
emitTerminalSteeringNotice(requestId, `Steering failed for run ${id}: ${delivery.message}`);
|
|
3553
2824
|
statusPayload.lastUpdate = now;
|
|
3554
2825
|
writeStatusPayload();
|
|
3555
2826
|
};
|
|
2827
|
+
/** Hand a routed steer to the step's live session, or hold it until the session exists. */
|
|
2828
|
+
const steerLiveChild = (index: number, request: SteerRequest): void => {
|
|
2829
|
+
const steer = activeChildSteers.get(index);
|
|
2830
|
+
if (!steer) {
|
|
2831
|
+
const queued = queuedStepSteers.get(index) ?? [];
|
|
2832
|
+
queued.push(request);
|
|
2833
|
+
queuedStepSteers.set(index, queued);
|
|
2834
|
+
return;
|
|
2835
|
+
}
|
|
2836
|
+
void steer(request).then(
|
|
2837
|
+
(delivery) => applySteerDelivery(request.id, index, delivery),
|
|
2838
|
+
(error) => applySteerDelivery(request.id, index, { state: "failed", message: error instanceof Error ? error.message : String(error) }),
|
|
2839
|
+
);
|
|
2840
|
+
};
|
|
3556
2841
|
const flushPendingStepSteers = (flatIndex: number): void => {
|
|
3557
2842
|
const remaining: SteerRequest[] = [];
|
|
3558
2843
|
for (const request of pendingStepSteers.splice(0)) {
|
|
@@ -3636,6 +2921,7 @@ async function runSubagent(
|
|
|
3636
2921
|
tokens: step.tokens?.total,
|
|
3637
2922
|
toolCount: step.toolCount,
|
|
3638
2923
|
currentTool: step.currentTool,
|
|
2924
|
+
toolCallId: event.toolCallId,
|
|
3639
2925
|
currentToolDurationMs: 0,
|
|
3640
2926
|
currentPath: step.currentPath,
|
|
3641
2927
|
})));
|
|
@@ -3910,23 +3196,6 @@ async function runSubagent(
|
|
|
3910
3196
|
pendingStepSteers.push(request);
|
|
3911
3197
|
}
|
|
3912
3198
|
},
|
|
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
3199
|
});
|
|
3931
3200
|
if (config.deadlineAt !== undefined) {
|
|
3932
3201
|
const remainingMs = Math.max(0, config.deadlineAt - Date.now());
|
|
@@ -3983,7 +3252,7 @@ async function runSubagent(
|
|
|
3983
3252
|
: [undefined]
|
|
3984
3253
|
: model ? [model] : [undefined];
|
|
3985
3254
|
for (const candidate of candidates) {
|
|
3986
|
-
assertThinkingWithinCeiling({ model: candidate, configThinking, ceiling: step.parallel.thinkingCeiling
|
|
3255
|
+
assertThinkingWithinCeiling({ model: candidate, configThinking, ceiling: step.parallel.thinkingCeiling, agent: step.parallel.agent, runId: id });
|
|
3987
3256
|
}
|
|
3988
3257
|
}
|
|
3989
3258
|
if (materialized.collectedOnEmpty) await validateDynamicCollection(step.collect.outputSchema, materialized.collectedOnEmpty);
|
|
@@ -4253,11 +3522,9 @@ async function runSubagent(
|
|
|
4253
3522
|
artifactsDir, artifactConfig, id,
|
|
4254
3523
|
flatIndex: fi, flatStepCount: Math.max(statusPayload.steps.length, 1),
|
|
4255
3524
|
outputFile: path.join(asyncDir, `output-${fi}.log`),
|
|
4256
|
-
steerInboxDir: stepSteerInboxDir(asyncDir, fi),
|
|
4257
|
-
steerCapabilityPath: steerCapabilityPath(asyncDir, fi),
|
|
4258
|
-
steerAckDir: steerAcksDir(asyncDir, fi),
|
|
4259
3525
|
piPackageRoot: config.piPackageRoot,
|
|
4260
|
-
|
|
3526
|
+
childSessions,
|
|
3527
|
+
inheritedChildRuntime: config.inheritedChildRuntime,
|
|
4261
3528
|
childIntercomTarget: config.childIntercomTargets?.[fi],
|
|
4262
3529
|
orchestratorIntercomTarget: config.controlIntercomTarget,
|
|
4263
3530
|
nestedRoute: config.nestedRoute,
|
|
@@ -4266,6 +3533,7 @@ async function runSubagent(
|
|
|
4266
3533
|
registerInterrupt: (interrupt) => registerStepInterrupt(fi, interrupt),
|
|
4267
3534
|
registerTimeout: (interrupt) => registerStepTimeout(fi, interrupt),
|
|
4268
3535
|
registerStop: (stop) => registerStepStop(fi, stop),
|
|
3536
|
+
registerSteer: (steer) => registerStepSteer(fi, steer),
|
|
4269
3537
|
timeoutSignal: timeoutAbortController.signal,
|
|
4270
3538
|
stopSignal: stopAbortController.signal,
|
|
4271
3539
|
trackedMutationEvidenceForCompletionGuard: false,
|
|
@@ -4274,7 +3542,6 @@ async function runSubagent(
|
|
|
4274
3542
|
toolTimeoutMs: task.toolTimeoutMs ?? config.toolTimeoutMs,
|
|
4275
3543
|
onAttemptStart: (attempt) => updateStepModel(fi, attempt.model, attempt.thinking, attempt.contextLimit),
|
|
4276
3544
|
onChildEvent: (event) => updateStepFromChildEvent(fi, event),
|
|
4277
|
-
onWriterProcess,
|
|
4278
3545
|
onExternalProcess: (process) => updateExternalProcess(fi, process),
|
|
4279
3546
|
onExternalJob: (externalJob) => updateExternalJob(fi, externalJob),
|
|
4280
3547
|
skipAcceptance: () => timedOut || stopped || childStopRequests.has(fi),
|
|
@@ -4355,7 +3622,6 @@ async function runSubagent(
|
|
|
4355
3622
|
output: pr.output,
|
|
4356
3623
|
outputState: pr.outputState,
|
|
4357
3624
|
error: pr.error,
|
|
4358
|
-
protocolError: pr.protocolError,
|
|
4359
3625
|
success: pr.stopped !== true && pr.interrupted !== true && pr.exitCode === 0,
|
|
4360
3626
|
exitCode: pr.interrupted === true ? 0 : pr.exitCode,
|
|
4361
3627
|
skipped: pr.skipped,
|
|
@@ -4515,6 +3781,7 @@ async function runSubagent(
|
|
|
4515
3781
|
labels: group.parallel.map((task) => task.lane?.key ?? config.workflowKey ?? task.outputName ?? task.label),
|
|
4516
3782
|
tasks: group.parallel.map((task) => task.task),
|
|
4517
3783
|
provider: config.worktreeProvider,
|
|
3784
|
+
baseRef: config.baseRef,
|
|
4518
3785
|
branchPrefix: config.worktreeBranchPrefix,
|
|
4519
3786
|
setupHook: config.worktreeSetupHook
|
|
4520
3787
|
? omitUndefinedProperties({ hookPath: config.worktreeSetupHook, timeoutMs: config.worktreeSetupHookTimeoutMs })
|
|
@@ -4652,11 +3919,9 @@ async function runSubagent(
|
|
|
4652
3919
|
artifactsDir, artifactConfig, id,
|
|
4653
3920
|
flatIndex: fi, flatStepCount: Math.max(statusPayload.steps.length, 1),
|
|
4654
3921
|
outputFile: path.join(asyncDir, `output-${fi}.log`),
|
|
4655
|
-
steerInboxDir: stepSteerInboxDir(asyncDir, fi),
|
|
4656
|
-
steerCapabilityPath: steerCapabilityPath(asyncDir, fi),
|
|
4657
|
-
steerAckDir: steerAcksDir(asyncDir, fi),
|
|
4658
3922
|
piPackageRoot: config.piPackageRoot,
|
|
4659
|
-
|
|
3923
|
+
childSessions,
|
|
3924
|
+
inheritedChildRuntime: config.inheritedChildRuntime,
|
|
4660
3925
|
childIntercomTarget: config.childIntercomTargets?.[fi],
|
|
4661
3926
|
orchestratorIntercomTarget: config.controlIntercomTarget,
|
|
4662
3927
|
nestedRoute: config.nestedRoute,
|
|
@@ -4665,6 +3930,7 @@ async function runSubagent(
|
|
|
4665
3930
|
registerInterrupt: (interrupt) => registerStepInterrupt(fi, interrupt),
|
|
4666
3931
|
registerTimeout: (interrupt) => registerStepTimeout(fi, interrupt),
|
|
4667
3932
|
registerStop: (stop) => registerStepStop(fi, stop),
|
|
3933
|
+
registerSteer: (steer) => registerStepSteer(fi, steer),
|
|
4668
3934
|
timeoutSignal: timeoutAbortController.signal,
|
|
4669
3935
|
stopSignal: stopAbortController.signal,
|
|
4670
3936
|
trackedMutationEvidenceForCompletionGuard: Boolean(worktreeSetup),
|
|
@@ -4673,7 +3939,6 @@ async function runSubagent(
|
|
|
4673
3939
|
toolTimeoutMs: taskForRun.toolTimeoutMs ?? config.toolTimeoutMs,
|
|
4674
3940
|
onAttemptStart: (attempt) => updateStepModel(fi, attempt.model, attempt.thinking, attempt.contextLimit),
|
|
4675
3941
|
onChildEvent: (event) => updateStepFromChildEvent(fi, event),
|
|
4676
|
-
onWriterProcess,
|
|
4677
3942
|
onExternalProcess: (process) => updateExternalProcess(fi, process),
|
|
4678
3943
|
onExternalJob: (externalJob) => updateExternalJob(fi, externalJob),
|
|
4679
3944
|
skipAcceptance: () => timedOut || stopped || childStopRequests.has(fi),
|
|
@@ -4801,7 +4066,6 @@ async function runSubagent(
|
|
|
4801
4066
|
output: pr.output,
|
|
4802
4067
|
outputState: pr.outputState,
|
|
4803
4068
|
error: pr.error,
|
|
4804
|
-
protocolError: pr.protocolError,
|
|
4805
4069
|
success: pr.stopped !== true && pr.interrupted !== true && pr.exitCode === 0,
|
|
4806
4070
|
exitCode: pr.interrupted === true ? 0 : pr.exitCode,
|
|
4807
4071
|
skipped: pr.skipped,
|
|
@@ -4952,6 +4216,7 @@ async function runSubagent(
|
|
|
4952
4216
|
labels: [seqStep.lane?.key ?? config.workflowKey ?? seqStep.outputName ?? seqStep.label],
|
|
4953
4217
|
tasks: [seqStep.task],
|
|
4954
4218
|
provider: config.worktreeProvider,
|
|
4219
|
+
baseRef: config.baseRef,
|
|
4955
4220
|
branchPrefix: config.worktreeBranchPrefix,
|
|
4956
4221
|
setupHook: config.worktreeSetupHook
|
|
4957
4222
|
? omitUndefinedProperties({ hookPath: config.worktreeSetupHook, timeoutMs: config.worktreeSetupHookTimeoutMs })
|
|
@@ -5019,11 +4284,9 @@ async function runSubagent(
|
|
|
5019
4284
|
artifactsDir, artifactConfig, id,
|
|
5020
4285
|
flatIndex, flatStepCount: Math.max(statusPayload.steps.length, 1),
|
|
5021
4286
|
outputFile: path.join(asyncDir, `output-${flatIndex}.log`),
|
|
5022
|
-
steerInboxDir: stepSteerInboxDir(asyncDir, flatIndex),
|
|
5023
|
-
steerCapabilityPath: steerCapabilityPath(asyncDir, flatIndex),
|
|
5024
|
-
steerAckDir: steerAcksDir(asyncDir, flatIndex),
|
|
5025
4287
|
piPackageRoot: config.piPackageRoot,
|
|
5026
|
-
|
|
4288
|
+
childSessions,
|
|
4289
|
+
inheritedChildRuntime: config.inheritedChildRuntime,
|
|
5027
4290
|
childIntercomTarget: config.childIntercomTargets?.[flatIndex],
|
|
5028
4291
|
orchestratorIntercomTarget: config.controlIntercomTarget,
|
|
5029
4292
|
nestedRoute: config.nestedRoute,
|
|
@@ -5032,6 +4295,7 @@ async function runSubagent(
|
|
|
5032
4295
|
registerInterrupt: (interrupt) => registerStepInterrupt(flatIndex, interrupt),
|
|
5033
4296
|
registerTimeout: (interrupt) => registerStepTimeout(flatIndex, interrupt),
|
|
5034
4297
|
registerStop: (stop) => registerStepStop(flatIndex, stop),
|
|
4298
|
+
registerSteer: (steer) => registerStepSteer(flatIndex, steer),
|
|
5035
4299
|
timeoutSignal: timeoutAbortController.signal,
|
|
5036
4300
|
stopSignal: stopAbortController.signal,
|
|
5037
4301
|
timeoutMessage,
|
|
@@ -5039,7 +4303,6 @@ async function runSubagent(
|
|
|
5039
4303
|
toolTimeoutMs: seqStep.toolTimeoutMs ?? config.toolTimeoutMs,
|
|
5040
4304
|
onAttemptStart: (attempt) => updateStepModel(flatIndex, attempt.model, attempt.thinking, attempt.contextLimit),
|
|
5041
4305
|
onChildEvent: (event) => updateStepFromChildEvent(flatIndex, event),
|
|
5042
|
-
onWriterProcess,
|
|
5043
4306
|
onExternalProcess: (process) => updateExternalProcess(flatIndex, process),
|
|
5044
4307
|
onExternalJob: (externalJob) => updateExternalJob(flatIndex, externalJob),
|
|
5045
4308
|
skipAcceptance: () => timedOut || stopped || childStopRequests.has(flatIndex),
|
|
@@ -5067,7 +4330,6 @@ async function runSubagent(
|
|
|
5067
4330
|
output: stopped || childStopped ? stopMessage : timedOut ? singleResult.output || (timeoutMessage ?? "Subagent timed out.") : singleResult.output,
|
|
5068
4331
|
outputState: singleResult.outputState,
|
|
5069
4332
|
error: stopped || childStopped ? stopMessage : timedOut ? (timeoutMessage ?? "Subagent timed out.") : singleResult.error,
|
|
5070
|
-
protocolError: singleResult.protocolError,
|
|
5071
4333
|
success: !stopped && !childStopped && !timedOut && singleResult.interrupted !== true && singleResult.exitCode === 0,
|
|
5072
4334
|
exitCode: stopped || childStopped ? 1 : timedOut ? 1 : singleResult.interrupted === true ? 0 : singleResult.exitCode,
|
|
5073
4335
|
sessionFile: singleResult.sessionFile,
|
|
@@ -5335,7 +4597,7 @@ async function runSubagent(
|
|
|
5335
4597
|
clearTimeout(timeoutTimer);
|
|
5336
4598
|
timeoutTimer = undefined;
|
|
5337
4599
|
}
|
|
5338
|
-
if (!timedOut && !stopped && !interrupted && config.timeoutMs !== undefined && results.some((result) => result.timedOut === true && result.error
|
|
4600
|
+
if (!timedOut && !stopped && !interrupted && config.timeoutMs !== undefined && timeoutMessage !== undefined && results.some((result) => result.timedOut === true && result.error?.startsWith(timeoutMessage))) {
|
|
5339
4601
|
timedOut = true;
|
|
5340
4602
|
}
|
|
5341
4603
|
const signalTerminated = !stopped && !timedOut && !interrupted && results.some((result) => result.exitCode !== 0 && isUnexplainedProcessSignal(omitUndefinedProperties({
|
|
@@ -5425,7 +4687,6 @@ async function runSubagent(
|
|
|
5425
4687
|
output: r.output,
|
|
5426
4688
|
outputState: r.outputState,
|
|
5427
4689
|
error: r.error,
|
|
5428
|
-
protocolError: r.protocolError,
|
|
5429
4690
|
success: r.success,
|
|
5430
4691
|
skipped: r.skipped || undefined,
|
|
5431
4692
|
interrupted: r.interrupted || undefined,
|
|
@@ -5507,7 +4768,7 @@ async function runSubagent(
|
|
|
5507
4768
|
statusPayload.lastUpdate = Date.now();
|
|
5508
4769
|
}
|
|
5509
4770
|
writeStatusPayload();
|
|
5510
|
-
orcaProgressTab?.finish(statusPayload.state === "complete" ? "completed" : statusPayload.state === "stopped" ? "stopped" : "failed", effectiveSessionFile);
|
|
4771
|
+
await orcaProgressTab?.finish(statusPayload.state === "complete" ? "completed" : statusPayload.state === "stopped" ? "stopped" : "failed", effectiveSessionFile);
|
|
5511
4772
|
appendJsonl(
|
|
5512
4773
|
eventsPath,
|
|
5513
4774
|
JSON.stringify({
|
|
@@ -5542,14 +4803,21 @@ async function runSubagent(
|
|
|
5542
4803
|
}), (filePath, content) => runPersistence.write(filePath, { content }, (_path, payload) => {
|
|
5543
4804
|
fs.writeFileSync(_path, (payload as { content: string }).content, "utf-8");
|
|
5544
4805
|
}));
|
|
5545
|
-
|
|
5546
|
-
|
|
4806
|
+
// The run is committed: release the child sessions first, then wait for the
|
|
4807
|
+
// storage-capacity retries that may still hold the final status or result.
|
|
4808
|
+
await childSessions.dispose().catch((error: unknown) => console.error("Failed to dispose runner child sessions:", error));
|
|
4809
|
+
while (runPersistence.pendingCount() + indexPersistence.pendingCount() > 0) {
|
|
4810
|
+
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
4811
|
+
}
|
|
4812
|
+
runPersistence.dispose();
|
|
4813
|
+
indexPersistence.dispose();
|
|
5547
4814
|
if (config.runnerProcessInstanceId) {
|
|
4815
|
+
// Children run inside this process, so no step has writer processes to prove terminal.
|
|
5548
4816
|
const writers: Record<string, PiWriterProcessInstanceExitV1[]> = {};
|
|
5549
4817
|
const expectedWriters: Record<string, number> = {};
|
|
5550
|
-
for (const
|
|
5551
|
-
writers[String(index)] =
|
|
5552
|
-
expectedWriters[String(index)] =
|
|
4818
|
+
for (const index of results.keys()) {
|
|
4819
|
+
writers[String(index)] = [];
|
|
4820
|
+
expectedWriters[String(index)] = 0;
|
|
5553
4821
|
}
|
|
5554
4822
|
const candidate: ProcessTerminalCandidate = {
|
|
5555
4823
|
version: 1,
|
|
@@ -5631,7 +4899,16 @@ async function runConfiguredSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
5631
4899
|
}
|
|
5632
4900
|
}
|
|
5633
4901
|
}
|
|
5634
|
-
|
|
4902
|
+
const childSessions = await loadRunnerChildSessionFactory(config);
|
|
4903
|
+
try {
|
|
4904
|
+
await runSubagent(config, childSessions);
|
|
4905
|
+
} finally {
|
|
4906
|
+
try {
|
|
4907
|
+
await childSessions.dispose();
|
|
4908
|
+
} catch (error) {
|
|
4909
|
+
console.error("Failed to dispose runner child sessions:", error);
|
|
4910
|
+
}
|
|
4911
|
+
}
|
|
5635
4912
|
} catch (error) {
|
|
5636
4913
|
if (!startupCommitted) {
|
|
5637
4914
|
try {
|
|
@@ -5660,10 +4937,16 @@ async function runConfiguredSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
5660
4937
|
}
|
|
5661
4938
|
|
|
5662
4939
|
function startConfiguredSubagent(config: SubagentRunConfig): void {
|
|
5663
|
-
|
|
5664
|
-
|
|
5665
|
-
|
|
5666
|
-
|
|
4940
|
+
// Child sessions and the extensions loaded into them may leave handles
|
|
4941
|
+
// behind even after shutdown; the run is fully persisted by now, so exit
|
|
4942
|
+
// explicitly instead of waiting for the event loop to drain.
|
|
4943
|
+
runConfiguredSubagent(config).then(
|
|
4944
|
+
() => process.exit(0),
|
|
4945
|
+
(runErr) => {
|
|
4946
|
+
console.error("Subagent runner error:", runErr);
|
|
4947
|
+
process.exit(1);
|
|
4948
|
+
},
|
|
4949
|
+
);
|
|
5667
4950
|
}
|
|
5668
4951
|
|
|
5669
4952
|
const configArg = process.argv[2];
|