pi-subagents 0.65.0 → 0.66.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 +87 -0
- package/README.md +1 -1
- package/agents/researcher.md +23 -13
- package/docs/agents.md +17 -3
- package/docs/configuration.md +34 -0
- package/docs/extension-api.md +94 -0
- package/docs/models.md +58 -1
- package/docs/observability.md +42 -2
- package/docs/tool-reference.md +11 -5
- package/docs/workflows.md +22 -7
- package/package.json +4 -1
- package/runner-server-preload.mjs +13 -0
- package/skills/pi-subagents/SKILL.md +2 -1
- package/skills/pi-subagents/references/execution-controls.md +19 -2
- package/skills/pi-subagents/references/management-authoring-rpc.md +2 -1
- package/skills/pi-subagents/references/multi-lane-orchestration.md +2 -0
- package/src/agents/advertised-agent-prompt.ts +63 -0
- package/src/agents/agent-management.ts +14 -1
- package/src/agents/agent-serializer.ts +2 -0
- package/src/agents/agents.ts +8 -0
- package/src/api/preflight.ts +5 -1
- package/src/api/shared-types.ts +1 -1
- package/src/api/workflow-resources.ts +6 -0
- package/src/extension/config.ts +4 -2
- package/src/extension/index.ts +71 -4
- package/src/extension/public-execution.ts +0 -1
- package/src/extension/rpc.ts +4 -21
- package/src/extension/schemas.ts +9 -7
- package/src/extension/tool-description.ts +10 -5
- package/src/integrations/pi-web-session-liveness.ts +73 -0
- package/src/intercom/native-supervisor-channel.ts +102 -88
- package/src/intercom/supervisor-ui.ts +3 -2
- package/src/missions/workflow-state.ts +37 -16
- package/src/runs/background/active-async-capacity.ts +18 -18
- package/src/runs/background/async-execution.ts +8 -1
- package/src/runs/background/async-job-tracker.ts +35 -3
- package/src/runs/background/async-resume.ts +3 -1
- package/src/runs/background/async-retention.ts +9 -0
- package/src/runs/background/async-status-snapshot.ts +10 -12
- package/src/runs/background/async-status.ts +17 -9
- package/src/runs/background/auto-drain.ts +40 -29
- package/src/runs/background/chain-root-attachment.ts +8 -0
- package/src/runs/background/control-channel.ts +78 -44
- package/src/runs/background/notify.ts +88 -12
- package/src/runs/background/owned-process-tree.ts +6 -6
- package/src/runs/background/process-terminal.ts +23 -23
- package/src/runs/background/retained-nested-route-tracker.ts +96 -0
- package/src/runs/background/run-child-session.ts +62 -33
- package/src/runs/background/run-status.ts +75 -5
- package/src/runs/background/runner-aliases.ts +46 -8
- package/src/runs/background/runner-child-launch.ts +86 -0
- package/src/runs/background/stale-run-reconciler.ts +3 -1
- package/src/runs/background/subagent-runner.ts +430 -208
- package/src/runs/background/subagent-wait.ts +3 -0
- package/src/runs/background/wait-completions.ts +4 -0
- package/src/runs/foreground/async-steering-action.ts +19 -0
- package/src/runs/foreground/execution.ts +116 -26
- package/src/runs/foreground/foreground-history.ts +3 -1
- package/src/runs/foreground/prompt-audit.ts +9 -5
- package/src/runs/foreground/subagent-executor.ts +531 -227
- package/src/runs/foreground/workflow-detach-reconcile.ts +8 -5
- package/src/runs/foreground/workflow-foreground-steering.ts +56 -2
- package/src/runs/shared/acceptance.ts +16 -3
- package/src/runs/shared/agent-contract.ts +1 -1
- package/src/runs/shared/async-status-projection.ts +47 -47
- package/src/runs/shared/child-hooks.ts +151 -2
- package/src/runs/shared/child-launch.ts +18 -13
- package/src/runs/shared/child-session.ts +55 -24
- package/src/runs/shared/child-tool-plan.ts +2 -2
- package/src/runs/shared/completion-evidence.ts +2 -2
- package/src/runs/shared/completion-guard.ts +1 -0
- package/src/runs/shared/host-step-status.ts +11 -11
- package/src/runs/shared/llm-intent-arbiter.ts +30 -20
- package/src/runs/shared/model-exclusions.ts +2 -1
- package/src/runs/shared/model-fallback.ts +41 -8
- package/src/runs/shared/nested-events.ts +8 -8
- package/src/runs/shared/orca-progress-tabs.ts +6 -0
- package/src/runs/shared/parallel-handoff.ts +57 -12
- package/src/runs/shared/parallel-utils.ts +3 -2
- package/src/runs/shared/readonly-drain-observation.ts +42 -0
- package/src/runs/shared/readonly-model-continuation.ts +69 -0
- package/src/runs/shared/readonly-session-evidence.ts +307 -0
- package/src/runs/shared/run-fanout-budget.ts +8 -8
- package/src/runs/shared/runtime-acknowledged-extensions.ts +3 -3
- package/src/runs/shared/subagent-prompt-runtime.ts +13 -3
- package/src/runs/shared/worktree-cleanup-plan.ts +6 -3
- package/src/runs/shared/worktree-setup-command.ts +190 -0
- package/src/runs/shared/worktree.ts +403 -210
- package/src/shared/model-response-aliases.ts +13 -0
- package/src/shared/types.ts +89 -60
- package/src/shared/utils.ts +10 -2
- package/src/shared/watch-strategy.ts +2 -0
- package/src/shared/workflow-child-permit.ts +18 -13
- package/src/tui/fleet-status.ts +1 -1
- package/src/tui/fleet.ts +11 -5
- package/src/tui/render.ts +44 -15
- package/src/workflows/chat-progress.ts +3 -3
- package/src/workflows/scripted-workflow.ts +70 -16
- package/src/workflows/workflow-checklist.ts +15 -18
- package/src/workflows/workflow-child-summary.ts +57 -8
- package/src/workflows/workflow-preflight.ts +19 -19
- package/src/workflows/workflow-receipt.ts +3 -3
- package/src/workflows/workflow-resources.ts +96 -21
- package/src/workflows/workflow-settlement.ts +3 -0
|
@@ -1,8 +1,28 @@
|
|
|
1
1
|
import { spawnSync } from "node:child_process";
|
|
2
|
+
import { EventEmitter } from "node:events";
|
|
2
3
|
import * as fs from "node:fs";
|
|
4
|
+
import { createRequire } from "node:module";
|
|
3
5
|
import * as path from "node:path";
|
|
4
6
|
import { pathToFileURL } from "node:url";
|
|
5
7
|
import type { Message } from "@earendil-works/pi-ai";
|
|
8
|
+
import { arbitrateCompletionGuardRescue, createTaskMutationArbiter } from "../shared/llm-intent-arbiter.ts";
|
|
9
|
+
|
|
10
|
+
// Detached runners skip Pi's CLI proxy setup. Keep fetch on the same Undici dispatcher.
|
|
11
|
+
function ensureProxyAwareHttpDispatcher(): void {
|
|
12
|
+
try {
|
|
13
|
+
// SAFETY: require loads the pinned direct dependency described by these types.
|
|
14
|
+
const undici = createRequire(import.meta.url)("undici") as typeof import("undici");
|
|
15
|
+
const dispatcher = new undici.EnvHttpProxyAgent({ allowH2: false });
|
|
16
|
+
// Fetch rejects stream errors; the listener prevents an unhandled EventEmitter error.
|
|
17
|
+
EventEmitter.prototype.on.call(dispatcher, "error", () => {});
|
|
18
|
+
undici.setGlobalDispatcher(dispatcher);
|
|
19
|
+
undici.install();
|
|
20
|
+
} catch (error) {
|
|
21
|
+
console.error(`[pi-subagents] proxy-aware HTTP dispatcher not installed: ${error instanceof Error ? error.message : String(error)}`);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
const isRunnerEntrypoint = Boolean(process.argv[1] && import.meta.url === pathToFileURL(path.resolve(process.argv[1])).href);
|
|
25
|
+
if (isRunnerEntrypoint) ensureProxyAwareHttpDispatcher();
|
|
6
26
|
import { writeAtomicJson } from "../../shared/atomic-json.ts";
|
|
7
27
|
import { writeAsyncResultFile, writePendingAsyncResultFile } from "./result-files.ts";
|
|
8
28
|
import { createFileCoalescer } from "../../shared/file-coalescer.ts";
|
|
@@ -27,10 +47,10 @@ import {
|
|
|
27
47
|
type AsyncStatus,
|
|
28
48
|
type ChainOutputMap,
|
|
29
49
|
type CostSummary,
|
|
30
|
-
type
|
|
31
|
-
type
|
|
50
|
+
type LaunchResolvedChildExtensions,
|
|
51
|
+
type RuntimeAcknowledgedChildExtensions,
|
|
32
52
|
type ModelAttempt,
|
|
33
|
-
type
|
|
53
|
+
type PiWriterProcessInstanceExit,
|
|
34
54
|
type NestedRouteInfo,
|
|
35
55
|
type NestedRunSummary,
|
|
36
56
|
type ResolvedControlConfig,
|
|
@@ -72,10 +92,14 @@ import {
|
|
|
72
92
|
DEFAULT_GLOBAL_CONCURRENCY_LIMIT,
|
|
73
93
|
Semaphore,
|
|
74
94
|
} from "../shared/parallel-utils.ts";
|
|
75
|
-
import { applyThinkingSuffix,
|
|
76
|
-
import {
|
|
95
|
+
import { applyThinkingSuffix, projectLaunchResolvedChildExtensions, resolvePiLaunchToolPlan } from "../shared/child-tool-plan.ts";
|
|
96
|
+
import type { InheritedChildRuntime } from "../shared/child-launch.ts";
|
|
97
|
+
import { buildRunnerChildLaunch } from "./runner-child-launch.ts";
|
|
98
|
+
import { normalizeExtensionBindings } from "../shared/extension-bindings.ts";
|
|
77
99
|
import type { ChildSessionFactory } from "../shared/child-session.ts";
|
|
78
|
-
import { runChildSession, type ChildEvent, type RunChildSessionResult, type StepSteerHandler } from "./run-child-session.ts";
|
|
100
|
+
import { getSettledReadonlyChild, runChildSession, type ChildEvent, type RunChildSessionInput, type RunChildSessionResult, type StepSteerHandler } from "./run-child-session.ts";
|
|
101
|
+
import { planReadonlyModelContinuation, READONLY_CONTINUATION_PROMPT, type LogicalRecoveryState } from "../shared/readonly-model-continuation.ts";
|
|
102
|
+
import { getReadonlySessionEvidence } from "../shared/readonly-session-evidence.ts";
|
|
79
103
|
import { loadRunnerChildSessionFactory } from "./runner-child-sessions.ts";
|
|
80
104
|
import { SUBAGENT_CHILD_ENV } from "../shared/child-runtime-config.ts";
|
|
81
105
|
import { deriveChildSessionName } from "../../shared/child-session-name.ts";
|
|
@@ -89,7 +113,7 @@ import { collectDynamicResults, DynamicFanoutError, materializeDynamicParallelSt
|
|
|
89
113
|
import { claimRunFanoutBatch, getRunFanoutBudgetSnapshot } from "../shared/run-fanout-budget.ts";
|
|
90
114
|
import { nestedSummaryFromAsyncStatus, projectNestedEvents, resolveNestedAsyncDir, writeNestedEvent } from "../shared/nested-events.ts";
|
|
91
115
|
import { formatModelAttemptNote, formatSubagentModelVerificationError, isContextOverflow, isRetryableModelFailureAttempt, recordRetryableModelFailure } from "../shared/model-fallback.ts";
|
|
92
|
-
import { markProcessTerminalCandidateLeaseRelease, writeProcessTerminalCandidate, type ProcessTerminalCandidate } from "./process-terminal.ts";
|
|
116
|
+
import { markProcessTerminalCandidateLeaseRelease, processTerminalPath, writeProcessTerminalCandidate, type ProcessTerminalCandidate } from "./process-terminal.ts";
|
|
93
117
|
import { createSteeringStatus, recordSteeringRequest, steeringStatus, terminalSteeringNoticeState, updateSteeringTarget } from "./steering.ts";
|
|
94
118
|
import { PROMPT_REDACTED, detectSubagentError, extractTextFromContent, extractToolArgsPreview, formatEmptyTerminalAssistantResponseError, getFinalOutput, hasEmptyTerminalAssistantResponse, readStatus } from "../../shared/utils.ts";
|
|
95
119
|
import { evaluateCompletionMutationGuard, expectsImplementationMutation, hasMutationToolCapability, validateImplementationToolContract } from "../shared/completion-guard.ts";
|
|
@@ -111,6 +135,9 @@ import type { TokenUsage } from "../../shared/types.ts";
|
|
|
111
135
|
import {
|
|
112
136
|
cleanupWorktrees,
|
|
113
137
|
createWorktrees,
|
|
138
|
+
withWorktreeTransaction,
|
|
139
|
+
WorktreeSetupError,
|
|
140
|
+
type WorktreeSetupProgress,
|
|
114
141
|
diffWorktrees,
|
|
115
142
|
findWorktreeTaskCwdConflict,
|
|
116
143
|
formatWorktreeDiffSummary,
|
|
@@ -118,21 +145,20 @@ import {
|
|
|
118
145
|
WORKTREE_AGENT_CWD_PLACEHOLDER,
|
|
119
146
|
type WorktreeSetup,
|
|
120
147
|
} from "../shared/worktree.ts";
|
|
121
|
-
import { findModelInfo, resolveEffectiveThinking } from "../../shared/model-info.ts";
|
|
148
|
+
import { findModelInfo, resolveEffectiveThinking, splitKnownThinkingSuffix } from "../../shared/model-info.ts";
|
|
122
149
|
import { assertThinkingWithinCeiling } from "../../shared/thinking-ceiling.ts";
|
|
123
150
|
import { launchBindingDigest } from "../../shared/launch-contract.ts";
|
|
124
151
|
import { writeInitialProgressFile } from "../../shared/settings.ts";
|
|
125
152
|
import { resolveSubagentIntercomTarget } from "../../intercom/intercom-bridge.ts";
|
|
126
153
|
import { acceptanceFailureMessage, aggregateAcceptanceReport, buildSkippedAcceptanceLedger, evaluateAcceptance, formatAcceptancePrompt, resolveAcceptanceReportMode, resolveEffectiveAcceptance, stripAcceptanceReport } from "../shared/acceptance.ts";
|
|
127
|
-
import { attachContractProjections,
|
|
154
|
+
import { attachContractProjections, isAgentContract } from "../shared/agent-contract.ts";
|
|
128
155
|
import { waitForImportedAsyncRoot } from "./chain-root-attachment.ts";
|
|
129
|
-
import { normalizeExtensionBindings } from "../shared/extension-bindings.ts";
|
|
130
156
|
import { appendRunnerStepsToStatus, consumeChainAppendRequests, countPendingChainAppendRequests, statusStepDescription } from "./chain-append.ts";
|
|
131
157
|
import { asyncStatusChildIdentity } from "../shared/child-identity.ts";
|
|
132
158
|
import { initialToolBudgetState, toolBudgetState } from "../shared/tool-budget.ts";
|
|
133
159
|
import { effectiveToolTimeoutMs, formatToolTimeoutMessage, toolTimeoutCallKey } from "../shared/tool-timeout.ts";
|
|
134
160
|
import { usageBudgetExceededMessage, usageBudgetState } from "../shared/usage-budget.ts";
|
|
135
|
-
import { formatParallelHandoffError, formatParallelHandoffReference, parallelHandoffPath, writeParallelHandoffGroup,
|
|
161
|
+
import { formatParallelHandoffError, formatParallelHandoffReference, parallelHandoffPath, writeParallelHandoffGroup, writeWorktreeSetupHandoff } from "../shared/parallel-handoff.ts";
|
|
136
162
|
import { resolveWatchdogConfig } from "../../watchdog/settings.ts";
|
|
137
163
|
import { acquireSessionLease, type SessionLeaseRequest } from "../shared/session-lease.ts";
|
|
138
164
|
import { buildExternalCliPrompt, runExternalCli } from "../shared/external-cli-runner.ts";
|
|
@@ -207,8 +233,8 @@ interface SubagentRunConfig {
|
|
|
207
233
|
capabilityCeiling?: ResolvedSubagentCapabilityCeiling;
|
|
208
234
|
runFanoutBudget?: RunFanoutBudgetDescriptor;
|
|
209
235
|
launchContractDigest?: string;
|
|
210
|
-
launchResolvedExtensions?:
|
|
211
|
-
runtimeAcknowledgedExtensions?:
|
|
236
|
+
launchResolvedExtensions?: LaunchResolvedChildExtensions;
|
|
237
|
+
runtimeAcknowledgedExtensions?: RuntimeAcknowledgedChildExtensions;
|
|
212
238
|
runnerProcessInstanceId?: string;
|
|
213
239
|
launchBarrierToken?: string;
|
|
214
240
|
parentWorkflowRunId?: string;
|
|
@@ -223,8 +249,8 @@ interface StepResult {
|
|
|
223
249
|
context?: "fresh" | "fork";
|
|
224
250
|
capabilityCeiling?: ResolvedSubagentCapabilityCeiling;
|
|
225
251
|
capabilityAudit?: import("../shared/capability-ceiling.ts").SubagentCapabilityAudit;
|
|
226
|
-
launchResolvedExtensions?:
|
|
227
|
-
runtimeAcknowledgedExtensions?:
|
|
252
|
+
launchResolvedExtensions?: LaunchResolvedChildExtensions;
|
|
253
|
+
runtimeAcknowledgedExtensions?: RuntimeAcknowledgedChildExtensions;
|
|
228
254
|
output: string;
|
|
229
255
|
outputState?: SubagentOutputState;
|
|
230
256
|
error?: string;
|
|
@@ -674,14 +700,17 @@ interface SingleStepContext {
|
|
|
674
700
|
onExternalProcess?: (process: ExternalProcessStatus) => void;
|
|
675
701
|
onExternalJob?: (status: ExternalJobStatus) => void;
|
|
676
702
|
skipAcceptance?: () => boolean;
|
|
677
|
-
|
|
703
|
+
/** Authoritative owner decision after event delivery; undefined includes incomplete run-wide usage. */
|
|
704
|
+
usageBudgetExhausted?: () => boolean | undefined;
|
|
705
|
+
/** Existing run-owned budget configuration; cost allowance is not settled by the live token ledger. */
|
|
706
|
+
usageBudget?: UsageBudgetConfig;
|
|
678
707
|
/** False when sibling work in the same Git worktree could have caused the tracked diff. */
|
|
679
708
|
trackedMutationEvidenceForCompletionGuard?: boolean;
|
|
680
709
|
orcaProgressTab?: OrcaProgressTab;
|
|
681
710
|
}
|
|
682
711
|
|
|
683
712
|
/** Run a single pi agent step, returning output and metadata */
|
|
684
|
-
async function runSingleStepInner(
|
|
713
|
+
export async function runSingleStepInner(
|
|
685
714
|
step: SubagentStep,
|
|
686
715
|
ctx: SingleStepContext,
|
|
687
716
|
): Promise<StepResult & { completionGuardTriggered?: boolean }> {
|
|
@@ -809,8 +838,8 @@ async function runSingleStepInner(
|
|
|
809
838
|
// Derive from the pre-acceptance task so internal acceptance/recovery
|
|
810
839
|
// instructions never leak into the display name.
|
|
811
840
|
const childSessionName = step.sessionName ?? deriveChildSessionName({ agent: step.agent, task, label: step.label });
|
|
812
|
-
if (step.effectiveAcceptance) {
|
|
813
|
-
const acceptancePrompt = formatAcceptancePrompt(step.effectiveAcceptance, { reportOptional:
|
|
841
|
+
if (step.effectiveAcceptance && (step.runner?.type === "external-cli" || step.runner?.type === "external-job")) {
|
|
842
|
+
const acceptancePrompt = formatAcceptancePrompt(step.effectiveAcceptance, { reportOptional: isAgentContract(step.agentContract), structuredOutput: Boolean(step.structuredOutput?.acceptanceReportPath) });
|
|
814
843
|
if (acceptancePrompt) task = `${task}\n${acceptancePrompt}`;
|
|
815
844
|
}
|
|
816
845
|
const sessionEnabled = Boolean(step.sessionFile) || ctx.sessionEnabled;
|
|
@@ -1017,11 +1046,31 @@ async function runSingleStepInner(
|
|
|
1017
1046
|
let modelIndex = 0;
|
|
1018
1047
|
let contextOverflow = false;
|
|
1019
1048
|
let launchWarningsEmitted = false;
|
|
1020
|
-
let
|
|
1049
|
+
let recoveryState: LogicalRecoveryState = "unused";
|
|
1050
|
+
let readonlyContinuation: RunChildSessionInput["readonlyContinuation"];
|
|
1051
|
+
const continuationBudget = () => {
|
|
1052
|
+
if (step.toolBudget) return "tool-budget-configured" as const;
|
|
1053
|
+
// Refresh the authoritative run ledger, already fed synchronously by onChildEvent.
|
|
1054
|
+
const exhausted = ctx.usageBudgetExhausted?.();
|
|
1055
|
+
if (exhausted === true) return "exhausted" as const;
|
|
1056
|
+
if (!ctx.usageBudget) return "unconfigured" as const;
|
|
1057
|
+
if (ctx.usageBudget.costUsd || !ctx.onChildEvent || exhausted !== false) return "unknown" as const;
|
|
1058
|
+
return "available" as const;
|
|
1059
|
+
};
|
|
1060
|
+
const lifecycleAllowsContinuation = () => !ctx.timeoutSignal?.aborted && !ctx.stopSignal?.aborted
|
|
1061
|
+
&& !ctx.skipAcceptance?.() && (ctx.deadlineAt === undefined || Date.now() < ctx.deadlineAt);
|
|
1062
|
+
const canContinue = () => lifecycleAllowsContinuation() && ["available", "unconfigured"].includes(continuationBudget());
|
|
1063
|
+
const failContinuationLaunch = (candidate: string | undefined, error: unknown) => {
|
|
1064
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1065
|
+
modelAttempts.push({ model: candidate ?? "default", success: false, exitCode: 1, error: message });
|
|
1066
|
+
if (candidate) attemptedModels.push(candidate);
|
|
1067
|
+
if (finalResult) finalResult = { ...finalResult, exitCode: 1, error: message };
|
|
1068
|
+
};
|
|
1021
1069
|
let nextAttemptTask = task;
|
|
1022
1070
|
modelAttemptsLoop: while (modelIndex < candidates.length) {
|
|
1023
1071
|
if (ctx.timeoutSignal?.aborted || ctx.stopSignal?.aborted || ctx.skipAcceptance?.()) break;
|
|
1024
|
-
|
|
1072
|
+
if (readonlyContinuation && !canContinue()) break;
|
|
1073
|
+
const recoveringAbort = recoveryState === "abort-recovery";
|
|
1025
1074
|
const attemptTask = nextAttemptTask;
|
|
1026
1075
|
const candidate = candidates[modelIndex];
|
|
1027
1076
|
const expectedModelForVerification = candidate && !(step.skipPrimaryModelVerification && modelIndex === 0) ? candidate : undefined;
|
|
@@ -1029,6 +1078,10 @@ async function runSingleStepInner(
|
|
|
1029
1078
|
assertThinkingWithinCeiling({ model: candidate, configThinking: step.thinking, ceiling: step.thinkingCeiling, agent: step.agent, runId: ctx.id });
|
|
1030
1079
|
} catch (error) {
|
|
1031
1080
|
const message = error instanceof Error ? error.message : String(error);
|
|
1081
|
+
if (readonlyContinuation) {
|
|
1082
|
+
failContinuationLaunch(candidate, error);
|
|
1083
|
+
break modelAttemptsLoop;
|
|
1084
|
+
}
|
|
1032
1085
|
return omitUndefinedProperties({ agent: step.agent, output: message, error: message, exitCode: 1, context: step.context, thinkingCeiling: step.thinkingCeiling });
|
|
1033
1086
|
}
|
|
1034
1087
|
ctx.onAttemptStart?.(omitUndefinedProperties({
|
|
@@ -1054,56 +1107,22 @@ async function runSingleStepInner(
|
|
|
1054
1107
|
})
|
|
1055
1108
|
: undefined;
|
|
1056
1109
|
let watchdogSink: ((event: ChildWatchdogStatusEvent) => void) | undefined;
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
fast: step.fast,
|
|
1074
|
-
modelCandidates: step.modelCandidates,
|
|
1075
|
-
systemPrompt: step.systemPrompt ?? "",
|
|
1076
|
-
systemPromptMode: step.systemPromptMode,
|
|
1077
|
-
mcpDirectTools: step.mcpDirectTools,
|
|
1078
|
-
extensionBindings,
|
|
1079
|
-
capabilityCeiling: step.capabilityCeiling ?? ctx.capabilityCeiling,
|
|
1080
|
-
cwd: step.cwd ?? ctx.cwd,
|
|
1081
|
-
intercomSessionName: ctx.childIntercomTarget,
|
|
1082
|
-
sessionName: childSessionName,
|
|
1083
|
-
orchestratorIntercomTarget: ctx.orchestratorIntercomTarget,
|
|
1084
|
-
runId: ctx.id,
|
|
1085
|
-
childAgentName: step.agent,
|
|
1086
|
-
childIndex: ctx.flatIndex,
|
|
1087
|
-
nestedRoute: ctx.nestedRoute,
|
|
1088
|
-
runFanoutBudget: ctx.runFanoutBudget ? {
|
|
1089
|
-
...ctx.runFanoutBudget,
|
|
1090
|
-
...(step.runFanoutPath ? { parentPath: `${ctx.runFanoutBudget.parentPath ? `${ctx.runFanoutBudget.parentPath}/` : ""}${step.runFanoutPath}` } : {}),
|
|
1091
|
-
} : undefined,
|
|
1092
|
-
structuredOutput: effectiveStructuredOutput,
|
|
1093
|
-
toolBudget: step.toolBudget,
|
|
1094
|
-
permissionRules: step.permissionRules,
|
|
1095
|
-
permissionAuditPath: step.permissionRules && ctx.artifactsDir
|
|
1096
|
-
? path.join(ctx.artifactsDir, "permission-audit", `${ctx.id}-${ctx.flatIndex}.jsonl`)
|
|
1097
|
-
: undefined,
|
|
1098
|
-
childWatchdog,
|
|
1099
|
-
watchdogStatus: (event) => watchdogSink?.(event),
|
|
1100
|
-
waitToolEnabled: step.waitToolEnabled,
|
|
1101
|
-
waitToolDefaultTimeoutMs: step.waitToolDefaultTimeoutMs,
|
|
1102
|
-
thinkingCeiling: step.thinkingCeiling,
|
|
1103
|
-
maxSubagentDepth: step.maxSubagentDepth,
|
|
1104
|
-
inherited: ctx.inheritedChildRuntime,
|
|
1105
|
-
host: "runner",
|
|
1106
|
-
}));
|
|
1110
|
+
let launch: ReturnType<typeof buildRunnerChildLaunch>;
|
|
1111
|
+
try {
|
|
1112
|
+
launch = buildRunnerChildLaunch(step, ctx, {
|
|
1113
|
+
sessionEnabled,
|
|
1114
|
+
sessionDir,
|
|
1115
|
+
model: candidate,
|
|
1116
|
+
sessionName: childSessionName,
|
|
1117
|
+
structuredOutput: effectiveStructuredOutput,
|
|
1118
|
+
childWatchdog,
|
|
1119
|
+
watchdogStatus: (event) => watchdogSink?.(event),
|
|
1120
|
+
});
|
|
1121
|
+
} catch (error) {
|
|
1122
|
+
if (!readonlyContinuation) throw error;
|
|
1123
|
+
failContinuationLaunch(candidate, error);
|
|
1124
|
+
break modelAttemptsLoop;
|
|
1125
|
+
}
|
|
1107
1126
|
if (effectiveStructuredOutput && launch.config.structuredOutput) {
|
|
1108
1127
|
// The runner reads the value back from the runtime's files after the run.
|
|
1109
1128
|
launch.config.structuredOutput.capture = createStructuredOutputFileCapture(effectiveStructuredOutput);
|
|
@@ -1163,6 +1182,9 @@ async function runSingleStepInner(
|
|
|
1163
1182
|
const run = await runChildSession(omitUndefinedProperties({
|
|
1164
1183
|
factory: ctx.childSessions,
|
|
1165
1184
|
launch,
|
|
1185
|
+
collectReadonlyEvidence: true,
|
|
1186
|
+
readonlyContinuation,
|
|
1187
|
+
canContinue,
|
|
1166
1188
|
prompt: `Task: ${attemptTask}`,
|
|
1167
1189
|
childWatchdog,
|
|
1168
1190
|
childEventContext: { runId: ctx.id, stepIndex: ctx.flatIndex, agent: step.agent },
|
|
@@ -1188,6 +1210,7 @@ async function runSingleStepInner(
|
|
|
1188
1210
|
runDeadlineAt: ctx.deadlineAt,
|
|
1189
1211
|
expectedModelForVerification,
|
|
1190
1212
|
modelVerificationRegistry: step.modelVerificationRegistry,
|
|
1213
|
+
modelResponseAliases: step.modelResponseAliases,
|
|
1191
1214
|
mutationTools: step.mutationTools,
|
|
1192
1215
|
}));
|
|
1193
1216
|
const toolDiagnostic = run.exitCode === 0 && !run.error ? launch.capture.toolDiagnostic() : undefined;
|
|
@@ -1242,7 +1265,7 @@ async function runSingleStepInner(
|
|
|
1242
1265
|
&& (!hiddenError?.hasError || hasEmptyTerminalAssistantResponse(run.messages))
|
|
1243
1266
|
? formatEmptyTerminalAssistantResponseError(run.messages)
|
|
1244
1267
|
: undefined;
|
|
1245
|
-
const completionGuardEnabled =
|
|
1268
|
+
const completionGuardEnabled = isAgentContract(step.agentContract) ? step.completionGuard === true : step.completionGuard !== false;
|
|
1246
1269
|
const completionToolPlan = resolvedTaskToolPlan;
|
|
1247
1270
|
const completionTools = completionToolPlan ? (completionToolPlan.explicitToolAllowlist ? completionToolPlan.effectiveToolAllowlist : undefined) : step.tools;
|
|
1248
1271
|
const mutationEvidence = collectTrackedMutationEvidence(mutationSnapshot, step.cwd ?? ctx.cwd);
|
|
@@ -1261,14 +1284,27 @@ async function runSingleStepInner(
|
|
|
1261
1284
|
}))
|
|
1262
1285
|
: undefined;
|
|
1263
1286
|
const mutationAttemptObserved = run.observedMutationAttempt === true || completionMutationEvidence?.attemptedMutation === true;
|
|
1287
|
+
let arbitration = { triggered: completionGuard?.triggered === true && !mutationAttemptObserved, rescued: false };
|
|
1288
|
+
if (arbitration.triggered) {
|
|
1289
|
+
const modelContext = launch.capture.completionIntentContext?.();
|
|
1290
|
+
arbitration = await arbitrateCompletionGuardRescue({
|
|
1291
|
+
guardTriggered: true,
|
|
1292
|
+
task: taskForCompletionGuard,
|
|
1293
|
+
// Construct lazily too: the shared gate refuses overlength tasks before
|
|
1294
|
+
// registry/auth/model work. The child has already shut down normally.
|
|
1295
|
+
arbiter: modelContext ? async (task) => createTaskMutationArbiter(modelContext)?.(task) ?? "unavailable" : undefined,
|
|
1296
|
+
});
|
|
1297
|
+
}
|
|
1264
1298
|
const completionEvidence = planCompletionEvidence({
|
|
1265
1299
|
guard: completionGuard,
|
|
1300
|
+
guardTriggered: arbitration.triggered,
|
|
1301
|
+
arbiterRescued: arbitration.rescued,
|
|
1266
1302
|
completionGuardEnabled,
|
|
1267
1303
|
mutationCapable: hasMutationToolCapability(completionTools, completionToolPlan?.effectiveMcpTools ?? step.mcpDirectTools),
|
|
1268
1304
|
implementationMutationExpected: expectsImplementationMutation(step.agent, taskForCompletionGuard),
|
|
1269
1305
|
mutationAttemptObserved,
|
|
1270
1306
|
mutationEvidence: completionMutationEvidence,
|
|
1271
|
-
|
|
1307
|
+
agentContractEnabled: isAgentContract(step.agentContract),
|
|
1272
1308
|
});
|
|
1273
1309
|
const finalOutputHasPersistableFileContent = run.exitCode === 0 && !run.error && !emptyOutputError && Boolean(stripAcceptanceReport(run.finalOutput).trim());
|
|
1274
1310
|
const requiredOutput = step.outputMode === "file-only" && step.outputPath
|
|
@@ -1328,7 +1364,7 @@ async function runSingleStepInner(
|
|
|
1328
1364
|
messages: run.messages,
|
|
1329
1365
|
error,
|
|
1330
1366
|
sessionAvailable: Boolean(step.sessionFile && fs.existsSync(step.sessionFile)),
|
|
1331
|
-
alreadyResumed:
|
|
1367
|
+
alreadyResumed: recoveryState !== "unused",
|
|
1332
1368
|
stopped: run.stopped || ctx.stopSignal?.aborted || ctx.skipAcceptance?.(),
|
|
1333
1369
|
interrupted: run.interrupted,
|
|
1334
1370
|
timedOut: run.timedOut || ctx.timeoutSignal?.aborted,
|
|
@@ -1348,15 +1384,59 @@ async function runSingleStepInner(
|
|
|
1348
1384
|
if (run.stopped || run.timedOut || ctx.timeoutSignal?.aborted || ctx.stopSignal?.aborted || ctx.skipAcceptance?.()) break modelAttemptsLoop;
|
|
1349
1385
|
if (abortRecovery?.action === "settle" && abortRecovery.diagnostic) break modelAttemptsLoop;
|
|
1350
1386
|
if (attempt.success) break modelAttemptsLoop;
|
|
1387
|
+
// The shared token is consumed before sibling creation; no third dispatch of any kind.
|
|
1388
|
+
if (recoveryState === "readonly-continuation") break modelAttemptsLoop;
|
|
1351
1389
|
if (recoveringAbort) break modelAttemptsLoop;
|
|
1352
1390
|
if (abortRecovery?.action === "resume") {
|
|
1353
|
-
|
|
1391
|
+
recoveryState = "abort-recovery";
|
|
1354
1392
|
nextAttemptTask = abortRecovery.prompt;
|
|
1355
1393
|
attemptNotes.push("[abort-recovery] provider/transport abort after useful progress; resuming the retained child session once.");
|
|
1356
1394
|
continue;
|
|
1357
1395
|
}
|
|
1358
1396
|
if (completionEvidence.guardTriggered) break modelAttemptsLoop;
|
|
1359
1397
|
|
|
1398
|
+
const source = getSettledReadonlyChild(run);
|
|
1399
|
+
if (source) {
|
|
1400
|
+
const captured = launch.capture.completionIntentContext?.();
|
|
1401
|
+
const sourceModel = captured?.model;
|
|
1402
|
+
const retained = getReadonlySessionEvidence(source)!;
|
|
1403
|
+
// Modality/capacity assessment, not another history/provenance validator.
|
|
1404
|
+
// Images and unknown content have no certified token bound in this host slice.
|
|
1405
|
+
const textOnly = (JSON.parse(retained.contextJson) as Message[]).every((message) => typeof message.content === "string"
|
|
1406
|
+
|| (Array.isArray(message.content) && message.content.every((block) => ["text", "thinking", "toolCall"].includes(block.type))));
|
|
1407
|
+
const retainedBytes = Buffer.byteLength(retained.contextJson, "utf8");
|
|
1408
|
+
const resolvedCandidates = candidates.map((reference, index) => {
|
|
1409
|
+
// Only exact registry identities qualify; aliases/default guesses remain ineligible.
|
|
1410
|
+
const base = reference ? splitKnownThinkingSuffix(reference).baseModel : "";
|
|
1411
|
+
const slash = base.indexOf("/");
|
|
1412
|
+
const model = slash > 0 ? captured?.modelRegistry.find(base.slice(0, slash), base.slice(slash + 1)) : undefined;
|
|
1413
|
+
// Reserve the source window for runtime/system/tool overhead, plus a conservative
|
|
1414
|
+
// byte bound for the complete retained text and new prompt; never assume a fit
|
|
1415
|
+
// merely because a 429 request was sent. Equal/smaller windows remain denied.
|
|
1416
|
+
const compatible = textOnly && model && sourceModel && model.input?.includes("text")
|
|
1417
|
+
&& Number.isFinite(sourceModel.contextWindow) && sourceModel.contextWindow > 0
|
|
1418
|
+
&& model.contextWindow >= sourceModel.contextWindow + retainedBytes + Buffer.byteLength(READONLY_CONTINUATION_PROMPT, "utf8")
|
|
1419
|
+
&& model.maxTokens > 0 && model.maxTokens <= sourceModel.maxTokens;
|
|
1420
|
+
return { resolved: model ? { provider: model.provider, model: model.id, api: model.api } : undefined,
|
|
1421
|
+
tried: index <= modelIndex, compatibility: compatible ? "compatible" as const : "unknown" as const };
|
|
1422
|
+
});
|
|
1423
|
+
const plan = planReadonlyModelContinuation({ source, recoveryState, candidates: resolvedCandidates, currentIndex: modelIndex,
|
|
1424
|
+
lifecycleAllowsContinuation: lifecycleAllowsContinuation() && !run.interrupted && !run.stopped && !run.timedOut,
|
|
1425
|
+
effectsAllowContinuation: !run.currentTool && !run.observedMutationAttempt && !mutationEvidence.attemptedMutation
|
|
1426
|
+
&& !structuredError && !effectiveStructuredOutput && !missingRequiredOutputError && !toolAvailabilityError
|
|
1427
|
+
&& !completionEvidence.guardTriggered && !midToolExitError && !hiddenError?.hasError,
|
|
1428
|
+
budget: continuationBudget(), knownContextOverflow: isContextOverflow(error) });
|
|
1429
|
+
if (plan.kind === "continue" && canContinue()) {
|
|
1430
|
+
recoveryState = plan.recoveryState;
|
|
1431
|
+
const selected = resolvedCandidates[plan.candidateIndex]!.resolved!;
|
|
1432
|
+
readonlyContinuation = { source, expected: plan.expected, modelId: `${selected.provider}/${selected.model}` };
|
|
1433
|
+
nextAttemptTask = plan.prompt;
|
|
1434
|
+
modelIndex = plan.candidateIndex;
|
|
1435
|
+
attemptNotes.push(`[readonly-continuation] ${attempt.model} returned HTTP 429 after read-only progress; continuing the retained session once with ${candidates[modelIndex]}.`);
|
|
1436
|
+
continue;
|
|
1437
|
+
}
|
|
1438
|
+
}
|
|
1439
|
+
|
|
1360
1440
|
const retryableModelFailure = isRetryableModelFailureAttempt({ error, messages: run.messages, toolCount: run.toolCount });
|
|
1361
1441
|
if (retryableModelFailure) recordRetryableModelFailure(candidate ?? run.model ?? step.model, error);
|
|
1362
1442
|
if (isContextOverflow(error)) {
|
|
@@ -1438,7 +1518,7 @@ async function runSingleStepInner(
|
|
|
1438
1518
|
cwd: step.cwd ?? ctx.cwd,
|
|
1439
1519
|
signal: combinedAbortSignal([ctx.timeoutSignal, ctx.stopSignal]),
|
|
1440
1520
|
abortMessage: ctx.stopSignal?.aborted ? ctx.stopMessage ?? "Subagent stopped by user." : ctx.timeoutMessage ?? "Subagent timed out.",
|
|
1441
|
-
reportOptional:
|
|
1521
|
+
reportOptional: isAgentContract(step.agentContract),
|
|
1442
1522
|
artifactsDir: ctx.artifactsDir,
|
|
1443
1523
|
runId: ctx.id,
|
|
1444
1524
|
watchdog: finalResult?.watchdog,
|
|
@@ -1454,7 +1534,7 @@ async function runSingleStepInner(
|
|
|
1454
1534
|
: acceptance
|
|
1455
1535
|
: undefined;
|
|
1456
1536
|
const acceptanceFailure = effectiveAcceptance ? acceptanceFailureMessage(effectiveAcceptance) : undefined;
|
|
1457
|
-
const acceptanceCanFailRun = acceptanceFailure && effectiveAcceptance?.explicit && (finalResult?.exitCode ?? 1) === 0 && !finalResult?.interrupted && !timedOutAfterAcceptance && !stoppedAfterAcceptance && !
|
|
1537
|
+
const acceptanceCanFailRun = acceptanceFailure && effectiveAcceptance?.explicit && (finalResult?.exitCode ?? 1) === 0 && !finalResult?.interrupted && !timedOutAfterAcceptance && !stoppedAfterAcceptance && !isAgentContract(step.agentContract);
|
|
1458
1538
|
const effectiveFinalExitCode = timedOutAfterAcceptance || stoppedAfterAcceptance ? 1 : acceptanceCanFailRun ? 1 : finalResult?.exitCode ?? 1;
|
|
1459
1539
|
const intercomDetachReceipt = finalResult?.finalOutput === INTERCOM_DETACH_RECEIPT;
|
|
1460
1540
|
const baseFinalError = stoppedAfterAcceptance
|
|
@@ -1496,7 +1576,7 @@ async function runSingleStepInner(
|
|
|
1496
1576
|
...(capabilityAudit ? { capabilityCeiling: capabilityAudit.ceiling, capabilityAudit } : {}),
|
|
1497
1577
|
launchContractDigest: actualLaunchContractDigest,
|
|
1498
1578
|
launchResolvedExtensions,
|
|
1499
|
-
...((finalResult as (RunChildSessionResult & { runtimeAcknowledgedExtensions?:
|
|
1579
|
+
...((finalResult as (RunChildSessionResult & { runtimeAcknowledgedExtensions?: RuntimeAcknowledgedChildExtensions }) | undefined)?.runtimeAcknowledgedExtensions ? { runtimeAcknowledgedExtensions: (finalResult as RunChildSessionResult & { runtimeAcknowledgedExtensions?: RuntimeAcknowledgedChildExtensions }).runtimeAcknowledgedExtensions } : {}),
|
|
1500
1580
|
...(transcriptWriter ? { transcriptPath: artifactPaths.transcriptPath } : {}),
|
|
1501
1581
|
transcriptError: transcriptWriter?.getError(),
|
|
1502
1582
|
skills: step.skills,
|
|
@@ -1525,6 +1605,7 @@ async function runSingleStepInner(
|
|
|
1525
1605
|
totalCost: costSummaryFromAttempts(modelAttempts),
|
|
1526
1606
|
usage,
|
|
1527
1607
|
artifactPaths,
|
|
1608
|
+
savedOutputPath: finalizedOutput.savedPath,
|
|
1528
1609
|
outputSaveError: [resolvedOutput.saveError, artifactErrors.outputSaveError].filter(Boolean).join("\n") || undefined,
|
|
1529
1610
|
metadataSaveError: artifactErrors.metadataSaveError,
|
|
1530
1611
|
transcriptPath: transcriptWriter ? artifactPaths?.transcriptPath : undefined,
|
|
@@ -1544,9 +1625,9 @@ async function runSingleStepInner(
|
|
|
1544
1625
|
watchdog: finalResult?.watchdog,
|
|
1545
1626
|
...(capabilityAudit ? { capabilityCeiling: capabilityAudit.ceiling, capabilityAudit } : {}),
|
|
1546
1627
|
launchResolvedExtensions,
|
|
1547
|
-
...((finalResult as (RunChildSessionResult & { runtimeAcknowledgedExtensions?:
|
|
1628
|
+
...((finalResult as (RunChildSessionResult & { runtimeAcknowledgedExtensions?: RuntimeAcknowledgedChildExtensions }) | undefined)?.runtimeAcknowledgedExtensions ? { runtimeAcknowledgedExtensions: (finalResult as RunChildSessionResult & { runtimeAcknowledgedExtensions?: RuntimeAcknowledgedChildExtensions }).runtimeAcknowledgedExtensions } : {}),
|
|
1548
1629
|
});
|
|
1549
|
-
return
|
|
1630
|
+
return isAgentContract(step.agentContract) ? attachContractProjections(result as unknown as import("../../shared/types.ts").SingleResult) as unknown as typeof result : result;
|
|
1550
1631
|
}
|
|
1551
1632
|
|
|
1552
1633
|
async function runSingleStep(
|
|
@@ -1637,12 +1718,15 @@ function markParallelGroupSetupFailure(input: {
|
|
|
1637
1718
|
const statusStep = requiredStatusStep(input.statusPayload, flatTaskIndex);
|
|
1638
1719
|
const task = input.group.parallel[taskIndex];
|
|
1639
1720
|
if (!task) throw new Error(`Missing parallel task at index ${taskIndex}`);
|
|
1640
|
-
statusStep.
|
|
1721
|
+
const stopped = statusStep.stopped || statusStep.stopRequested || input.statusPayload.stopped;
|
|
1722
|
+
const paused = !stopped && input.statusPayload.state === "paused";
|
|
1723
|
+
statusStep.status = stopped ? "stopped" : paused ? "paused" : "failed";
|
|
1641
1724
|
statusStep.startedAt = input.failedAt;
|
|
1642
1725
|
statusStep.endedAt = input.failedAt;
|
|
1643
1726
|
statusStep.durationMs = 0;
|
|
1644
|
-
statusStep.exitCode = 1;
|
|
1645
|
-
|
|
1727
|
+
statusStep.exitCode = paused ? 0 : 1;
|
|
1728
|
+
statusStep.error ??= input.setupError;
|
|
1729
|
+
input.results.push(omitUndefinedProperties({ agent: task.agent, context: task.context, output: input.setupError, error: input.setupError, success: false, exitCode: paused ? 0 : 1, sessionFile: task.sessionFile, stopped: stopped || undefined, interrupted: paused || undefined, timedOut: input.statusPayload.timedOut || undefined }));
|
|
1646
1730
|
}
|
|
1647
1731
|
input.statusPayload.currentStep = input.groupStartFlatIndex;
|
|
1648
1732
|
input.statusPayload.lastUpdate = input.failedAt;
|
|
@@ -1799,7 +1883,10 @@ async function runSingleStepWithTimeout(
|
|
|
1799
1883
|
ctx: SingleStepContext,
|
|
1800
1884
|
parentDeadlineAt?: number,
|
|
1801
1885
|
): Promise<SingleStepResult> {
|
|
1802
|
-
if (step.timeoutMs === undefined) return runSingleStep(step, ctx
|
|
1886
|
+
if (step.timeoutMs === undefined) return runSingleStep(step, parentDeadlineAt === undefined ? ctx : {
|
|
1887
|
+
...ctx,
|
|
1888
|
+
deadlineAt: ctx.deadlineAt === undefined ? parentDeadlineAt : Math.min(ctx.deadlineAt, parentDeadlineAt),
|
|
1889
|
+
});
|
|
1803
1890
|
|
|
1804
1891
|
const parentRemainingMs = parentDeadlineAt === undefined ? undefined : Math.max(0, parentDeadlineAt - Date.now());
|
|
1805
1892
|
const timeoutMs = parentRemainingMs === undefined ? step.timeoutMs : Math.min(step.timeoutMs, parentRemainingMs);
|
|
@@ -1836,7 +1923,7 @@ async function runSingleStepWithTimeout(
|
|
|
1836
1923
|
}
|
|
1837
1924
|
}
|
|
1838
1925
|
|
|
1839
|
-
async function runSubagent(
|
|
1926
|
+
export async function runSubagent(
|
|
1840
1927
|
config: SubagentRunConfig,
|
|
1841
1928
|
childSessions: ChildSessionFactory,
|
|
1842
1929
|
): Promise<void> {
|
|
@@ -1874,6 +1961,8 @@ async function runSubagent(
|
|
|
1874
1961
|
const stopMessage = "Subagent stopped by user.";
|
|
1875
1962
|
const timeoutAbortController = new AbortController();
|
|
1876
1963
|
const stopAbortController = new AbortController();
|
|
1964
|
+
const setupInterruptController = new AbortController();
|
|
1965
|
+
const setupSignal = AbortSignal.any([timeoutAbortController.signal, stopAbortController.signal, setupInterruptController.signal]);
|
|
1877
1966
|
let previousCumulativeTokens: TokenUsage = { input: 0, output: 0, total: 0 };
|
|
1878
1967
|
let latestSessionFile: string | undefined;
|
|
1879
1968
|
|
|
@@ -2040,20 +2129,29 @@ async function runSubagent(
|
|
|
2040
2129
|
},
|
|
2041
2130
|
onError: (error, filePath) => console.error(`Failed to update async run index '${filePath}':`, error),
|
|
2042
2131
|
});
|
|
2043
|
-
const queueActiveRunIndex = (): void => {
|
|
2044
|
-
const state =
|
|
2132
|
+
const queueActiveRunIndex = (status: AsyncStatus): void => {
|
|
2133
|
+
const state = status.state;
|
|
2045
2134
|
if (state === lastIndexedStatusState && indexPersistence.pendingCount() === 0) return;
|
|
2046
|
-
indexPersistence.write(asyncDir, { state, toolCallId:
|
|
2135
|
+
indexPersistence.write(asyncDir, { state, toolCallId: status.toolCallId }, (_filePath, payload) => {
|
|
2047
2136
|
const indexPayload = payload as { state: AsyncStatus["state"]; toolCallId?: string };
|
|
2048
2137
|
updateActiveRunIndex(asyncDir, indexPayload.state, indexPayload.toolCallId, { retryCapacityErrors: true });
|
|
2049
2138
|
});
|
|
2050
2139
|
};
|
|
2140
|
+
let finalResultCommitted = false;
|
|
2141
|
+
let finalResultPublication: { resolve(): void; reject(error: unknown): void } | undefined;
|
|
2051
2142
|
const runPersistence = createCapacityResilientJsonWriter({
|
|
2052
2143
|
keepAlive: true,
|
|
2053
|
-
onSuccess: (filePath) => {
|
|
2054
|
-
if (filePath === statusPath) queueActiveRunIndex();
|
|
2144
|
+
onSuccess: (filePath, payload) => {
|
|
2145
|
+
if (filePath === statusPath) queueActiveRunIndex(payload as AsyncStatus);
|
|
2146
|
+
if (filePath === resultPath && finalResultPublication) {
|
|
2147
|
+
finalResultCommitted = true;
|
|
2148
|
+
finalResultPublication.resolve();
|
|
2149
|
+
}
|
|
2150
|
+
},
|
|
2151
|
+
onError: (error, filePath) => {
|
|
2152
|
+
console.error(`Failed to persist async run state '${filePath}':`, error);
|
|
2153
|
+
if (filePath === resultPath) finalResultPublication?.reject(error);
|
|
2055
2154
|
},
|
|
2056
|
-
onError: (error, filePath) => console.error(`Failed to persist async run state '${filePath}':`, error),
|
|
2057
2155
|
});
|
|
2058
2156
|
try {
|
|
2059
2157
|
fs.mkdirSync(asyncDir, { recursive: true });
|
|
@@ -2061,7 +2159,7 @@ async function runSubagent(
|
|
|
2061
2159
|
if (!isStorageCapacityError(error)) throw error;
|
|
2062
2160
|
console.error(`Failed to prepare async run storage '${asyncDir}' while storage is full:`, error);
|
|
2063
2161
|
}
|
|
2064
|
-
runPersistence.write(statusPath, statusPayload);
|
|
2162
|
+
runPersistence.write(statusPath, { ...statusPayload });
|
|
2065
2163
|
|
|
2066
2164
|
let pendingParallelUsageCost: CostSummary = { inputTokens: 0, outputTokens: 0, costUsd: 0 };
|
|
2067
2165
|
const currentUsageTotals = (): CostSummary => {
|
|
@@ -2080,6 +2178,13 @@ async function runSubagent(
|
|
|
2080
2178
|
setOptionalProperty(statusPayload, "usageBudget", usageBudgetState(config.usageBudget, currentUsageTotals()));
|
|
2081
2179
|
return statusPayload.usageBudget;
|
|
2082
2180
|
};
|
|
2181
|
+
// Continuation admission only: the existing ledger has no in-flight cost or
|
|
2182
|
+
// external/import usage coverage. Never change ordinary budget enforcement.
|
|
2183
|
+
let continuationUsageUncertain = config.steps.some(isDynamicRunnerGroup) || flatSteps.some((step) => Boolean(step.runner || step.importAsyncRoot));
|
|
2184
|
+
const continuationUsageBudgetExhausted = (): boolean | undefined => {
|
|
2185
|
+
const exhausted = refreshUsageBudget()?.exhausted === true;
|
|
2186
|
+
return exhausted ? true : config.usageBudget && continuationUsageUncertain ? undefined : false;
|
|
2187
|
+
};
|
|
2083
2188
|
const emitNestedSelfEvent = (type: "subagent.nested.updated" | "subagent.nested.completed"): void => {
|
|
2084
2189
|
if (!config.nestedRoute || !config.nestedSelf) return;
|
|
2085
2190
|
try {
|
|
@@ -2134,7 +2239,6 @@ async function runSubagent(
|
|
|
2134
2239
|
for (const node of graph.nodes) updateNode(node);
|
|
2135
2240
|
statusPayload.workflowGraph = graph;
|
|
2136
2241
|
};
|
|
2137
|
-
let finalResultCommitted = false;
|
|
2138
2242
|
const statusResultState = (): AsyncStatus["state"] | undefined => {
|
|
2139
2243
|
if (statusPayload.state === "running" || statusPayload.state === "queued") return undefined;
|
|
2140
2244
|
return statusPayload.state;
|
|
@@ -2196,14 +2300,15 @@ async function runSubagent(
|
|
|
2196
2300
|
}), (filePath, payload) => writePendingAsyncResultFile(filePath, payload as Record<string, unknown>));
|
|
2197
2301
|
};
|
|
2198
2302
|
const writeStatusPayloadNow = (): void => {
|
|
2303
|
+
if (finalResultPublication) return;
|
|
2199
2304
|
refreshWorkflowGraph();
|
|
2200
2305
|
writeRecoverableStatusResult();
|
|
2201
|
-
runPersistence.write(statusPath, statusPayload);
|
|
2306
|
+
runPersistence.write(statusPath, { ...statusPayload });
|
|
2202
2307
|
emitNestedSelfEvent(statusPayload.state === "running" || statusPayload.state === "queued" ? "subagent.nested.updated" : "subagent.nested.completed");
|
|
2203
2308
|
};
|
|
2204
2309
|
const statusWriteCoalescer = createFileCoalescer(writeStatusPayloadNow, 100);
|
|
2205
2310
|
const writeStatusPayload = (immediate = true): void => {
|
|
2206
|
-
if (immediate || statusPayload.state !== "running"
|
|
2311
|
+
if (immediate || statusPayload.state !== "running") {
|
|
2207
2312
|
if (!statusWriteCoalescer.flush(statusPath)) writeStatusPayloadNow();
|
|
2208
2313
|
return;
|
|
2209
2314
|
}
|
|
@@ -2859,6 +2964,7 @@ async function runSubagent(
|
|
|
2859
2964
|
const updateStepFromChildEvent = (flatIndex: number, event: ChildEvent): void => {
|
|
2860
2965
|
const step = statusPayload.steps[flatIndex];
|
|
2861
2966
|
if (!step) return;
|
|
2967
|
+
const previousActivityState = step.activityState;
|
|
2862
2968
|
const now = Date.now();
|
|
2863
2969
|
statusPayload.currentStep = flatIndex;
|
|
2864
2970
|
if (isChildWatchdogStatusEvent(event)) {
|
|
@@ -2993,6 +3099,10 @@ async function runSubagent(
|
|
|
2993
3099
|
appendRecentStepOutput(step, stripAcceptanceReport(extractTextFromContent(event.message.content)).split("\n").slice(-10));
|
|
2994
3100
|
step.turnCount = (step.turnCount ?? 0) + 1;
|
|
2995
3101
|
const usage = event.message.usage;
|
|
3102
|
+
if (config.usageBudget) {
|
|
3103
|
+
const known = (value: unknown) => typeof value === "number" && Number.isFinite(value) && value >= 0;
|
|
3104
|
+
if (!known(usage?.input ?? usage?.inputTokens) || !known(usage?.output ?? usage?.outputTokens)) continuationUsageUncertain = true;
|
|
3105
|
+
}
|
|
2996
3106
|
if (usage) {
|
|
2997
3107
|
const input = usage.input ?? usage.inputTokens ?? 0;
|
|
2998
3108
|
const output = usage.output ?? usage.outputTokens ?? 0;
|
|
@@ -3012,7 +3122,8 @@ async function runSubagent(
|
|
|
3012
3122
|
statusPayload.lastActivityAt = now;
|
|
3013
3123
|
statusPayload.lastUpdate = now;
|
|
3014
3124
|
maybeEmitActiveLongRunning(flatIndex, now);
|
|
3015
|
-
|
|
3125
|
+
// A sibling may keep aggregate attention unchanged; publish this step's transition.
|
|
3126
|
+
writeStatusPayload(step.activityState !== previousActivityState);
|
|
3016
3127
|
};
|
|
3017
3128
|
const updateRunnerActivityState = (now: number): boolean => {
|
|
3018
3129
|
if (!controlConfig.enabled) return false;
|
|
@@ -3084,10 +3195,55 @@ async function runSubagent(
|
|
|
3084
3195
|
activityTimer.unref?.();
|
|
3085
3196
|
}
|
|
3086
3197
|
|
|
3198
|
+
const publishSetupUnknown = (progress: WorktreeSetupProgress): void => {
|
|
3199
|
+
if (!progress.unknown) return;
|
|
3200
|
+
const proof = {
|
|
3201
|
+
version: 1 as const, state: "unknown" as const, runId: id,
|
|
3202
|
+
runnerProcessInstanceId: config.runnerProcessInstanceId ?? "unknown",
|
|
3203
|
+
reason: "process-tree-unverified" as const,
|
|
3204
|
+
diagnostic: `Worktree setup settlement unknown: ${progress.unknown}; manual reconciliation required; ${parallelHandoffPath(asyncDir)}`,
|
|
3205
|
+
};
|
|
3206
|
+
statusPayload.processTerminal = proof;
|
|
3207
|
+
// Publish the sticky proof independently of in-process child writer counts.
|
|
3208
|
+
writeAtomicJson(processTerminalPath(asyncDir), proof);
|
|
3209
|
+
};
|
|
3210
|
+
const finalizeWorktree = async (setup: WorktreeSetup, stepIndex: number, flatStartIndex: number, action: () => void, deadlineAt?: number): Promise<void> => {
|
|
3211
|
+
let admitted = false;
|
|
3212
|
+
try { await withWorktreeTransaction(() => {
|
|
3213
|
+
if (deadlineAt !== undefined && Date.now() >= deadlineAt) throw new Error("Run deadline expired before worktree cleanup");
|
|
3214
|
+
admitted = true;
|
|
3215
|
+
action();
|
|
3216
|
+
}); }
|
|
3217
|
+
catch (error) {
|
|
3218
|
+
if (admitted) throw error;
|
|
3219
|
+
const reason = `Worktree finalization retained; manual reconciliation required: ${error instanceof Error ? error.message : String(error)}`;
|
|
3220
|
+
statusPayload.parallelHandoff = writeParallelHandoffGroup({
|
|
3221
|
+
manifestPath: parallelHandoffPath(asyncDir), runId: id,
|
|
3222
|
+
mode: (config.resultMode ?? statusPayload.mode) === "parallel" ? "parallel" : (config.resultMode ?? statusPayload.mode) === "single" ? "single" : "chain",
|
|
3223
|
+
source: "async", cwd, stepIndex, flatStartIndex, setup, diffs: [], results: [],
|
|
3224
|
+
cleanup: { state: "partial", pruned: false, errors: [reason], tasks: setup.worktrees.map((worktree) => ({
|
|
3225
|
+
index: worktree.index, path: worktree.path, branch: worktree.branch, provider: worktree.provider, naming: worktree.naming,
|
|
3226
|
+
worktreeRemoved: false, branchRemoved: false, preserved: true, reason,
|
|
3227
|
+
})) },
|
|
3228
|
+
});
|
|
3229
|
+
previousOutput = [previousOutput, reason, formatParallelHandoffReference(statusPayload.parallelHandoff)].filter(Boolean).join("\n\n");
|
|
3230
|
+
writeStatusPayload();
|
|
3231
|
+
}
|
|
3232
|
+
};
|
|
3233
|
+
const cleanupRemainingWorktree = (setup: WorktreeSetup, stepIndex: number, flatStartIndex: number) => finalizeWorktree(setup, stepIndex, flatStartIndex, () => {
|
|
3234
|
+
const cleanup = cleanupWorktrees(setup);
|
|
3235
|
+
statusPayload.parallelHandoff = writeParallelHandoffGroup({
|
|
3236
|
+
manifestPath: parallelHandoffPath(asyncDir), runId: id,
|
|
3237
|
+
mode: (config.resultMode ?? statusPayload.mode) === "parallel" ? "parallel" : (config.resultMode ?? statusPayload.mode) === "single" ? "single" : "chain",
|
|
3238
|
+
source: "async", cwd, stepIndex, flatStartIndex, setup, diffs: [], results: [], cleanup,
|
|
3239
|
+
});
|
|
3240
|
+
writeStatusPayload();
|
|
3241
|
+
}, config.deadlineAt);
|
|
3087
3242
|
const interruptRunner = () => {
|
|
3088
3243
|
consumeInterruptRequest(asyncDir);
|
|
3089
3244
|
if (interrupted || statusPayload.state !== "running") return;
|
|
3090
3245
|
interrupted = true;
|
|
3246
|
+
setupInterruptController.abort();
|
|
3091
3247
|
const now = Date.now();
|
|
3092
3248
|
statusPayload.state = "paused";
|
|
3093
3249
|
currentActivityState = undefined;
|
|
@@ -3321,14 +3477,14 @@ async function runSubagent(
|
|
|
3321
3477
|
cwd,
|
|
3322
3478
|
signal: combinedAbortSignal([timeoutAbortController.signal, stopAbortController.signal]),
|
|
3323
3479
|
abortMessage: stopAbortController.signal.aborted ? stopMessage : timeoutMessage ?? "Subagent timed out.",
|
|
3324
|
-
reportOptional:
|
|
3480
|
+
reportOptional: isAgentContract(step.agentContract),
|
|
3325
3481
|
}))
|
|
3326
3482
|
: undefined;
|
|
3327
3483
|
const groupStopped = stopped || stopAbortController.signal.aborted;
|
|
3328
3484
|
const groupTimedOut = !groupStopped && (timedOut || timeoutAbortController.signal.aborted);
|
|
3329
3485
|
const effectiveGroupAcceptance = groupTimedOut || groupStopped ? undefined : groupAcceptance;
|
|
3330
3486
|
if (placeholder && effectiveGroupAcceptance) placeholder.acceptance = effectiveGroupAcceptance;
|
|
3331
|
-
const groupAcceptanceFailure = effectiveGroupAcceptance && (!
|
|
3487
|
+
const groupAcceptanceFailure = effectiveGroupAcceptance && (!isAgentContract(step.agentContract) || step.gateOn === "acceptance") ? acceptanceFailureMessage(effectiveGroupAcceptance) : undefined;
|
|
3332
3488
|
if (groupTimedOut || groupStopped || groupAcceptanceFailure) {
|
|
3333
3489
|
const errorMessage = groupStopped ? stopMessage : groupTimedOut ? timeoutMessage ?? "Subagent timed out." : groupAcceptanceFailure!;
|
|
3334
3490
|
statusPayload.state = groupStopped ? "stopped" : "failed";
|
|
@@ -3545,7 +3701,8 @@ async function runSubagent(
|
|
|
3545
3701
|
onExternalProcess: (process) => updateExternalProcess(fi, process),
|
|
3546
3702
|
onExternalJob: (externalJob) => updateExternalJob(fi, externalJob),
|
|
3547
3703
|
skipAcceptance: () => timedOut || stopped || childStopRequests.has(fi),
|
|
3548
|
-
usageBudgetExhausted:
|
|
3704
|
+
usageBudgetExhausted: continuationUsageBudgetExhausted,
|
|
3705
|
+
usageBudget: config.usageBudget,
|
|
3549
3706
|
orcaProgressTab,
|
|
3550
3707
|
}), config.deadlineAt);
|
|
3551
3708
|
const taskEndTime = Date.now();
|
|
@@ -3661,7 +3818,7 @@ async function runSubagent(
|
|
|
3661
3818
|
const failures = parallelResults.filter((result) => result.exitCode !== 0 && result.exitCode !== -1);
|
|
3662
3819
|
const acceptanceFailures = parallelResults
|
|
3663
3820
|
.map((result, originalIndex) => ({ result, originalIndex, task: dynamicSteps[originalIndex] }))
|
|
3664
|
-
.filter(({ result, task }) =>
|
|
3821
|
+
.filter(({ result, task }) => isAgentContract(task?.agentContract ?? step.agentContract) && task?.gateOn === "acceptance" && result.acceptance?.status === "rejected");
|
|
3665
3822
|
if (acceptanceFailures.length > 0) {
|
|
3666
3823
|
const message = acceptanceFailures
|
|
3667
3824
|
.map(({ result, originalIndex }) => `Dynamic item ${originalIndex + 1} (${result.agent}, key ${materialized.items[originalIndex]?.key ?? originalIndex}) acceptance rejected: ${(result.acceptance ? acceptanceFailureMessage(result.acceptance) : undefined) ?? "acceptance rejected"}`)
|
|
@@ -3691,13 +3848,13 @@ async function runSubagent(
|
|
|
3691
3848
|
cwd,
|
|
3692
3849
|
signal: combinedAbortSignal([timeoutAbortController.signal, stopAbortController.signal]),
|
|
3693
3850
|
abortMessage: stopAbortController.signal.aborted ? stopMessage : timeoutMessage ?? "Subagent timed out.",
|
|
3694
|
-
reportOptional:
|
|
3851
|
+
reportOptional: isAgentContract(step.agentContract),
|
|
3695
3852
|
}))
|
|
3696
3853
|
: undefined;
|
|
3697
3854
|
const groupStopped = stopped || stopAbortController.signal.aborted;
|
|
3698
3855
|
const groupTimedOut = !groupStopped && (timedOut || timeoutAbortController.signal.aborted);
|
|
3699
3856
|
const effectiveGroupAcceptance = groupTimedOut || groupStopped ? undefined : groupAcceptance;
|
|
3700
|
-
const groupAcceptanceFailure = effectiveDynamicGroupAcceptance.explicit && effectiveGroupAcceptance && (!
|
|
3857
|
+
const groupAcceptanceFailure = effectiveDynamicGroupAcceptance.explicit && effectiveGroupAcceptance && (!isAgentContract(step.agentContract) || step.gateOn === "acceptance") ? acceptanceFailureMessage(effectiveGroupAcceptance) : undefined;
|
|
3701
3858
|
const groupError = groupStopped ? stopMessage : groupTimedOut ? timeoutMessage ?? "Subagent timed out." : groupAcceptanceFailure;
|
|
3702
3859
|
markDynamicGraphGroup(stepIndex, groupError ? groupStopped ? "stopped" : "failed" : "completed", groupError, effectiveGroupAcceptance);
|
|
3703
3860
|
if (groupError) {
|
|
@@ -3776,7 +3933,9 @@ async function runSubagent(
|
|
|
3776
3933
|
break;
|
|
3777
3934
|
}
|
|
3778
3935
|
try {
|
|
3779
|
-
worktreeSetup = createWorktrees(cwd, `${id}-s${stepIndex}`, group.parallel.length, omitUndefinedProperties({
|
|
3936
|
+
worktreeSetup = await createWorktrees(cwd, `${id}-s${stepIndex}`, group.parallel.length, omitUndefinedProperties({
|
|
3937
|
+
signal: setupSignal,
|
|
3938
|
+
deadlineAt: config.deadlineAt,
|
|
3780
3939
|
agents: group.parallel.map((task) => task.agent),
|
|
3781
3940
|
labels: group.parallel.map((task) => task.lane?.key ?? config.workflowKey ?? task.outputName ?? task.label),
|
|
3782
3941
|
tasks: group.parallel.map((task) => task.task),
|
|
@@ -3787,9 +3946,10 @@ async function runSubagent(
|
|
|
3787
3946
|
? omitUndefinedProperties({ hookPath: config.worktreeSetupHook, timeoutMs: config.worktreeSetupHookTimeoutMs })
|
|
3788
3947
|
: undefined,
|
|
3789
3948
|
baseDir: config.worktreeBaseDir,
|
|
3790
|
-
|
|
3791
|
-
|
|
3792
|
-
const
|
|
3949
|
+
onProgress: (progress) => {
|
|
3950
|
+
publishSetupUnknown(progress);
|
|
3951
|
+
for (const worktree of progress.setup.worktrees) setStatusWorktreeReference(requiredStatusStep(statusPayload, groupStartFlatIndex + worktree.index), worktree);
|
|
3952
|
+
const pendingHandoff = writeWorktreeSetupHandoff({
|
|
3793
3953
|
manifestPath: parallelHandoffPath(asyncDir),
|
|
3794
3954
|
runId: id,
|
|
3795
3955
|
mode: (config.resultMode ?? statusPayload.mode) === "parallel" ? "parallel" : "chain",
|
|
@@ -3797,16 +3957,25 @@ async function runSubagent(
|
|
|
3797
3957
|
cwd,
|
|
3798
3958
|
stepIndex,
|
|
3799
3959
|
flatStartIndex: groupStartFlatIndex,
|
|
3800
|
-
|
|
3960
|
+
progress,
|
|
3801
3961
|
laneBindings: handoffWorkflowKey || config.lane ? [{ index: groupStartFlatIndex, taskIndex: 0, ...(handoffWorkflowKey ? { workflowKey: handoffWorkflowKey } : {}), ...(handoffChildRunId ? { runId: handoffChildRunId } : {}), ...(config.lane ? { lane: config.lane } : {}) }] : undefined,
|
|
3802
3962
|
});
|
|
3963
|
+
if (!pendingHandoff) return;
|
|
3803
3964
|
statusPayload.parallelHandoff = pendingHandoff;
|
|
3804
3965
|
statusPayload.lastUpdate = Date.now();
|
|
3805
3966
|
writeStatusPayload();
|
|
3806
3967
|
},
|
|
3807
3968
|
}));
|
|
3969
|
+
if (config.deadlineAt !== undefined && Date.now() >= config.deadlineAt) timeoutRunner();
|
|
3970
|
+
if (setupSignal.aborted) throw new Error(stopped ? stopMessage : timedOut ? timeoutMessage ?? "Subagent timed out." : "Subagent paused during worktree setup.");
|
|
3808
3971
|
} catch (error) {
|
|
3972
|
+
if (worktreeSetup) await cleanupRemainingWorktree(worktreeSetup, stepIndex, groupStartFlatIndex);
|
|
3973
|
+
if (config.deadlineAt !== undefined && Date.now() >= config.deadlineAt) timeoutRunner();
|
|
3809
3974
|
const setupError = error instanceof Error ? error.message : String(error);
|
|
3975
|
+
if (error instanceof WorktreeSetupError) publishSetupUnknown(error.snapshot);
|
|
3976
|
+
for (let index = groupStartFlatIndex; index < groupStartFlatIndex + group.parallel.length; index++) {
|
|
3977
|
+
if (childStopRequests.has(index)) markChildStopped(index);
|
|
3978
|
+
}
|
|
3810
3979
|
const failedAt = Date.now();
|
|
3811
3980
|
markParallelGroupSetupFailure({
|
|
3812
3981
|
statusPayload,
|
|
@@ -3942,7 +4111,8 @@ async function runSubagent(
|
|
|
3942
4111
|
onExternalProcess: (process) => updateExternalProcess(fi, process),
|
|
3943
4112
|
onExternalJob: (externalJob) => updateExternalJob(fi, externalJob),
|
|
3944
4113
|
skipAcceptance: () => timedOut || stopped || childStopRequests.has(fi),
|
|
3945
|
-
usageBudgetExhausted:
|
|
4114
|
+
usageBudgetExhausted: continuationUsageBudgetExhausted,
|
|
4115
|
+
usageBudget: config.usageBudget,
|
|
3946
4116
|
orcaProgressTab,
|
|
3947
4117
|
}), config.deadlineAt);
|
|
3948
4118
|
if (task.sessionFile) {
|
|
@@ -4120,47 +4290,50 @@ async function runSubagent(
|
|
|
4120
4290
|
})),
|
|
4121
4291
|
);
|
|
4122
4292
|
if (worktreeSetup) {
|
|
4123
|
-
const
|
|
4124
|
-
if (captured.summary) previousOutput = `${previousOutput}\n\n${captured.summary}`;
|
|
4293
|
+
const setup = worktreeSetup;
|
|
4125
4294
|
worktreeFinalized = true;
|
|
4126
|
-
|
|
4127
|
-
|
|
4128
|
-
|
|
4129
|
-
|
|
4130
|
-
|
|
4131
|
-
|
|
4132
|
-
|
|
4133
|
-
|
|
4134
|
-
|
|
4135
|
-
|
|
4136
|
-
|
|
4137
|
-
|
|
4138
|
-
|
|
4139
|
-
|
|
4140
|
-
|
|
4141
|
-
|
|
4142
|
-
|
|
4143
|
-
|
|
4144
|
-
|
|
4145
|
-
|
|
4146
|
-
|
|
4147
|
-
|
|
4148
|
-
|
|
4149
|
-
|
|
4150
|
-
|
|
4151
|
-
|
|
4152
|
-
|
|
4153
|
-
|
|
4154
|
-
|
|
4155
|
-
|
|
4156
|
-
|
|
4157
|
-
|
|
4158
|
-
|
|
4159
|
-
|
|
4160
|
-
|
|
4161
|
-
|
|
4162
|
-
|
|
4163
|
-
|
|
4295
|
+
await finalizeWorktree(setup, stepIndex, groupStartFlatIndex, () => {
|
|
4296
|
+
const captured = captureParallelWorktreeDiffs(setup, asyncDir, stepIndex, group);
|
|
4297
|
+
if (captured.summary) previousOutput = `${previousOutput}\n\n${captured.summary}`;
|
|
4298
|
+
const manifestPath = parallelHandoffPath(asyncDir);
|
|
4299
|
+
const handoff = {
|
|
4300
|
+
manifestPath,
|
|
4301
|
+
runId: id,
|
|
4302
|
+
mode: (config.resultMode ?? statusPayload.mode) === "parallel" ? "parallel" as const : "chain" as const,
|
|
4303
|
+
source: "async" as const,
|
|
4304
|
+
cwd,
|
|
4305
|
+
stepIndex,
|
|
4306
|
+
flatStartIndex: groupStartFlatIndex,
|
|
4307
|
+
setup,
|
|
4308
|
+
diffs: captured.diffs,
|
|
4309
|
+
results: parallelResults.map((result) => ({
|
|
4310
|
+
agent: result.agent,
|
|
4311
|
+
...(handoffWorkflowKey ? { workflowKey: handoffWorkflowKey } : {}),
|
|
4312
|
+
...(handoffChildRunId ? { runId: handoffChildRunId } : {}),
|
|
4313
|
+
...(config.lane ? { lane: config.lane } : {}),
|
|
4314
|
+
status: result.stopped || (result.exitCode !== 0 && isUnexplainedProcessSignal(omitUndefinedProperties({
|
|
4315
|
+
processSignal: result.processSignal,
|
|
4316
|
+
interrupted: result.interrupted,
|
|
4317
|
+
timedOut: result.timedOut,
|
|
4318
|
+
stopped: result.stopped,
|
|
4319
|
+
}))) ? "stopped" as const : result.interrupted ? "paused" as const : result.exitCode === 0 ? "completed" as const : "failed" as const,
|
|
4320
|
+
summary: result.output || result.error || "(no output)",
|
|
4321
|
+
...(result.artifactPaths?.outputPath ? { outputPath: result.artifactPaths.outputPath } : {}),
|
|
4322
|
+
...(result.structuredOutput !== undefined ? { structuredOutput: result.structuredOutput } : {}),
|
|
4323
|
+
...(result.structuredOutputPath ? { structuredOutputPath: result.structuredOutputPath } : {}),
|
|
4324
|
+
...(result.sessionFile ? { sessionPath: result.sessionFile } : {}),
|
|
4325
|
+
})),
|
|
4326
|
+
};
|
|
4327
|
+
try {
|
|
4328
|
+
writeParallelHandoffGroup(handoff);
|
|
4329
|
+
const cleanup = cleanupWorktrees(setup, { kind: "preserve", capturedDiffs: captured.diffs, handoffManifestPath: manifestPath });
|
|
4330
|
+
statusPayload.parallelHandoff = writeParallelHandoffGroup({ ...handoff, cleanup });
|
|
4331
|
+
previousOutput = `${previousOutput}\n\n${formatParallelHandoffReference(statusPayload.parallelHandoff)}`;
|
|
4332
|
+
} catch (error) {
|
|
4333
|
+
previousOutput = `${previousOutput}\n\n${formatParallelHandoffError(error)}`;
|
|
4334
|
+
}
|
|
4335
|
+
writeStatusPayload();
|
|
4336
|
+
});
|
|
4164
4337
|
}
|
|
4165
4338
|
|
|
4166
4339
|
appendJsonl(eventsPath, JSON.stringify({
|
|
@@ -4169,12 +4342,12 @@ async function runSubagent(
|
|
|
4169
4342
|
runId: id,
|
|
4170
4343
|
stepIndex,
|
|
4171
4344
|
success: parallelResults.every((r) => r.exitCode === 0 || r.exitCode === -1)
|
|
4172
|
-
&& parallelResults.every((result, index) => !(
|
|
4345
|
+
&& parallelResults.every((result, index) => !(isAgentContract(group.parallel[index]?.agentContract) && group.parallel[index]?.gateOn === "acceptance" && result.acceptance?.status === "rejected")),
|
|
4173
4346
|
}));
|
|
4174
4347
|
|
|
4175
4348
|
const acceptanceGateFailure = parallelResults
|
|
4176
4349
|
.map((result, index) => ({ result, index, task: group.parallel[index] }))
|
|
4177
|
-
.find(({ result, task }) =>
|
|
4350
|
+
.find(({ result, task }) => isAgentContract(task?.agentContract) && task?.gateOn === "acceptance" && result.acceptance?.status === "rejected");
|
|
4178
4351
|
if (acceptanceGateFailure) {
|
|
4179
4352
|
statusPayload.error = (acceptanceGateFailure.result.acceptance ? acceptanceFailureMessage(acceptanceGateFailure.result.acceptance) : undefined) ?? "Parallel acceptance gate rejected the step.";
|
|
4180
4353
|
writeStatusPayload();
|
|
@@ -4184,7 +4357,7 @@ async function runSubagent(
|
|
|
4184
4357
|
break;
|
|
4185
4358
|
}
|
|
4186
4359
|
} finally {
|
|
4187
|
-
if (worktreeSetup && !worktreeFinalized)
|
|
4360
|
+
if (worktreeSetup && !worktreeFinalized) await cleanupRemainingWorktree(worktreeSetup, stepIndex, groupStartFlatIndex);
|
|
4188
4361
|
}
|
|
4189
4362
|
} else {
|
|
4190
4363
|
const seqStep = step as SubagentStep;
|
|
@@ -4211,7 +4384,9 @@ async function runSubagent(
|
|
|
4211
4384
|
let singleWorktreeSetup: WorktreeSetup | undefined;
|
|
4212
4385
|
if (seqStep.worktree) {
|
|
4213
4386
|
try {
|
|
4214
|
-
singleWorktreeSetup = createWorktrees(cwd, `${id}-s${stepIndex}`, 1, omitUndefinedProperties({
|
|
4387
|
+
singleWorktreeSetup = await createWorktrees(cwd, `${id}-s${stepIndex}`, 1, omitUndefinedProperties({
|
|
4388
|
+
signal: setupSignal,
|
|
4389
|
+
deadlineAt: config.deadlineAt,
|
|
4215
4390
|
agents: [seqStep.agent],
|
|
4216
4391
|
labels: [seqStep.lane?.key ?? config.workflowKey ?? seqStep.outputName ?? seqStep.label],
|
|
4217
4392
|
tasks: [seqStep.task],
|
|
@@ -4222,12 +4397,13 @@ async function runSubagent(
|
|
|
4222
4397
|
? omitUndefinedProperties({ hookPath: config.worktreeSetupHook, timeoutMs: config.worktreeSetupHookTimeoutMs })
|
|
4223
4398
|
: undefined,
|
|
4224
4399
|
baseDir: config.worktreeBaseDir,
|
|
4225
|
-
|
|
4226
|
-
|
|
4400
|
+
onProgress: (progress) => {
|
|
4401
|
+
publishSetupUnknown(progress);
|
|
4402
|
+
const worktree = progress.setup.worktrees[0];
|
|
4227
4403
|
if (worktree) {
|
|
4228
4404
|
setStatusWorktreeReference(requiredStatusStep(statusPayload, flatIndex), worktree);
|
|
4229
4405
|
}
|
|
4230
|
-
const pendingHandoff =
|
|
4406
|
+
const pendingHandoff = writeWorktreeSetupHandoff({
|
|
4231
4407
|
manifestPath: parallelHandoffPath(asyncDir),
|
|
4232
4408
|
runId: id,
|
|
4233
4409
|
mode: "single",
|
|
@@ -4235,19 +4411,47 @@ async function runSubagent(
|
|
|
4235
4411
|
cwd,
|
|
4236
4412
|
stepIndex,
|
|
4237
4413
|
flatStartIndex: flatIndex,
|
|
4238
|
-
|
|
4414
|
+
progress,
|
|
4239
4415
|
laneBindings: handoffWorkflowKey || config.lane ? [{ index: flatIndex, taskIndex: 0, ...(handoffWorkflowKey ? { workflowKey: handoffWorkflowKey } : {}), ...(handoffChildRunId ? { runId: handoffChildRunId } : {}), ...(config.lane ? { lane: config.lane } : {}) }] : undefined,
|
|
4240
4416
|
});
|
|
4417
|
+
if (!pendingHandoff) return;
|
|
4241
4418
|
statusPayload.parallelHandoff = pendingHandoff;
|
|
4242
4419
|
statusPayload.lastUpdate = Date.now();
|
|
4243
4420
|
writeStatusPayload();
|
|
4244
4421
|
},
|
|
4245
4422
|
}));
|
|
4246
4423
|
} catch (error) {
|
|
4247
|
-
if (
|
|
4248
|
-
|
|
4424
|
+
if (error instanceof WorktreeSetupError) publishSetupUnknown(error.snapshot);
|
|
4425
|
+
if (config.deadlineAt !== undefined && Date.now() >= config.deadlineAt) timeoutRunner();
|
|
4426
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
4427
|
+
if (childStopRequests.has(flatIndex)) markChildStopped(flatIndex);
|
|
4428
|
+
const statusStep = requiredStatusStep(statusPayload, flatIndex);
|
|
4429
|
+
statusStep.status = stopped || childStopRequests.has(flatIndex) ? "stopped" : interrupted ? "paused" : "failed";
|
|
4430
|
+
statusStep.error ??= message;
|
|
4431
|
+
statusStep.exitCode = interrupted ? 0 : 1;
|
|
4432
|
+
statusStep.endedAt = Date.now();
|
|
4433
|
+
results.push(stopped ? stoppedStepResult(seqStep.agent, seqStep.context, seqStep.sessionName)
|
|
4434
|
+
: childStopRequests.has(flatIndex) ? childStopResult(flatIndex, seqStep.agent, seqStep.context)
|
|
4435
|
+
: timedOut ? timedOutStepResult(seqStep.agent, seqStep.context, seqStep.sessionName)
|
|
4436
|
+
: interrupted ? pausedStepResult(seqStep.agent, seqStep.context, seqStep.sessionName)
|
|
4437
|
+
: { agent: seqStep.agent, output: message, error: message, exitCode: 1, success: false });
|
|
4438
|
+
statusPayload.error ??= message;
|
|
4439
|
+
writeStatusPayload();
|
|
4440
|
+
flatIndex++;
|
|
4441
|
+
break;
|
|
4249
4442
|
}
|
|
4250
4443
|
}
|
|
4444
|
+
if (singleWorktreeSetup && config.deadlineAt !== undefined && Date.now() >= config.deadlineAt) timeoutRunner();
|
|
4445
|
+
if (singleWorktreeSetup && (timedOut || stopped || interrupted || childStopRequests.has(flatIndex))) {
|
|
4446
|
+
await cleanupRemainingWorktree(singleWorktreeSetup, stepIndex, flatIndex);
|
|
4447
|
+
results.push(stopped ? stoppedStepResult(seqStep.agent, seqStep.context, seqStep.sessionName)
|
|
4448
|
+
: childStopRequests.has(flatIndex) ? childStopResult(flatIndex, seqStep.agent, seqStep.context)
|
|
4449
|
+
: timedOut ? timedOutStepResult(seqStep.agent, seqStep.context, seqStep.sessionName)
|
|
4450
|
+
: pausedStepResult(seqStep.agent, seqStep.context, seqStep.sessionName));
|
|
4451
|
+
if (interrupted) requiredStatusStep(statusPayload, flatIndex).status = "paused";
|
|
4452
|
+
flatIndex++;
|
|
4453
|
+
continue;
|
|
4454
|
+
}
|
|
4251
4455
|
const singleCwd = singleWorktreeSetup?.worktrees[0]?.agentCwd ?? cwd;
|
|
4252
4456
|
const stepStartTime = Date.now();
|
|
4253
4457
|
statusPayload.currentStep = flatIndex;
|
|
@@ -4306,11 +4510,12 @@ async function runSubagent(
|
|
|
4306
4510
|
onExternalProcess: (process) => updateExternalProcess(flatIndex, process),
|
|
4307
4511
|
onExternalJob: (externalJob) => updateExternalJob(flatIndex, externalJob),
|
|
4308
4512
|
skipAcceptance: () => timedOut || stopped || childStopRequests.has(flatIndex),
|
|
4309
|
-
usageBudgetExhausted:
|
|
4513
|
+
usageBudgetExhausted: continuationUsageBudgetExhausted,
|
|
4514
|
+
usageBudget: config.usageBudget,
|
|
4310
4515
|
orcaProgressTab,
|
|
4311
4516
|
}), config.deadlineAt);
|
|
4312
4517
|
} catch (error) {
|
|
4313
|
-
if (singleWorktreeSetup)
|
|
4518
|
+
if (singleWorktreeSetup) await cleanupRemainingWorktree(singleWorktreeSetup, stepIndex, flatIndex);
|
|
4314
4519
|
throw error;
|
|
4315
4520
|
}
|
|
4316
4521
|
if (seqStep.sessionFile) {
|
|
@@ -4342,6 +4547,7 @@ async function runSubagent(
|
|
|
4342
4547
|
totalCost: singleResult.totalCost,
|
|
4343
4548
|
usage: singleResult.usage,
|
|
4344
4549
|
artifactPaths: singleResult.artifactPaths,
|
|
4550
|
+
savedOutputPath: singleResult.savedOutputPath,
|
|
4345
4551
|
transcriptPath: singleResult.transcriptPath,
|
|
4346
4552
|
transcriptError: singleResult.transcriptError,
|
|
4347
4553
|
effects: singleResult.effects,
|
|
@@ -4462,48 +4668,51 @@ async function runSubagent(
|
|
|
4462
4668
|
}));
|
|
4463
4669
|
if (stopped || childStopped) appendTerminalChildStatusEvent(flatIndex, stepEndTime);
|
|
4464
4670
|
if (singleWorktreeSetup && !singleResult.detached) {
|
|
4465
|
-
const
|
|
4466
|
-
|
|
4467
|
-
|
|
4468
|
-
|
|
4469
|
-
manifestPath
|
|
4470
|
-
|
|
4471
|
-
|
|
4472
|
-
|
|
4473
|
-
|
|
4474
|
-
|
|
4475
|
-
|
|
4476
|
-
|
|
4477
|
-
|
|
4478
|
-
|
|
4479
|
-
|
|
4480
|
-
|
|
4481
|
-
|
|
4482
|
-
|
|
4483
|
-
|
|
4484
|
-
|
|
4485
|
-
|
|
4486
|
-
|
|
4487
|
-
|
|
4488
|
-
|
|
4489
|
-
|
|
4490
|
-
|
|
4491
|
-
|
|
4492
|
-
|
|
4493
|
-
|
|
4494
|
-
|
|
4495
|
-
|
|
4496
|
-
|
|
4497
|
-
|
|
4498
|
-
|
|
4499
|
-
|
|
4500
|
-
|
|
4501
|
-
|
|
4502
|
-
|
|
4503
|
-
|
|
4504
|
-
|
|
4505
|
-
|
|
4506
|
-
|
|
4671
|
+
const setup = singleWorktreeSetup;
|
|
4672
|
+
await finalizeWorktree(setup, stepIndex, flatIndex, () => {
|
|
4673
|
+
const diffs = diffWorktrees(setup, [seqStep.agent], path.join(asyncDir, "worktree-diffs", `step-${stepIndex}`));
|
|
4674
|
+
const diffSummary = formatWorktreeDiffSummary(diffs);
|
|
4675
|
+
const manifestPath = parallelHandoffPath(asyncDir);
|
|
4676
|
+
const handoff = {
|
|
4677
|
+
manifestPath,
|
|
4678
|
+
runId: id,
|
|
4679
|
+
mode: "single" as const,
|
|
4680
|
+
source: "async" as const,
|
|
4681
|
+
cwd,
|
|
4682
|
+
stepIndex,
|
|
4683
|
+
flatStartIndex: flatIndex,
|
|
4684
|
+
setup,
|
|
4685
|
+
diffs,
|
|
4686
|
+
results: [{
|
|
4687
|
+
agent: singleResult.agent,
|
|
4688
|
+
...(handoffWorkflowKey ? { workflowKey: handoffWorkflowKey } : {}),
|
|
4689
|
+
...(handoffChildRunId ? { runId: handoffChildRunId } : {}),
|
|
4690
|
+
...(config.lane ? { lane: config.lane } : {}),
|
|
4691
|
+
status: singleResult.stopped ? "stopped" as const : singleResult.interrupted ? "paused" as const : singleResult.exitCode === 0 ? "completed" as const : "failed" as const,
|
|
4692
|
+
summary: singleResult.output || singleResult.error || "(no output)",
|
|
4693
|
+
...(singleResult.artifactPaths?.outputPath ? { outputPath: singleResult.artifactPaths.outputPath } : {}),
|
|
4694
|
+
...(singleResult.structuredOutput !== undefined ? { structuredOutput: singleResult.structuredOutput } : {}),
|
|
4695
|
+
...(singleResult.structuredOutputPath ? { structuredOutputPath: singleResult.structuredOutputPath } : {}),
|
|
4696
|
+
...(singleResult.sessionFile ? { sessionPath: singleResult.sessionFile } : {}),
|
|
4697
|
+
}],
|
|
4698
|
+
};
|
|
4699
|
+
try {
|
|
4700
|
+
writeParallelHandoffGroup(handoff);
|
|
4701
|
+
const cleanup = cleanupWorktrees(setup, {
|
|
4702
|
+
kind: "preserve",
|
|
4703
|
+
capturedDiffs: diffs,
|
|
4704
|
+
handoffManifestPath: manifestPath,
|
|
4705
|
+
...(config.parentWorkflowRunId && singleResult.sessionFile && fs.existsSync(singleResult.sessionFile) && !singleResult.stopped
|
|
4706
|
+
? { cleanupBlocker: "retained child resume requires managed worktree cwd" }
|
|
4707
|
+
: {}),
|
|
4708
|
+
});
|
|
4709
|
+
statusPayload.parallelHandoff = writeParallelHandoffGroup({ ...handoff, cleanup });
|
|
4710
|
+
previousOutput = [previousOutput, diffSummary, formatParallelHandoffReference(statusPayload.parallelHandoff)].filter(Boolean).join("\n\n");
|
|
4711
|
+
} catch (error) {
|
|
4712
|
+
previousOutput = [previousOutput, diffSummary, formatParallelHandoffError(error)].filter(Boolean).join("\n\n");
|
|
4713
|
+
}
|
|
4714
|
+
writeStatusPayload();
|
|
4715
|
+
});
|
|
4507
4716
|
}
|
|
4508
4717
|
|
|
4509
4718
|
if (singleResult.completionGuardTriggered) {
|
|
@@ -4521,7 +4730,7 @@ async function runSubagent(
|
|
|
4521
4730
|
}
|
|
4522
4731
|
|
|
4523
4732
|
flatIndex++;
|
|
4524
|
-
if (
|
|
4733
|
+
if (isAgentContract(seqStep.agentContract) && seqStep.gateOn === "acceptance" && singleResult.acceptance?.status === "rejected") {
|
|
4525
4734
|
statusPayload.error = acceptanceFailureMessage(singleResult.acceptance) ?? "Chain acceptance gate rejected the step.";
|
|
4526
4735
|
writeStatusPayload();
|
|
4527
4736
|
break;
|
|
@@ -4607,6 +4816,11 @@ async function runSubagent(
|
|
|
4607
4816
|
stopped: result.stopped,
|
|
4608
4817
|
})));
|
|
4609
4818
|
const partialWithEvidence = !stopped && !signalTerminated && !timedOut && !usageBudgetExceeded && !interrupted && results.some(partialEvidenceResult) && !results.some(concreteFailureResult);
|
|
4819
|
+
// Flush while still nonterminal; deferred status retries retain that state snapshot.
|
|
4820
|
+
statusWriteCoalescer.flush(statusPath);
|
|
4821
|
+
const publication = new Promise<void>((resolve, reject) => {
|
|
4822
|
+
finalResultPublication = { resolve, reject };
|
|
4823
|
+
});
|
|
4610
4824
|
statusPayload.state = stopped || signalTerminated ? "stopped" : timedOut || usageBudgetExceeded ? "failed" : interrupted ? "paused" : results.every((r) => r.success) ? "complete" : partialWithEvidence ? "partial" : "failed";
|
|
4611
4825
|
closeSteerInbox(asyncDir, statusPayload.state, (filePath, payload) => runPersistence.write(filePath, payload));
|
|
4612
4826
|
disposeControlInbox();
|
|
@@ -4665,6 +4879,9 @@ async function runSubagent(
|
|
|
4665
4879
|
statusPayload.error = `Step failed: ${failedStep.agent}`;
|
|
4666
4880
|
}
|
|
4667
4881
|
}
|
|
4882
|
+
let childSessionDisposal: Promise<void> | undefined;
|
|
4883
|
+
const disposeChildSessions = (): Promise<void> => childSessionDisposal ??= childSessions.dispose()
|
|
4884
|
+
.catch((error: unknown) => console.error("Failed to dispose runner child sessions:", error));
|
|
4668
4885
|
try {
|
|
4669
4886
|
runPersistence.write(resultPath, {
|
|
4670
4887
|
lifecycleArtifactVersion: SUBAGENT_LIFECYCLE_ARTIFACT_VERSION,
|
|
@@ -4705,6 +4922,7 @@ async function runSubagent(
|
|
|
4705
4922
|
totalCost: r.totalCost,
|
|
4706
4923
|
usage: r.usage,
|
|
4707
4924
|
artifactPaths: r.artifactPaths,
|
|
4925
|
+
savedOutputPath: r.savedOutputPath,
|
|
4708
4926
|
outputSaveError: r.outputSaveError,
|
|
4709
4927
|
metadataSaveError: r.metadataSaveError,
|
|
4710
4928
|
truncated: r.truncated,
|
|
@@ -4759,13 +4977,16 @@ async function runSubagent(
|
|
|
4759
4977
|
...(taskIndex !== undefined && { taskIndex }),
|
|
4760
4978
|
...(totalTasks !== undefined && { totalTasks }),
|
|
4761
4979
|
}, (filePath, payload) => { writeAsyncResultFile(filePath, payload as Record<string, unknown>); });
|
|
4762
|
-
|
|
4980
|
+
// Only capacity deferral releases settled sessions before terminal publication.
|
|
4981
|
+
if (!finalResultCommitted) await Promise.all([publication, disposeChildSessions()]);
|
|
4763
4982
|
} catch (err) {
|
|
4764
4983
|
const message = `Failed to write result file ${resultPath}: ${err instanceof Error ? err.message : String(err)}`;
|
|
4765
4984
|
console.error(message, err);
|
|
4766
4985
|
statusPayload.state = "failed";
|
|
4767
4986
|
statusPayload.error = message;
|
|
4768
4987
|
statusPayload.lastUpdate = Date.now();
|
|
4988
|
+
} finally {
|
|
4989
|
+
finalResultPublication = undefined;
|
|
4769
4990
|
}
|
|
4770
4991
|
writeStatusPayload();
|
|
4771
4992
|
await orcaProgressTab?.finish(statusPayload.state === "complete" ? "completed" : statusPayload.state === "stopped" ? "stopped" : "failed", effectiveSessionFile);
|
|
@@ -4803,9 +5024,8 @@ async function runSubagent(
|
|
|
4803
5024
|
}), (filePath, content) => runPersistence.write(filePath, { content }, (_path, payload) => {
|
|
4804
5025
|
fs.writeFileSync(_path, (payload as { content: string }).content, "utf-8");
|
|
4805
5026
|
}));
|
|
4806
|
-
//
|
|
4807
|
-
|
|
4808
|
-
await childSessions.dispose().catch((error: unknown) => console.error("Failed to dispose runner child sessions:", error));
|
|
5027
|
+
// Preserve normal-success disposal ordering, then drain remaining persistence retries.
|
|
5028
|
+
await disposeChildSessions();
|
|
4809
5029
|
while (runPersistence.pendingCount() + indexPersistence.pendingCount() > 0) {
|
|
4810
5030
|
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
4811
5031
|
}
|
|
@@ -4813,7 +5033,7 @@ async function runSubagent(
|
|
|
4813
5033
|
indexPersistence.dispose();
|
|
4814
5034
|
if (config.runnerProcessInstanceId) {
|
|
4815
5035
|
// Children run inside this process, so no step has writer processes to prove terminal.
|
|
4816
|
-
const writers: Record<string,
|
|
5036
|
+
const writers: Record<string, PiWriterProcessInstanceExit[]> = {};
|
|
4817
5037
|
const expectedWriters: Record<string, number> = {};
|
|
4818
5038
|
for (const index of results.keys()) {
|
|
4819
5039
|
writers[String(index)] = [];
|
|
@@ -4949,6 +5169,7 @@ function startConfiguredSubagent(config: SubagentRunConfig): void {
|
|
|
4949
5169
|
);
|
|
4950
5170
|
}
|
|
4951
5171
|
|
|
5172
|
+
if (isRunnerEntrypoint) {
|
|
4952
5173
|
const configArg = process.argv[2];
|
|
4953
5174
|
if (configArg) {
|
|
4954
5175
|
try {
|
|
@@ -4980,3 +5201,4 @@ if (configArg) {
|
|
|
4980
5201
|
}
|
|
4981
5202
|
});
|
|
4982
5203
|
}
|
|
5204
|
+
}
|