pi-subagents 0.67.0 → 0.69.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 +90 -0
- package/README.md +1 -1
- package/docs/agents.md +41 -12
- package/docs/configuration.md +61 -19
- package/docs/extension-api.md +5 -1
- package/docs/missions.md +2 -2
- package/docs/models.md +11 -79
- package/docs/observability.md +18 -8
- package/docs/standalone-background.md +13 -3
- package/docs/tool-reference.md +38 -14
- package/docs/watchdog.md +10 -12
- package/docs/workflows.md +59 -1
- package/index.ts +5 -2
- package/package.json +4 -2
- package/runner-peer-loader.mjs +24 -0
- package/runner-peer-preload.mjs +25 -11
- package/skills/pi-subagents/SKILL.md +18 -21
- package/skills/pi-subagents/references/constraints-and-recipes.md +3 -2
- package/skills/pi-subagents/references/execution-controls.md +6 -4
- package/skills/pi-subagents/references/management-authoring-rpc.md +0 -1
- package/skills/pi-subagents/references/multi-lane-orchestration.md +1 -1
- package/skills/pi-subagents/references/prompting-and-roles.md +16 -12
- package/skills/pi-subagents/references/review-and-validation.md +3 -3
- package/src/agents/agent-management.ts +57 -58
- package/src/agents/agent-serializer.ts +4 -3
- package/src/agents/agents.ts +185 -72
- package/src/agents/chain-serializer.ts +5 -0
- package/src/agents/runtime-agent-registry.ts +7 -6
- package/src/agents/skills.ts +1 -1
- package/src/api/preflight.ts +20 -16
- package/src/api/required-child-extensions.ts +6 -0
- package/src/extension/config.ts +10 -37
- package/src/extension/fanout-child.ts +3 -0
- package/src/extension/herdr-pi-bridge.ts +160 -0
- package/src/extension/index.ts +42 -31
- package/src/extension/public-execution.ts +3 -3
- package/src/extension/schemas.ts +23 -6
- package/src/extension/tool-description.ts +8 -7
- package/src/inspectors/ghostty/plugin.ts +13 -1
- package/src/intercom/native-supervisor-channel.ts +22 -18
- package/src/policy/authority.ts +4 -0
- package/src/profiles/profiles.ts +12 -6
- package/src/runs/background/active-run-index.ts +17 -1
- package/src/runs/background/async-execution.ts +309 -126
- package/src/runs/background/async-job-tracker.ts +8 -6
- package/src/runs/background/async-resume.ts +13 -4
- package/src/runs/background/async-status.ts +15 -4
- package/src/runs/background/auto-drain.ts +20 -10
- package/src/runs/background/binary-bootstrap.ts +5 -0
- package/src/runs/background/chain-append.ts +1 -1
- package/src/runs/background/chain-root-attachment.ts +14 -33
- package/src/runs/background/notify.ts +74 -6
- package/src/runs/background/result-files.ts +8 -4
- package/src/runs/background/result-watcher.ts +19 -2
- package/src/runs/background/run-child-session.ts +20 -29
- package/src/runs/background/runner-aliases.ts +4 -33
- package/src/runs/background/runner-child-launch.ts +4 -1
- package/src/runs/background/runner-child-sessions.ts +2 -2
- package/src/runs/background/runner-http-dispatcher.ts +119 -0
- package/src/runs/background/scheduled-runs.ts +11 -5
- package/src/runs/background/stale-run-reconciler.ts +35 -11
- package/src/runs/background/subagent-runner.ts +413 -276
- package/src/runs/background/subagent-wait.ts +128 -23
- package/src/runs/background/wait-completions.ts +75 -27
- package/src/runs/background/wait-subscriptions.ts +9 -3
- package/src/runs/background/wait-tool.ts +4 -2
- package/src/runs/foreground/async-stop-action.ts +93 -3
- package/src/runs/foreground/execution.ts +115 -219
- package/src/runs/foreground/foreground-history.ts +2 -1
- package/src/runs/foreground/subagent-executor.ts +281 -80
- package/src/runs/shared/acceptance.ts +194 -37
- package/src/runs/shared/async-status-projection.ts +123 -33
- package/src/runs/shared/child-launch-plan.ts +15 -3
- package/src/runs/shared/child-launch.ts +19 -6
- package/src/runs/shared/child-runtime-config.ts +5 -0
- package/src/runs/shared/child-session.ts +94 -50
- package/src/runs/shared/child-tool-plan.ts +28 -16
- package/src/runs/shared/dynamic-fanout.ts +2 -2
- package/src/runs/shared/external-cli-contract.ts +11 -1
- package/src/runs/shared/external-cli-preflight.ts +6 -2
- package/src/runs/shared/herdr-connection.ts +134 -0
- package/src/runs/shared/herdr-external-adapters.ts +169 -0
- package/src/runs/shared/herdr-machine.ts +279 -0
- package/src/runs/shared/herdr-pi-protocol.ts +59 -0
- package/src/runs/shared/herdr-placed-run.ts +263 -0
- package/src/runs/shared/model-resolution-diagnostic.ts +76 -0
- package/src/runs/shared/{model-fallback.ts → model-resolution.ts} +22 -237
- package/src/runs/shared/model-scope.ts +1 -1
- package/src/runs/shared/nested-events.ts +11 -2
- package/src/runs/shared/parallel-utils.ts +7 -2
- package/src/runs/shared/pi-spawn.ts +1 -1
- package/src/runs/shared/subagent-prompt-runtime.ts +4 -2
- package/src/runs/shared/worktree-setup-command.ts +27 -4
- package/src/runs/shared/worktree.ts +30 -8
- package/src/shared/child-cache-retention.ts +43 -0
- package/src/shared/launch-contract.ts +6 -9
- package/src/shared/pruned-fork.ts +1 -1
- package/src/shared/required-child-extensions.ts +81 -0
- package/src/shared/settings.ts +5 -2
- package/src/shared/shortcuts.ts +0 -4
- package/src/shared/types.ts +81 -29
- package/src/slash/slash-commands.ts +0 -6
- package/src/slash/subagents-admin.ts +13 -9
- package/src/tui/render.ts +20 -10
- package/src/watchdog/child-status.ts +28 -36
- package/src/watchdog/lsp-diagnostics.ts +1 -1
- package/src/watchdog/model-selection.ts +1 -1
- package/src/watchdog/register-child.ts +10 -3
- package/src/watchdog/register-main.ts +20 -20
- package/src/watchdog/render.ts +1 -1
- package/src/watchdog/review.ts +14 -30
- package/src/watchdog/rules.ts +1 -1
- package/src/watchdog/runtime.ts +23 -12
- package/src/watchdog/settings.ts +3 -6
- package/src/watchdog/types.ts +3 -5
- package/src/watchdog/warning-format.ts +1 -1
- package/src/workflows/scripted-workflow.ts +68 -7
- package/src/workflows/workflow-receipt.ts +21 -3
- package/src/workflows/workflow-resources.ts +13 -2
- package/src/runs/shared/model-exclusions.ts +0 -374
- package/src/runs/shared/readonly-model-continuation.ts +0 -69
- package/src/runs/shared/readonly-session-evidence.ts +0 -307
|
@@ -1,28 +1,15 @@
|
|
|
1
1
|
import { spawnSync } from "node:child_process";
|
|
2
|
-
import { EventEmitter } from "node:events";
|
|
3
2
|
import * as fs from "node:fs";
|
|
4
|
-
import { createRequire } from "node:module";
|
|
5
3
|
import * as path from "node:path";
|
|
4
|
+
import { performance } from "node:perf_hooks";
|
|
6
5
|
import { pathToFileURL } from "node:url";
|
|
7
6
|
import type { Message } from "@earendil-works/pi-ai";
|
|
8
7
|
import { arbitrateCompletionGuardRescue, createTaskMutationArbiter } from "../shared/llm-intent-arbiter.ts";
|
|
8
|
+
import { installRunnerHttpDispatcher } from "./runner-http-dispatcher.ts";
|
|
9
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
10
|
const isRunnerEntrypoint = Boolean(process.argv[1] && import.meta.url === pathToFileURL(path.resolve(process.argv[1])).href);
|
|
25
|
-
|
|
11
|
+
// Detached Node runners skip Pi's CLI dispatcher setup; install the runner's own.
|
|
12
|
+
if (isRunnerEntrypoint) installRunnerHttpDispatcher({ agentDir: getAgentDir(), cwd: process.cwd() });
|
|
26
13
|
import { writeAtomicJson } from "../../shared/atomic-json.ts";
|
|
27
14
|
import { writeAsyncResultFile, writePendingAsyncResultFile } from "./result-files.ts";
|
|
28
15
|
import { createFileCoalescer } from "../../shared/file-coalescer.ts";
|
|
@@ -30,11 +17,12 @@ import { createCapacityResilientJsonWriter } from "../../shared/capacity-resilie
|
|
|
30
17
|
import { isStorageCapacityError } from "../../shared/file-system-retry.ts";
|
|
31
18
|
import { updateActiveRunIndex } from "./active-run-index.ts";
|
|
32
19
|
import { createChildTranscriptWriter, type ChildTranscriptWriter } from "../../shared/child-transcript.ts";
|
|
33
|
-
import { closeSteerInbox, consumeInterruptRequest, consumeSteerRequests, deliverInterruptRequest, deliverStopRequest, deliverTimeoutRequest, watchAsyncControlInbox, type SteerRequest, type StopRequest } from "./control-channel.ts";
|
|
20
|
+
import { closeSteerInbox, consumeInterruptRequest, consumeSteerRequests, consumeStopRequestPayloads, deliverInterruptRequest, deliverStopRequest, deliverTimeoutRequest, watchAsyncControlInbox, type SteerRequest, type StopRequest } from "./control-channel.ts";
|
|
34
21
|
import { appendJsonl as appendRawJsonl, formatOutputArtifactContent, getArtifactPaths, writeArtifact, writeMetadata } from "../../shared/artifacts.ts";
|
|
35
22
|
import { PI_CODING_AGENT_PACKAGE, resolveInstalledPiPackageRoot } from "../shared/pi-spawn.ts";
|
|
36
23
|
import { preflightLaunchCwd } from "../shared/launch-cwd.ts";
|
|
37
24
|
import { captureSingleOutputSnapshot, extractChildWrittenOutput, finalizeSingleOutput, formatSavedOutputReference, injectOutputPathSystemPrompt, injectSingleOutputInstruction, resolveSingleOutput, type SingleOutputSnapshot } from "../shared/single-output.ts";
|
|
25
|
+
import { runSetupCommand } from "../shared/worktree-setup-command.ts";
|
|
38
26
|
import {
|
|
39
27
|
type ActivityState,
|
|
40
28
|
type ArtifactConfig,
|
|
@@ -49,7 +37,6 @@ import {
|
|
|
49
37
|
type CostSummary,
|
|
50
38
|
type LaunchResolvedChildExtensions,
|
|
51
39
|
type RuntimeAcknowledgedChildExtensions,
|
|
52
|
-
type ModelAttempt,
|
|
53
40
|
type PiWriterProcessInstanceExit,
|
|
54
41
|
type NestedRouteInfo,
|
|
55
42
|
type NestedRunSummary,
|
|
@@ -66,6 +53,7 @@ import {
|
|
|
66
53
|
type SteeringTargetStatus,
|
|
67
54
|
type SubagentChildStatusEvent,
|
|
68
55
|
type WorkflowLaneMetadata,
|
|
56
|
+
type HerdrMachineReference,
|
|
69
57
|
DEFAULT_MAX_OUTPUT,
|
|
70
58
|
type MaxOutputConfig,
|
|
71
59
|
SUBAGENT_LIFECYCLE_ARTIFACT_VERSION,
|
|
@@ -97,9 +85,7 @@ import type { InheritedChildRuntime } from "../shared/child-launch.ts";
|
|
|
97
85
|
import { buildRunnerChildLaunch } from "./runner-child-launch.ts";
|
|
98
86
|
import { normalizeExtensionBindings } from "../shared/extension-bindings.ts";
|
|
99
87
|
import type { ChildSessionFactory, DefaultChildSessionFactoryOptions } from "../shared/child-session.ts";
|
|
100
|
-
import {
|
|
101
|
-
import { planReadonlyModelContinuation, READONLY_CONTINUATION_PROMPT, type LogicalRecoveryState } from "../shared/readonly-model-continuation.ts";
|
|
102
|
-
import { getReadonlySessionEvidence } from "../shared/readonly-session-evidence.ts";
|
|
88
|
+
import { runChildSession, type ChildEvent, type RunChildSessionInput, type RunChildSessionResult, type SteerDelivery, type StepSteerHandler } from "./run-child-session.ts";
|
|
103
89
|
import { loadRunnerChildSessionFactory } from "./runner-child-sessions.ts";
|
|
104
90
|
import { SUBAGENT_CHILD_ENV } from "../shared/child-runtime-config.ts";
|
|
105
91
|
import { deriveChildSessionName } from "../../shared/child-session-name.ts";
|
|
@@ -112,10 +98,10 @@ import { buildTimeoutRecoverySummary, collectTrackedMutationEvidence, snapshotTr
|
|
|
112
98
|
import { collectDynamicResults, DynamicFanoutError, materializeDynamicParallelStep, validateDynamicCollection } from "../shared/dynamic-fanout.ts";
|
|
113
99
|
import { claimRunFanoutBatch, getRunFanoutBudgetSnapshot } from "../shared/run-fanout-budget.ts";
|
|
114
100
|
import { nestedSummaryFromAsyncStatus, projectNestedEvents, resolveNestedAsyncDir, writeNestedEvent } from "../shared/nested-events.ts";
|
|
115
|
-
import {
|
|
101
|
+
import { formatSubagentModelVerificationError, isContextOverflow } from "../shared/model-resolution.ts";
|
|
116
102
|
import { markProcessTerminalCandidateLeaseRelease, processTerminalPath, writeProcessTerminalCandidate, type ProcessTerminalCandidate } from "./process-terminal.ts";
|
|
117
103
|
import { createSteeringStatus, recordSteeringRequest, steeringStatus, terminalSteeringNoticeState, unconsumedSteerReason, updateSteeringTarget } from "./steering.ts";
|
|
118
|
-
import { PROMPT_REDACTED, detectSubagentError, extractTextFromContent, extractToolArgsPreview, formatEmptyTerminalAssistantResponseError, getFinalOutput, hasEmptyTerminalAssistantResponse, readStatus } from "../../shared/utils.ts";
|
|
104
|
+
import { PROMPT_REDACTED, detectSubagentError, extractTextFromContent, extractToolArgsPreview, formatEmptyTerminalAssistantResponseError, getAgentDir, getFinalOutput, hasEmptyTerminalAssistantResponse, readStatus } from "../../shared/utils.ts";
|
|
119
105
|
import { evaluateCompletionMutationGuard, expectsImplementationMutation, hasMutationToolCapability, validateImplementationToolContract } from "../shared/completion-guard.ts";
|
|
120
106
|
import { planCompletionEvidence, projectSettlementDiagnostic } from "../shared/completion-evidence.ts";
|
|
121
107
|
import { planAbortRecovery } from "../shared/abort-recovery.ts";
|
|
@@ -150,7 +136,7 @@ import { assertThinkingWithinCeiling } from "../../shared/thinking-ceiling.ts";
|
|
|
150
136
|
import { resolveLaunchBinding } from "../../shared/launch-contract.ts";
|
|
151
137
|
import { writeInitialProgressFile } from "../../shared/settings.ts";
|
|
152
138
|
import { resolveSubagentIntercomTarget } from "../../intercom/intercom-bridge.ts";
|
|
153
|
-
import { acceptanceFailureMessage, aggregateAcceptanceReport, buildSkippedAcceptanceLedger, evaluateAcceptance, formatAcceptancePrompt, resolveAcceptanceReportMode, resolveEffectiveAcceptance, stripAcceptanceReport } from "../shared/acceptance.ts";
|
|
139
|
+
import { acceptanceFailureMessage, aggregateAcceptanceReport, buildSkippedAcceptanceLedger, captureStagedIndexBaseline, evaluateAcceptance, formatAcceptancePrompt, resolveAcceptanceReportMode, resolveEffectiveAcceptance, stripAcceptanceReport, typedVerifyOutput } from "../shared/acceptance.ts";
|
|
154
140
|
import { attachContractProjections, isAgentContract } from "../shared/agent-contract.ts";
|
|
155
141
|
import { waitForImportedAsyncRoot } from "./chain-root-attachment.ts";
|
|
156
142
|
import { appendRunnerStepsToStatus, consumeChainAppendRequests, countPendingChainAppendRequests, statusStepDescription } from "./chain-append.ts";
|
|
@@ -166,6 +152,8 @@ import { resolveClaudeCodeLaunch } from "../shared/claude-code-adapter.ts";
|
|
|
166
152
|
import { resolveCodexExecLaunch } from "../shared/codex-exec-adapter.ts";
|
|
167
153
|
import { resolveCursorAgentLaunch } from "../shared/cursor-agent-adapter.ts";
|
|
168
154
|
import { resolveExternalCliRunnerStatus } from "../shared/external-cli-contract.ts";
|
|
155
|
+
import { formatHerdrMachineHint, prepareHerdrMachineExternalCliRun } from "../shared/herdr-machine.ts";
|
|
156
|
+
import { HerdrExternalNeedsAttentionError, createHerdrExternalAdapter, prepareInternalHerdrExternalAdapter, type HerdrExternalAdapterId, type HerdrExternalResult } from "../shared/herdr-external-adapters.ts";
|
|
169
157
|
import { runExternalJob } from "../shared/external-job-runner.ts";
|
|
170
158
|
import { createOrcaProgressTab, type OrcaProgressTab } from "../shared/orca-progress-tabs.ts";
|
|
171
159
|
import type { ResolvedSubagentCapabilityCeiling } from "../shared/capability-ceiling.ts";
|
|
@@ -224,6 +212,8 @@ export interface SubagentRunConfig {
|
|
|
224
212
|
deadlineAt?: number;
|
|
225
213
|
/** Resolved configured hard per-tool-call timeout (ms); fast tools still have a default when undefined. */
|
|
226
214
|
toolTimeoutMs?: number;
|
|
215
|
+
/** Steer the running steps to checkpoint and stop this many ms before `deadlineAt`; absent = no checkpoint steer. */
|
|
216
|
+
checkpointBeforeDeadlineMs?: number;
|
|
227
217
|
toolBudget?: ResolvedToolBudget;
|
|
228
218
|
usageBudget?: UsageBudgetConfig;
|
|
229
219
|
revivalLease?: SessionLeaseRequest;
|
|
@@ -272,14 +262,15 @@ interface StepResult {
|
|
|
272
262
|
sessionFile?: string;
|
|
273
263
|
intercomTarget?: string;
|
|
274
264
|
model?: string;
|
|
265
|
+
nativeMachine?: import("../../shared/types.ts").SingleResult["nativeMachine"];
|
|
275
266
|
thinking?: string;
|
|
276
|
-
|
|
277
|
-
modelAttempts?: ModelAttempt[];
|
|
267
|
+
requestedModel?: string;
|
|
278
268
|
/** True when the dispatch failed because the input exceeded the model's context window. */
|
|
279
269
|
contextOverflow?: boolean;
|
|
280
270
|
totalCost?: CostSummary;
|
|
281
271
|
artifactPaths?: ArtifactPaths;
|
|
282
272
|
outputSaveError?: string;
|
|
273
|
+
artifactOutputSaveFailed?: true;
|
|
283
274
|
metadataSaveError?: string;
|
|
284
275
|
truncated?: boolean;
|
|
285
276
|
transcriptPath?: string;
|
|
@@ -422,50 +413,22 @@ function emptyUsage(): Usage {
|
|
|
422
413
|
return { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0, turns: 0 };
|
|
423
414
|
}
|
|
424
415
|
|
|
425
|
-
function
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
let output = 0;
|
|
429
|
-
for (const attempt of attempts) {
|
|
430
|
-
input += attempt.usage?.input ?? 0;
|
|
431
|
-
output += attempt.usage?.output ?? 0;
|
|
432
|
-
}
|
|
416
|
+
function tokenUsageFromUsage(usage: Usage | undefined): TokenUsage | null {
|
|
417
|
+
const input = usage?.input ?? 0;
|
|
418
|
+
const output = usage?.output ?? 0;
|
|
433
419
|
const total = input + output;
|
|
434
420
|
return total > 0 ? { input, output, total } : null;
|
|
435
421
|
}
|
|
436
422
|
|
|
437
|
-
function
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
let costUsd = 0;
|
|
442
|
-
for (const attempt of attempts) {
|
|
443
|
-
inputTokens += attempt.usage?.input ?? 0;
|
|
444
|
-
outputTokens += attempt.usage?.output ?? 0;
|
|
445
|
-
costUsd += attempt.usage?.cost ?? 0;
|
|
446
|
-
}
|
|
423
|
+
function costSummaryFromUsage(usage: Usage | undefined): CostSummary | undefined {
|
|
424
|
+
const inputTokens = usage?.input ?? 0;
|
|
425
|
+
const outputTokens = usage?.output ?? 0;
|
|
426
|
+
const costUsd = usage?.cost ?? 0;
|
|
447
427
|
return inputTokens > 0 || outputTokens > 0 || costUsd > 0
|
|
448
428
|
? { inputTokens, outputTokens, costUsd }
|
|
449
429
|
: undefined;
|
|
450
430
|
}
|
|
451
431
|
|
|
452
|
-
function usageFromAttempts(attempts: ModelAttempt[] | undefined): Usage | undefined {
|
|
453
|
-
if (!attempts || attempts.length === 0) return undefined;
|
|
454
|
-
const usage = emptyUsage();
|
|
455
|
-
for (const attempt of attempts) {
|
|
456
|
-
if (!attempt.usage) continue;
|
|
457
|
-
usage.input += attempt.usage.input;
|
|
458
|
-
usage.output += attempt.usage.output;
|
|
459
|
-
usage.cacheRead += attempt.usage.cacheRead;
|
|
460
|
-
usage.cacheWrite += attempt.usage.cacheWrite;
|
|
461
|
-
usage.cost += attempt.usage.cost;
|
|
462
|
-
usage.turns += attempt.usage.turns;
|
|
463
|
-
}
|
|
464
|
-
return usage.input !== 0 || usage.output !== 0 || usage.cacheRead !== 0 || usage.cacheWrite !== 0 || usage.cost !== 0 || usage.turns !== 0
|
|
465
|
-
? usage
|
|
466
|
-
: undefined;
|
|
467
|
-
}
|
|
468
|
-
|
|
469
432
|
function appendRecentStepOutput(step: RunnerStatusStep, lines: string[]): void {
|
|
470
433
|
const nonEmpty = lines.filter((line) => line.trim());
|
|
471
434
|
if (nonEmpty.length === 0) return;
|
|
@@ -659,6 +622,38 @@ function writeRunLog(
|
|
|
659
622
|
write(logPath, lines.join("\n"));
|
|
660
623
|
}
|
|
661
624
|
|
|
625
|
+
function expectedMissingGitEvidence(error: unknown): boolean {
|
|
626
|
+
if (typeof (error as { code?: unknown })?.code !== "number") return false;
|
|
627
|
+
const stderr = (error as { stderr?: unknown }).stderr;
|
|
628
|
+
const detail = Buffer.isBuffer(stderr) ? stderr.toString("utf-8") : String(stderr ?? "");
|
|
629
|
+
return /not a git repository|Needed a single revision/i.test(detail);
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
async function readGitFingerprint(cwd: string, signal: AbortSignal, onError: (error: unknown) => void): Promise<string | undefined> {
|
|
633
|
+
try {
|
|
634
|
+
const runGit = async (args: string[]) => {
|
|
635
|
+
const result = await runSetupCommand("git", args, {
|
|
636
|
+
cwd, signal, maxBuffer: 16 * 1024 * 1024, deadlineAt: Date.now() + 30_000,
|
|
637
|
+
acceptedExitCodes: Array.from({ length: 256 }, (_, code) => code),
|
|
638
|
+
});
|
|
639
|
+
if (result.error) throw result.error;
|
|
640
|
+
if (result.status !== 0) {
|
|
641
|
+
throw Object.assign(new Error(result.stderr || `git exited with ${result.status}`), { code: result.status, stderr: result.stderr });
|
|
642
|
+
}
|
|
643
|
+
return result.stdoutBuffer;
|
|
644
|
+
};
|
|
645
|
+
const headOutput = await runGit(["rev-parse", "--verify", "HEAD"]);
|
|
646
|
+
const head = headOutput.toString("utf-8").trim();
|
|
647
|
+
if (!head) return undefined;
|
|
648
|
+
const status = await runGit(["status", "--porcelain=v1", "-z", "--untracked-files=normal"]);
|
|
649
|
+
return `${head}\0${status.toString("base64")}`;
|
|
650
|
+
} catch (error) {
|
|
651
|
+
if ((error as NodeJS.ErrnoException)?.code === "PROCESS_TREE_UNVERIFIED") throw error;
|
|
652
|
+
if (!signal.aborted && !expectedMissingGitEvidence(error)) onError(error);
|
|
653
|
+
return undefined;
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
|
|
662
657
|
/** Context for running a single step */
|
|
663
658
|
interface SingleStepContext {
|
|
664
659
|
previousOutput: string;
|
|
@@ -703,6 +698,8 @@ interface SingleStepContext {
|
|
|
703
698
|
onAttemptStart?: (attempt: { model?: string; thinking?: string; contextLimit?: number }) => void;
|
|
704
699
|
onChildEvent?: (event: ChildEvent) => void;
|
|
705
700
|
onExternalProcess?: (process: ExternalProcessStatus) => void;
|
|
701
|
+
prepareExternalActivity?: (cwd: string, signal: AbortSignal) => Promise<void>;
|
|
702
|
+
onExternalStreamActivity?: () => void;
|
|
706
703
|
onExternalJob?: (status: ExternalJobStatus) => void;
|
|
707
704
|
skipAcceptance?: () => boolean;
|
|
708
705
|
/** Authoritative owner decision after event delivery; undefined includes incomplete run-wide usage. */
|
|
@@ -714,7 +711,26 @@ interface SingleStepContext {
|
|
|
714
711
|
orcaProgressTab?: OrcaProgressTab;
|
|
715
712
|
}
|
|
716
713
|
|
|
717
|
-
/**
|
|
714
|
+
/** Machine runs: a one-line hint for predictable remote failures, and a note that writer changes live on the machine. */
|
|
715
|
+
function decorateHerdrMachineResult<T extends { output: string; exitCode: number | null; error?: string; externalProcess: { stderrPath: string } }>(result: T, machine: HerdrMachineReference, adapter: string | undefined): T {
|
|
716
|
+
let stderrTail = "";
|
|
717
|
+
try {
|
|
718
|
+
const stderr = fs.readFileSync(result.externalProcess.stderrPath, "utf-8");
|
|
719
|
+
stderrTail = stderr.slice(-4096);
|
|
720
|
+
} catch { /* stderr log is best-effort evidence. */ }
|
|
721
|
+
const hint = result.exitCode === 0 ? undefined : formatHerdrMachineHint(machine, `${result.error ?? ""}\n${stderrTail}\nexit code ${result.exitCode}`);
|
|
722
|
+
const error = hint ? `${result.error ?? `Remote command exited with code ${result.exitCode}.`}\n${hint}` : result.error;
|
|
723
|
+
const note = result.exitCode === 0 && adapter?.endsWith("-writer") ? `Changes made by this run live on ${machine.label ?? machine.id} at ${machine.cwd}; the local checkout is unchanged.` : undefined;
|
|
724
|
+
const output = note ? (result.output.trim() ? `${result.output.trimEnd()}\n\n${note}` : note) : result.output;
|
|
725
|
+
return { ...result, output, ...(error !== undefined ? { error } : {}) };
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
export async function settleHerdrExternalRunnerError(error: unknown, adapter: HerdrExternalAdapterId, evidence: Parameters<ReturnType<typeof createHerdrExternalAdapter>["normalize"]>[0], retain: () => Promise<void>): Promise<HerdrExternalResult> {
|
|
729
|
+
await retain();
|
|
730
|
+
if (!(error instanceof HerdrExternalNeedsAttentionError)) throw error;
|
|
731
|
+
return createHerdrExternalAdapter(adapter).normalize(evidence, error.message);
|
|
732
|
+
}
|
|
733
|
+
|
|
718
734
|
export async function runSingleStepInner(
|
|
719
735
|
step: SubagentStep,
|
|
720
736
|
ctx: SingleStepContext,
|
|
@@ -773,8 +789,7 @@ export async function runSingleStepInner(
|
|
|
773
789
|
sessionFile: imported.sessionFile,
|
|
774
790
|
intercomTarget: imported.intercomTarget,
|
|
775
791
|
model: imported.model,
|
|
776
|
-
|
|
777
|
-
modelAttempts: imported.modelAttempts,
|
|
792
|
+
requestedModel: imported.requestedModel,
|
|
778
793
|
contextOverflow: imported.contextOverflow,
|
|
779
794
|
totalCost: imported.totalCost,
|
|
780
795
|
usage: imported.usage,
|
|
@@ -808,13 +823,13 @@ export async function runSingleStepInner(
|
|
|
808
823
|
subagentOnlyExtensions: step.subagentOnlyExtensions,
|
|
809
824
|
fast: step.fast,
|
|
810
825
|
model: step.model,
|
|
811
|
-
modelCandidates: step.modelCandidates,
|
|
812
826
|
mcpDirectTools: step.mcpDirectTools,
|
|
813
827
|
cwd: step.cwd ?? ctx.cwd,
|
|
814
828
|
requireReadTool: Boolean(step.skills?.length),
|
|
815
829
|
structuredOutput: Boolean(effectiveStructuredOutput),
|
|
816
830
|
capabilityCeiling: step.capabilityCeiling ?? ctx.capabilityCeiling,
|
|
817
831
|
inheritedCapabilityCeiling: ctx.inheritedChildRuntime?.capabilityCeiling,
|
|
832
|
+
requiredExtensions: step.requiredExtensions ?? ctx.inheritedChildRuntime?.requiredExtensions,
|
|
818
833
|
permissionRules: step.permissionRules,
|
|
819
834
|
hostAvailableBuiltins: ctx.hostAvailableBuiltins,
|
|
820
835
|
}));
|
|
@@ -874,7 +889,53 @@ export async function runSingleStepInner(
|
|
|
874
889
|
transcriptWriter?.writeInitialUserMessage(`${PROMPT_REDACTED}; live Prompt Audit only.`);
|
|
875
890
|
|
|
876
891
|
if (step.runner?.type === "external-cli") {
|
|
877
|
-
const externalCwd = step.cwd ?? ctx.cwd;
|
|
892
|
+
const externalCwd = step.machine?.cwd ?? step.cwd ?? ctx.cwd;
|
|
893
|
+
const externalAbortSignal = combinedAbortSignal([ctx.timeoutSignal, ctx.stopSignal]);
|
|
894
|
+
if (!step.machine && externalAbortSignal) await ctx.prepareExternalActivity?.(externalCwd, externalAbortSignal);
|
|
895
|
+
if (externalAbortSignal?.aborted) {
|
|
896
|
+
const stopped = ctx.stopSignal?.aborted === true;
|
|
897
|
+
const message = stopped ? ctx.stopMessage ?? "Subagent stopped by user." : ctx.timeoutMessage ?? "Subagent timed out.";
|
|
898
|
+
return omitUndefinedProperties({ agent: step.agent, context: step.context, output: message, error: message, exitCode: 1, stopped: stopped || undefined, timedOut: stopped ? undefined : true });
|
|
899
|
+
}
|
|
900
|
+
if (step.machine) {
|
|
901
|
+
const runner = resolveExternalCliRunnerStatus({ ...step.runner, machine: step.machine });
|
|
902
|
+
const outputSnapshot = captureSingleOutputSnapshot(step.outputPath);
|
|
903
|
+
const placedRunId = `${ctx.id}-${ctx.flatIndex}`;
|
|
904
|
+
const session = await prepareInternalHerdrExternalAdapter({ adapter: step.runner.adapter as HerdrExternalAdapterId, machine: step.machine, runId: placedRunId }, {});
|
|
905
|
+
let timedOut = false, stopped = false, retain = false;
|
|
906
|
+
const evidenceInput = { runId: placedRunId, requestId: `prompt-${ctx.flatIndex}`, task: buildExternalCliPrompt(step.systemPrompt ?? "", task), cwd: externalCwd, nativeSessionId: session.launch.nativeSessionId };
|
|
907
|
+
let resolveInterruption!: (value: "timeout" | "stop") => void, rejectInterruption!: (error: unknown) => void;
|
|
908
|
+
const interruption = new Promise<"timeout" | "stop">((resolve, reject) => { resolveInterruption = resolve; rejectInterruption = reject; });
|
|
909
|
+
ctx.registerTimeout?.(() => { timedOut = true; retain = true; resolveInterruption("timeout"); });
|
|
910
|
+
ctx.registerStop?.(() => { stopped = true; void session.abort().then(() => resolveInterruption("stop"), rejectInterruption); });
|
|
911
|
+
try {
|
|
912
|
+
let settled: HerdrExternalResult;
|
|
913
|
+
try {
|
|
914
|
+
const raced = await Promise.race([session.promptAndSettle(evidenceInput).then((value) => ({ kind: "settled" as const, value })), interruption.then((kind) => ({ kind }))]);
|
|
915
|
+
if (raced.kind === "settled") settled = raced.value;
|
|
916
|
+
else if (raced.kind === "timeout") settled = createHerdrExternalAdapter(step.runner.adapter as HerdrExternalAdapterId).normalize(evidenceInput, "Placed external run timed out; truthful pane retained for inspection.");
|
|
917
|
+
else return { agent: step.agent, context: step.context, output: "Placed external run stopped by user.", outputState: "present", exitCode: 1, stopped: true, runner, execution: { status: "stopped", success: false, exitCode: 1, stopped: true } };
|
|
918
|
+
} catch (error) {
|
|
919
|
+
retain = true;
|
|
920
|
+
settled = await settleHerdrExternalRunnerError(error, step.runner.adapter as HerdrExternalAdapterId, evidenceInput, () => session.retain());
|
|
921
|
+
}
|
|
922
|
+
const output = settled.output;
|
|
923
|
+
try { fs.writeFileSync(ctx.outputFile, output, "utf-8"); } catch { /* Observability output is best-effort. */ }
|
|
924
|
+
const resolvedOutput = step.outputPath ? resolveSingleOutput(step.outputPath, output, outputSnapshot, step.outputClaimPath) : { fullOutput: output };
|
|
925
|
+
const outputReference = resolvedOutput.savedPath ? formatSavedOutputReference(resolvedOutput.savedPath, resolvedOutput.fullOutput) : undefined;
|
|
926
|
+
const finalizedOutput = finalizeSingleOutput(omitUndefinedProperties({ fullOutput: resolvedOutput.fullOutput, outputPath: step.outputPath, outputMode: step.outputMode, exitCode: 1, preserveSavedOutput: true, savedPath: resolvedOutput.savedPath, outputReference, saveError: resolvedOutput.saveError }));
|
|
927
|
+
const artifactErrors = artifactPaths && ctx.artifactConfig?.enabled !== false ? persistStepArtifacts({ artifactPaths, artifactConfig: ctx.artifactConfig, output: formatOutputArtifactContent(omitUndefinedProperties({ output: resolvedOutput.fullOutput, metadataPath: ctx.artifactConfig?.includeMetadata === false ? undefined : artifactPaths.metadataPath })), metadata: { runId: ctx.id, agent: step.agent, task: PROMPT_REDACTED, runner, placement: session.owner.identity, settlement: settled.settlement, outcome: settled.outcome, timestamp: Date.now() } }) : {};
|
|
928
|
+
return omitUndefinedProperties({ agent: step.agent, ...(childSessionName ? { sessionName: childSessionName } : {}), context: step.context, output: finalizedOutput.displayOutput, outputState: output.trim() ? "present" : "absent", exitCode: 1, error: resolvedOutput.fatalError ? resolvedOutput.saveError : undefined, timedOut, stopped, artifactPaths, outputSaveError: [resolvedOutput.saveError, artifactErrors.outputSaveError].filter(Boolean).join("\n") || undefined, metadataSaveError: artifactErrors.metadataSaveError, runner, execution: { status: "partial", success: false, exitCode: 1 } });
|
|
929
|
+
} catch (error) {
|
|
930
|
+
// Any post-allocation uncertainty retains the pane; only explicit stop
|
|
931
|
+
// and successful exact settlement are destructive.
|
|
932
|
+
retain = true;
|
|
933
|
+
throw error;
|
|
934
|
+
} finally {
|
|
935
|
+
ctx.registerTimeout?.(undefined); ctx.registerStop?.(undefined);
|
|
936
|
+
if (retain) await session.retain(); else await session.dispose();
|
|
937
|
+
}
|
|
938
|
+
}
|
|
878
939
|
const adapterLaunch = step.runner.adapter === "codex-exec" || step.runner.adapter === "codex-exec-writer"
|
|
879
940
|
? resolveCodexExecLaunch({ adapter: step.runner.adapter, command: step.runner.command, asyncDir: path.dirname(ctx.outputFile), stepIndex: ctx.flatIndex })
|
|
880
941
|
: step.runner.adapter === "claude-code" || step.runner.adapter === "claude-code-writer"
|
|
@@ -882,9 +943,13 @@ export async function runSingleStepInner(
|
|
|
882
943
|
: step.runner.adapter === "cursor-agent" || step.runner.adapter === "cursor-agent-writer"
|
|
883
944
|
? resolveCursorAgentLaunch({ adapter: step.runner.adapter, command: step.runner.command, cwd: externalCwd, asyncDir: path.dirname(ctx.outputFile), stepIndex: ctx.flatIndex })
|
|
884
945
|
: undefined;
|
|
885
|
-
const runner = resolveExternalCliRunnerStatus({ ...step.runner, ...(adapterLaunch ? { args: adapterLaunch.args } : {}) });
|
|
946
|
+
const runner = resolveExternalCliRunnerStatus({ ...step.runner, ...(adapterLaunch ? { args: adapterLaunch.args } : {}), ...(step.machine ? { machine: step.machine } : {}) });
|
|
886
947
|
const outputSnapshot = captureSingleOutputSnapshot(step.outputPath);
|
|
887
|
-
const
|
|
948
|
+
const onExternalOutput = (chunk: Buffer): void => {
|
|
949
|
+
if (chunk.length > 0) ctx.onExternalStreamActivity?.();
|
|
950
|
+
ctx.orcaProgressTab?.append(chunk.toString("utf-8"));
|
|
951
|
+
};
|
|
952
|
+
const externalInput = omitUndefinedProperties({
|
|
888
953
|
command: adapterLaunch?.command ?? runner.command,
|
|
889
954
|
args: adapterLaunch?.args ?? runner.args,
|
|
890
955
|
cwd: externalCwd,
|
|
@@ -902,9 +967,13 @@ export async function runSingleStepInner(
|
|
|
902
967
|
timeoutMessage: ctx.timeoutMessage,
|
|
903
968
|
stopMessage: ctx.stopMessage,
|
|
904
969
|
onProcess: ctx.onExternalProcess,
|
|
905
|
-
onStdout:
|
|
906
|
-
onStderr:
|
|
907
|
-
})
|
|
970
|
+
onStdout: onExternalOutput,
|
|
971
|
+
onStderr: onExternalOutput,
|
|
972
|
+
});
|
|
973
|
+
const preparedExternal = prepareHerdrMachineExternalCliRun(externalInput, step.machine ? { machine: step.machine, ...(step.machineEnv ? { env: step.machineEnv } : {}) } : undefined, { localCwd: ctx.cwd });
|
|
974
|
+
const ran = await runExternalCli(preparedExternal.input);
|
|
975
|
+
const externalProcess = preparedExternal.decorateProcess(ran.externalProcess);
|
|
976
|
+
const external = step.machine ? decorateHerdrMachineResult(ran, step.machine, step.runner.adapter) : ran;
|
|
908
977
|
try { fs.writeFileSync(ctx.outputFile, external.output, "utf-8"); } catch { /* Observability output is best-effort. */ }
|
|
909
978
|
const resolvedOutput = step.outputPath && external.exitCode === 0
|
|
910
979
|
? resolveSingleOutput(step.outputPath, external.output, outputSnapshot, step.outputClaimPath)
|
|
@@ -928,7 +997,7 @@ export async function runSingleStepInner(
|
|
|
928
997
|
artifactPaths,
|
|
929
998
|
artifactConfig: ctx.artifactConfig,
|
|
930
999
|
output: formatOutputArtifactContent(omitUndefinedProperties({ output: resolvedOutput.fullOutput, error: external.error, metadataPath: ctx.artifactConfig?.includeMetadata === false ? undefined : artifactPaths.metadataPath })),
|
|
931
|
-
metadata: { runId: ctx.id, agent: step.agent, task: PROMPT_REDACTED, runner, externalProcess:
|
|
1000
|
+
metadata: { runId: ctx.id, agent: step.agent, task: PROMPT_REDACTED, runner, externalProcess: externalProcess, exitCode: external.exitCode, error: external.error, timestamp: Date.now() },
|
|
932
1001
|
})
|
|
933
1002
|
: {};
|
|
934
1003
|
return omitUndefinedProperties({
|
|
@@ -944,9 +1013,10 @@ export async function runSingleStepInner(
|
|
|
944
1013
|
processSignal: external.processSignal,
|
|
945
1014
|
artifactPaths,
|
|
946
1015
|
outputSaveError: [resolvedOutput.saveError, artifactErrors.outputSaveError].filter(Boolean).join("\n") || undefined,
|
|
1016
|
+
artifactOutputSaveFailed: artifactErrors.outputSaveError ? true : undefined,
|
|
947
1017
|
metadataSaveError: artifactErrors.metadataSaveError,
|
|
948
1018
|
runner,
|
|
949
|
-
externalProcess:
|
|
1019
|
+
externalProcess: externalProcess,
|
|
950
1020
|
});
|
|
951
1021
|
}
|
|
952
1022
|
|
|
@@ -1013,6 +1083,7 @@ export async function runSingleStepInner(
|
|
|
1013
1083
|
stopped: external.stopped,
|
|
1014
1084
|
artifactPaths,
|
|
1015
1085
|
outputSaveError: [resolvedOutput.saveError, artifactErrors.outputSaveError].filter(Boolean).join("\n") || undefined,
|
|
1086
|
+
artifactOutputSaveFailed: artifactErrors.outputSaveError ? true : undefined,
|
|
1016
1087
|
metadataSaveError: artifactErrors.metadataSaveError,
|
|
1017
1088
|
runner,
|
|
1018
1089
|
externalJob: external.externalJob,
|
|
@@ -1026,16 +1097,9 @@ export async function runSingleStepInner(
|
|
|
1026
1097
|
alignForkedSessionCwd(step.sessionFile, effectiveCwd);
|
|
1027
1098
|
}
|
|
1028
1099
|
|
|
1029
|
-
const
|
|
1030
|
-
? step.modelCandidates.length > 0 ? step.modelCandidates : [undefined]
|
|
1031
|
-
: step.model
|
|
1032
|
-
? [step.model]
|
|
1033
|
-
: [undefined];
|
|
1034
|
-
const attemptedModels: string[] = [];
|
|
1100
|
+
const candidate = step.model;
|
|
1035
1101
|
let capabilityAudit: import("../shared/capability-ceiling.ts").SubagentCapabilityAudit | undefined;
|
|
1036
1102
|
let launchResolvedExtensions = step.launchResolvedExtensions;
|
|
1037
|
-
const modelAttempts: ModelAttempt[] = [];
|
|
1038
|
-
const attemptNotes: string[] = [];
|
|
1039
1103
|
let finalRequiredOutputMissing: boolean | undefined;
|
|
1040
1104
|
const eventsPath = path.join(path.dirname(ctx.outputFile), "events.jsonl");
|
|
1041
1105
|
let finalResult: RunChildSessionResult | undefined;
|
|
@@ -1046,48 +1110,22 @@ export async function runSingleStepInner(
|
|
|
1046
1110
|
let toolBudget = step.toolBudget ? initialToolBudgetState(step.toolBudget) : undefined;
|
|
1047
1111
|
let toolBudgetBlocked = false;
|
|
1048
1112
|
let actualLaunchContractDigest = step.launchContractDigest;
|
|
1049
|
-
const mutationSnapshot = snapshotTrackedMutations(step.cwd ?? ctx.cwd);
|
|
1113
|
+
const mutationSnapshot = step.machine ? { source: "tracked-files" as const, trackedOnly: true as const, cwd: step.cwd ?? ctx.cwd, dirtyFiles: [], fingerprints: {}, unavailable: "Local Git evidence is not authoritative for a pane-native remote run." } : snapshotTrackedMutations(step.cwd ?? ctx.cwd);
|
|
1050
1114
|
let finalMutationEvidence = collectTrackedMutationEvidence(mutationSnapshot, step.cwd ?? ctx.cwd);
|
|
1051
1115
|
|
|
1052
|
-
let modelIndex = 0;
|
|
1053
1116
|
let contextOverflow = false;
|
|
1054
1117
|
let launchWarningsEmitted = false;
|
|
1055
|
-
|
|
1056
|
-
let
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
if (!ctx.usageBudget) return "unconfigured" as const;
|
|
1063
|
-
if (ctx.usageBudget.costUsd || !ctx.onChildEvent || exhausted !== false) return "unknown" as const;
|
|
1064
|
-
return "available" as const;
|
|
1065
|
-
};
|
|
1066
|
-
const lifecycleAllowsContinuation = () => !ctx.timeoutSignal?.aborted && !ctx.stopSignal?.aborted
|
|
1067
|
-
&& !ctx.skipAcceptance?.() && (ctx.deadlineAt === undefined || Date.now() < ctx.deadlineAt);
|
|
1068
|
-
const canContinue = () => lifecycleAllowsContinuation() && ["available", "unconfigured"].includes(continuationBudget());
|
|
1069
|
-
const failContinuationLaunch = (candidate: string | undefined, error: unknown) => {
|
|
1070
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
1071
|
-
modelAttempts.push({ model: candidate ?? "default", success: false, exitCode: 1, error: message });
|
|
1072
|
-
if (candidate) attemptedModels.push(candidate);
|
|
1073
|
-
if (finalResult) finalResult = { ...finalResult, exitCode: 1, error: message };
|
|
1074
|
-
};
|
|
1075
|
-
let nextAttemptTask = task;
|
|
1076
|
-
modelAttemptsLoop: while (modelIndex < candidates.length) {
|
|
1077
|
-
if (ctx.timeoutSignal?.aborted || ctx.stopSignal?.aborted || ctx.skipAcceptance?.()) break;
|
|
1078
|
-
if (readonlyContinuation && !canContinue()) break;
|
|
1079
|
-
const recoveringAbort = recoveryState === "abort-recovery";
|
|
1080
|
-
const attemptTask = nextAttemptTask;
|
|
1081
|
-
const candidate = candidates[modelIndex];
|
|
1082
|
-
const expectedModelForVerification = candidate && !(step.skipPrimaryModelVerification && modelIndex === 0) ? candidate : undefined;
|
|
1118
|
+
const aggregateUsage = emptyUsage();
|
|
1119
|
+
let launched = false;
|
|
1120
|
+
let recoveryTask = task;
|
|
1121
|
+
let stagedIndexBaseline: string | undefined;
|
|
1122
|
+
singleLaunch: for (let attemptIndex = 0; attemptIndex < 2; attemptIndex++) {
|
|
1123
|
+
if (ctx.timeoutSignal?.aborted || ctx.stopSignal?.aborted || ctx.skipAcceptance?.()) break singleLaunch;
|
|
1124
|
+
const expectedModelForVerification = candidate && !step.skipPrimaryModelVerification ? candidate : undefined;
|
|
1083
1125
|
try {
|
|
1084
1126
|
assertThinkingWithinCeiling({ model: candidate, configThinking: step.thinking, ceiling: step.thinkingCeiling, agent: step.agent, runId: ctx.id });
|
|
1085
1127
|
} catch (error) {
|
|
1086
1128
|
const message = error instanceof Error ? error.message : String(error);
|
|
1087
|
-
if (readonlyContinuation) {
|
|
1088
|
-
failContinuationLaunch(candidate, error);
|
|
1089
|
-
break modelAttemptsLoop;
|
|
1090
|
-
}
|
|
1091
1129
|
return omitUndefinedProperties({ agent: step.agent, output: message, error: message, exitCode: 1, context: step.context, thinkingCeiling: step.thinkingCeiling });
|
|
1092
1130
|
}
|
|
1093
1131
|
ctx.onAttemptStart?.(omitUndefinedProperties({
|
|
@@ -1124,11 +1162,7 @@ export async function runSingleStepInner(
|
|
|
1124
1162
|
childWatchdog,
|
|
1125
1163
|
watchdogStatus: (event) => watchdogSink?.(event),
|
|
1126
1164
|
});
|
|
1127
|
-
} catch (error) {
|
|
1128
|
-
if (!readonlyContinuation) throw error;
|
|
1129
|
-
failContinuationLaunch(candidate, error);
|
|
1130
|
-
break modelAttemptsLoop;
|
|
1131
|
-
}
|
|
1165
|
+
} catch (error) { throw error; }
|
|
1132
1166
|
if (effectiveStructuredOutput && launch.config.structuredOutput) {
|
|
1133
1167
|
// The runner reads the value back from the runtime's files after the run.
|
|
1134
1168
|
launch.config.structuredOutput.capture = createStructuredOutputFileCapture(effectiveStructuredOutput);
|
|
@@ -1147,13 +1181,13 @@ export async function runSingleStepInner(
|
|
|
1147
1181
|
subagentOnlyExtensions: step.subagentOnlyExtensions,
|
|
1148
1182
|
fast: step.fast,
|
|
1149
1183
|
model: step.model,
|
|
1150
|
-
modelCandidates: step.modelCandidates,
|
|
1151
1184
|
mcpDirectTools: step.mcpDirectTools,
|
|
1152
1185
|
cwd: step.cwd ?? ctx.cwd,
|
|
1153
1186
|
requireReadTool: Boolean(step.skills?.length),
|
|
1154
1187
|
structuredOutput: Boolean(effectiveStructuredOutput),
|
|
1155
1188
|
capabilityCeiling: step.capabilityCeiling ?? ctx.capabilityCeiling,
|
|
1156
1189
|
inheritedCapabilityCeiling: ctx.inheritedChildRuntime?.capabilityCeiling,
|
|
1190
|
+
requiredExtensions: step.requiredExtensions ?? ctx.inheritedChildRuntime?.requiredExtensions,
|
|
1157
1191
|
permissionRules: step.permissionRules,
|
|
1158
1192
|
hostAvailableBuiltins: ctx.hostAvailableBuiltins,
|
|
1159
1193
|
}));
|
|
@@ -1165,7 +1199,7 @@ export async function runSingleStepInner(
|
|
|
1165
1199
|
inheritGlobalContext: step.inheritGlobalContext,
|
|
1166
1200
|
inheritSkills: step.inheritSkills,
|
|
1167
1201
|
task: step.launchBindingTask ?? task,
|
|
1168
|
-
|
|
1202
|
+
model: candidate,
|
|
1169
1203
|
fast: step.fast,
|
|
1170
1204
|
thinking: resolveEffectiveThinking(candidate, step.thinking),
|
|
1171
1205
|
systemPrompt: step.systemPrompt ?? "",
|
|
@@ -1181,13 +1215,18 @@ export async function runSingleStepInner(
|
|
|
1181
1215
|
// Each attempt rewrites the step output log; synchronous appends keep a
|
|
1182
1216
|
// retried attempt from interleaving with the previous attempt's flush.
|
|
1183
1217
|
fs.writeFileSync(ctx.outputFile, "", "utf-8");
|
|
1218
|
+
if (step.effectiveAcceptance?.preserveStagedIndex && stagedIndexBaseline === undefined) {
|
|
1219
|
+
try {
|
|
1220
|
+
stagedIndexBaseline = captureStagedIndexBaseline(step.cwd ?? ctx.cwd);
|
|
1221
|
+
} catch (error) {
|
|
1222
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1223
|
+
return { agent: step.agent, output: message, error: message, exitCode: 1, context: step.context };
|
|
1224
|
+
}
|
|
1225
|
+
}
|
|
1184
1226
|
const run = await runChildSession(omitUndefinedProperties({
|
|
1185
1227
|
factory: ctx.childSessions,
|
|
1186
1228
|
launch,
|
|
1187
|
-
|
|
1188
|
-
readonlyContinuation,
|
|
1189
|
-
canContinue,
|
|
1190
|
-
prompt: `Task: ${attemptTask}`,
|
|
1229
|
+
prompt: `Task: ${recoveryTask}`,
|
|
1191
1230
|
childWatchdog,
|
|
1192
1231
|
childEventContext: { runId: ctx.id, stepIndex: ctx.flatIndex, agent: step.agent },
|
|
1193
1232
|
appendChildEvent: (event) => appendDiagnosticJsonl(eventsPath, JSON.stringify(event), typeof event.type === "string" ? event.type : undefined),
|
|
@@ -1216,6 +1255,13 @@ export async function runSingleStepInner(
|
|
|
1216
1255
|
modelResponseAliases: step.modelResponseAliases,
|
|
1217
1256
|
mutationTools: step.mutationTools,
|
|
1218
1257
|
}));
|
|
1258
|
+
launched = true;
|
|
1259
|
+
aggregateUsage.input += run.usage.input;
|
|
1260
|
+
aggregateUsage.output += run.usage.output;
|
|
1261
|
+
aggregateUsage.cacheRead += run.usage.cacheRead;
|
|
1262
|
+
aggregateUsage.cacheWrite += run.usage.cacheWrite;
|
|
1263
|
+
aggregateUsage.cost += run.usage.cost;
|
|
1264
|
+
aggregateUsage.turns += run.usage.turns;
|
|
1219
1265
|
// A parked run still owes completion evidence when it actually finishes.
|
|
1220
1266
|
// Stopped/timedOut runs already have terminal failures; checking completion evidence there is meaningless.
|
|
1221
1267
|
const completionDiagnosticsEligible = !run.interrupted && !run.stopped && !run.timedOut;
|
|
@@ -1274,7 +1320,8 @@ export async function runSingleStepInner(
|
|
|
1274
1320
|
const completionGuardEnabled = isAgentContract(step.agentContract) ? step.completionGuard === true : step.completionGuard !== false;
|
|
1275
1321
|
const completionToolPlan = resolvedTaskToolPlan;
|
|
1276
1322
|
const completionTools = completionToolPlan ? (completionToolPlan.explicitToolAllowlist ? completionToolPlan.effectiveToolAllowlist : undefined) : step.tools;
|
|
1277
|
-
const
|
|
1323
|
+
const remoteGitChanged = run.nativeMachine?.initialGit && run.nativeMachine.finalGit ? run.nativeMachine.initialGit.head !== run.nativeMachine.finalGit.head || run.nativeMachine.initialGit.dirty !== run.nativeMachine.finalGit.dirty : undefined;
|
|
1324
|
+
const mutationEvidence = run.nativeMachine ? { source: "tracked-files" as const, trackedOnly: true as const, changedFiles: [], attemptedMutation: remoteGitChanged === true, ...(remoteGitChanged === undefined ? { unavailable: "Remote Git before/after evidence was incomplete." } : {}) } : collectTrackedMutationEvidence(mutationSnapshot, step.cwd ?? ctx.cwd);
|
|
1278
1325
|
finalMutationEvidence = mutationEvidence;
|
|
1279
1326
|
const completionMutationEvidence = ctx.trackedMutationEvidenceForCompletionGuard === false ? undefined : mutationEvidence;
|
|
1280
1327
|
const completionGuard = completionDiagnosticsEligible && run.exitCode === 0 && !run.error && !structuredError && !hiddenError?.hasError && !midToolExitError && !emptyOutputError && completionGuardEnabled
|
|
@@ -1340,15 +1387,6 @@ export async function runSingleStepInner(
|
|
|
1340
1387
|
: `${hiddenError.errorType} failed with exit code ${effectiveExitCode}`
|
|
1341
1388
|
: undefined);
|
|
1342
1389
|
const error = underlyingError ?? missingRequiredOutputError ?? completionEvidence.legacyFailureError;
|
|
1343
|
-
const attempt: ModelAttempt = omitUndefinedProperties({
|
|
1344
|
-
model: candidate ?? run.model ?? step.model ?? "default",
|
|
1345
|
-
success: effectiveExitCode === 0 && !error,
|
|
1346
|
-
exitCode: effectiveExitCode,
|
|
1347
|
-
error,
|
|
1348
|
-
usage: run.usage,
|
|
1349
|
-
});
|
|
1350
|
-
modelAttempts.push(attempt);
|
|
1351
|
-
if (!recoveringAbort && candidate) attemptedModels.push(candidate);
|
|
1352
1390
|
completionGuardTriggeredFinal = completionEvidence.guardTriggered && !underlyingError && !missingRequiredOutputError;
|
|
1353
1391
|
finalOutputSnapshot = outputSnapshot;
|
|
1354
1392
|
if (step.toolBudget) {
|
|
@@ -1366,11 +1404,14 @@ export async function runSingleStepInner(
|
|
|
1366
1404
|
});
|
|
1367
1405
|
const fileMutationEffect = completionEvidence.fileMutation ?? (missingRequiredOutputAfterMutation ? { status: "observed" as const, expected: completionEvidence.mutationExpected, attempted: true, evidence: mutationEvidence } : undefined);
|
|
1368
1406
|
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;
|
|
1369
|
-
|
|
1407
|
+
if (run.stopped || run.timedOut || ctx.timeoutSignal?.aborted || ctx.stopSignal?.aborted || ctx.skipAcceptance?.()) break singleLaunch;
|
|
1408
|
+
if (effectiveExitCode === 0 && !error) break singleLaunch;
|
|
1409
|
+
if (completionEvidence.guardTriggered) break singleLaunch;
|
|
1410
|
+
const recovery = planAbortRecovery({
|
|
1370
1411
|
messages: run.messages,
|
|
1371
1412
|
error,
|
|
1372
1413
|
sessionAvailable: Boolean(step.sessionFile && fs.existsSync(step.sessionFile)),
|
|
1373
|
-
alreadyResumed:
|
|
1414
|
+
alreadyResumed: attemptIndex > 0,
|
|
1374
1415
|
stopped: run.stopped || ctx.stopSignal?.aborted || ctx.skipAcceptance?.(),
|
|
1375
1416
|
interrupted: run.interrupted,
|
|
1376
1417
|
timedOut: run.timedOut || ctx.timeoutSignal?.aborted,
|
|
@@ -1380,83 +1421,26 @@ export async function runSingleStepInner(
|
|
|
1380
1421
|
acceptanceFailed: false,
|
|
1381
1422
|
currentTool: run.currentTool,
|
|
1382
1423
|
afterCompactionSettlement: run.afterCompactionSettlement,
|
|
1383
|
-
})
|
|
1384
|
-
if (
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
: abortRecovery.diagnostic;
|
|
1388
|
-
if (finalResult) finalResult.abortRecoveryDiagnostic = abortRecovery.diagnostic;
|
|
1424
|
+
});
|
|
1425
|
+
if (recovery.action === "resume") {
|
|
1426
|
+
recoveryTask = recovery.prompt;
|
|
1427
|
+
continue singleLaunch;
|
|
1389
1428
|
}
|
|
1390
|
-
if (
|
|
1391
|
-
|
|
1392
|
-
if (attempt.success) break modelAttemptsLoop;
|
|
1393
|
-
// The shared token is consumed before sibling creation; no third dispatch of any kind.
|
|
1394
|
-
if (recoveryState === "readonly-continuation") break modelAttemptsLoop;
|
|
1395
|
-
if (recoveringAbort) break modelAttemptsLoop;
|
|
1396
|
-
if (abortRecovery?.action === "resume") {
|
|
1397
|
-
recoveryState = "abort-recovery";
|
|
1398
|
-
nextAttemptTask = abortRecovery.prompt;
|
|
1399
|
-
attemptNotes.push("[abort-recovery] provider/transport abort after useful progress; resuming the retained child session once.");
|
|
1400
|
-
continue;
|
|
1429
|
+
if (recovery.diagnostic) {
|
|
1430
|
+
finalResult.abortRecoveryDiagnostic = recovery.diagnostic;
|
|
1401
1431
|
}
|
|
1402
|
-
if (completionEvidence.guardTriggered) break modelAttemptsLoop;
|
|
1403
1432
|
|
|
1404
|
-
const source = getSettledReadonlyChild(run);
|
|
1405
|
-
if (source) {
|
|
1406
|
-
const captured = launch.capture.completionIntentContext?.();
|
|
1407
|
-
const sourceModel = captured?.model;
|
|
1408
|
-
const retained = getReadonlySessionEvidence(source)!;
|
|
1409
|
-
// Modality/capacity assessment, not another history/provenance validator.
|
|
1410
|
-
// Images and unknown content have no certified token bound in this host slice.
|
|
1411
|
-
const textOnly = (JSON.parse(retained.contextJson) as Message[]).every((message) => typeof message.content === "string"
|
|
1412
|
-
|| (Array.isArray(message.content) && message.content.every((block) => ["text", "thinking", "toolCall"].includes(block.type))));
|
|
1413
|
-
const retainedBytes = Buffer.byteLength(retained.contextJson, "utf8");
|
|
1414
|
-
const resolvedCandidates = candidates.map((reference, index) => {
|
|
1415
|
-
// Only exact registry identities qualify; aliases/default guesses remain ineligible.
|
|
1416
|
-
const base = reference ? splitKnownThinkingSuffix(reference).baseModel : "";
|
|
1417
|
-
const slash = base.indexOf("/");
|
|
1418
|
-
const model = slash > 0 ? captured?.modelRegistry.find(base.slice(0, slash), base.slice(slash + 1)) : undefined;
|
|
1419
|
-
// Reserve the source window for runtime/system/tool overhead, plus a conservative
|
|
1420
|
-
// byte bound for the complete retained text and new prompt; never assume a fit
|
|
1421
|
-
// merely because a 429 request was sent. Equal/smaller windows remain denied.
|
|
1422
|
-
const compatible = textOnly && model && sourceModel && model.input?.includes("text")
|
|
1423
|
-
&& Number.isFinite(sourceModel.contextWindow) && sourceModel.contextWindow > 0
|
|
1424
|
-
&& model.contextWindow >= sourceModel.contextWindow + retainedBytes + Buffer.byteLength(READONLY_CONTINUATION_PROMPT, "utf8")
|
|
1425
|
-
&& model.maxTokens > 0 && model.maxTokens <= sourceModel.maxTokens;
|
|
1426
|
-
return { resolved: model ? { provider: model.provider, model: model.id, api: model.api } : undefined,
|
|
1427
|
-
tried: index <= modelIndex, compatibility: compatible ? "compatible" as const : "unknown" as const };
|
|
1428
|
-
});
|
|
1429
|
-
const plan = planReadonlyModelContinuation({ source, recoveryState, candidates: resolvedCandidates, currentIndex: modelIndex,
|
|
1430
|
-
lifecycleAllowsContinuation: lifecycleAllowsContinuation() && !run.interrupted && !run.stopped && !run.timedOut,
|
|
1431
|
-
effectsAllowContinuation: !run.currentTool && !run.observedMutationAttempt && !mutationEvidence.attemptedMutation
|
|
1432
|
-
&& !structuredError && !effectiveStructuredOutput && !missingRequiredOutputError && !toolAvailabilityError
|
|
1433
|
-
&& !completionEvidence.guardTriggered && !midToolExitError && !hiddenError?.hasError,
|
|
1434
|
-
budget: continuationBudget(), knownContextOverflow: isContextOverflow(error) });
|
|
1435
|
-
if (plan.kind === "continue" && canContinue()) {
|
|
1436
|
-
recoveryState = plan.recoveryState;
|
|
1437
|
-
const selected = resolvedCandidates[plan.candidateIndex]!.resolved!;
|
|
1438
|
-
readonlyContinuation = { source, expected: plan.expected, modelId: `${selected.provider}/${selected.model}` };
|
|
1439
|
-
nextAttemptTask = plan.prompt;
|
|
1440
|
-
modelIndex = plan.candidateIndex;
|
|
1441
|
-
attemptNotes.push(`[readonly-continuation] ${attempt.model} returned HTTP 429 after read-only progress; continuing the retained session once with ${candidates[modelIndex]}.`);
|
|
1442
|
-
continue;
|
|
1443
|
-
}
|
|
1444
|
-
}
|
|
1445
|
-
|
|
1446
|
-
const retryableModelFailure = isRetryableModelFailureAttempt({ error, messages: run.messages, toolCount: run.toolCount });
|
|
1447
|
-
if (retryableModelFailure) recordRetryableModelFailure(candidate ?? run.model ?? step.model, error);
|
|
1448
1433
|
if (isContextOverflow(error)) {
|
|
1449
1434
|
contextOverflow = true;
|
|
1450
|
-
|
|
1451
|
-
break modelAttemptsLoop;
|
|
1435
|
+
break singleLaunch;
|
|
1452
1436
|
}
|
|
1453
|
-
|
|
1454
|
-
attemptNotes.push(formatModelAttemptNote(attempt, candidates[modelIndex + 1]));
|
|
1455
|
-
modelIndex += 1;
|
|
1437
|
+
break singleLaunch;
|
|
1456
1438
|
}
|
|
1457
1439
|
|
|
1458
1440
|
const rawOutput = finalResult?.finalOutput ?? "";
|
|
1459
|
-
|
|
1441
|
+
let outputForPersistence = stripAcceptanceReport(rawOutput);
|
|
1442
|
+
if (!outputForPersistence.trim() && finalResult?.structuredOutput !== undefined)
|
|
1443
|
+
outputForPersistence = JSON.stringify(finalResult.structuredOutput, null, 2);
|
|
1460
1444
|
const resolvedOutput = step.outputPath && finalResult?.exitCode === 0
|
|
1461
1445
|
? resolveSingleOutput(step.outputPath, outputForPersistence, finalOutputSnapshot, step.outputClaimPath)
|
|
1462
1446
|
: { fullOutput: outputForPersistence };
|
|
@@ -1469,9 +1453,6 @@ export async function runSingleStepInner(
|
|
|
1469
1453
|
const output = stripAcceptanceReport(resolvedOutput.fullOutput);
|
|
1470
1454
|
const outputReference = resolvedOutput.savedPath ? formatSavedOutputReference(resolvedOutput.savedPath, output) : undefined;
|
|
1471
1455
|
let outputForSummary = output;
|
|
1472
|
-
if (attemptNotes.length > 0) {
|
|
1473
|
-
outputForSummary = `${attemptNotes.join("\n")}\n\n${outputForSummary}`.trim();
|
|
1474
|
-
}
|
|
1475
1456
|
if (finalResult?.stopped && !outputForSummary.trim()) {
|
|
1476
1457
|
outputForSummary = ctx.stopMessage ?? "Subagent stopped by user.";
|
|
1477
1458
|
}
|
|
@@ -1522,6 +1503,7 @@ export async function runSingleStepInner(
|
|
|
1522
1503
|
? { content: childWrittenOutput, path: step.outputPath, authoritative: step.outputMode === "file-only", durable: resolvedOutput.savedPath !== undefined }
|
|
1523
1504
|
: undefined,
|
|
1524
1505
|
cwd: step.cwd ?? ctx.cwd,
|
|
1506
|
+
stagedIndexBaseline,
|
|
1525
1507
|
signal: combinedAbortSignal([ctx.timeoutSignal, ctx.stopSignal]),
|
|
1526
1508
|
abortMessage: ctx.stopSignal?.aborted ? ctx.stopMessage ?? "Subagent stopped by user." : ctx.timeoutMessage ?? "Subagent timed out.",
|
|
1527
1509
|
reportOptional: isAgentContract(step.agentContract),
|
|
@@ -1542,6 +1524,12 @@ export async function runSingleStepInner(
|
|
|
1542
1524
|
const acceptanceFailure = effectiveAcceptance ? acceptanceFailureMessage(effectiveAcceptance) : undefined;
|
|
1543
1525
|
const acceptanceCanFailRun = acceptanceFailure && effectiveAcceptance?.explicit && (finalResult?.exitCode ?? 1) === 0 && !finalResult?.interrupted && !timedOutAfterAcceptance && !stoppedAfterAcceptance && !isAgentContract(step.agentContract);
|
|
1544
1526
|
const effectiveFinalExitCode = timedOutAfterAcceptance || stoppedAfterAcceptance ? 1 : acceptanceCanFailRun ? 1 : finalResult?.exitCode ?? 1;
|
|
1527
|
+
// A passing typed gate supplies the structured output for runs that have no
|
|
1528
|
+
// outputSchema of their own; preflight rejects the combination.
|
|
1529
|
+
const typedGate = typedVerifyOutput(effectiveAcceptance);
|
|
1530
|
+
if (typedGate && finalResult && finalResult.structuredOutput === undefined && effectiveFinalExitCode === 0) {
|
|
1531
|
+
finalResult = { ...finalResult, structuredOutput: typedGate.value };
|
|
1532
|
+
}
|
|
1545
1533
|
const intercomDetachReceipt = finalResult?.finalOutput === INTERCOM_DETACH_RECEIPT;
|
|
1546
1534
|
const baseFinalError = stoppedAfterAcceptance
|
|
1547
1535
|
? ctx.stopMessage ?? "Subagent stopped by user."
|
|
@@ -1556,7 +1544,7 @@ export async function runSingleStepInner(
|
|
|
1556
1544
|
abortRecoveryDiagnostic: effectiveFinalExitCode !== 0 ? finalResult?.abortRecoveryDiagnostic : undefined,
|
|
1557
1545
|
requiredOutput: effectiveFinalExitCode !== 0 ? finalResult?.effects?.settlementDiagnostic?.requiredOutput : undefined,
|
|
1558
1546
|
});
|
|
1559
|
-
const usage =
|
|
1547
|
+
const usage = launched ? aggregateUsage : undefined;
|
|
1560
1548
|
|
|
1561
1549
|
const artifactErrors = artifactPaths && ctx.artifactConfig?.enabled !== false
|
|
1562
1550
|
? persistStepArtifacts({
|
|
@@ -1574,8 +1562,8 @@ export async function runSingleStepInner(
|
|
|
1574
1562
|
task: PROMPT_REDACTED,
|
|
1575
1563
|
exitCode: effectiveFinalExitCode,
|
|
1576
1564
|
model: finalResult?.model,
|
|
1577
|
-
|
|
1578
|
-
|
|
1565
|
+
nativeMachine: finalResult?.nativeMachine,
|
|
1566
|
+
requestedModel: step.requestedModel,
|
|
1579
1567
|
usage,
|
|
1580
1568
|
error: effectiveFinalError,
|
|
1581
1569
|
acceptance: effectiveAcceptance,
|
|
@@ -1604,15 +1592,16 @@ export async function runSingleStepInner(
|
|
|
1604
1592
|
sessionFile: step.sessionFile,
|
|
1605
1593
|
intercomTarget: ctx.childIntercomTarget,
|
|
1606
1594
|
model: finalResult?.model,
|
|
1595
|
+
nativeMachine: finalResult?.nativeMachine,
|
|
1607
1596
|
thinking: resolveEffectiveThinking(finalResult?.model, step.thinking),
|
|
1608
|
-
|
|
1609
|
-
modelAttempts,
|
|
1597
|
+
requestedModel: step.requestedModel,
|
|
1610
1598
|
contextOverflow: contextOverflow || undefined,
|
|
1611
|
-
totalCost:
|
|
1599
|
+
totalCost: costSummaryFromUsage(usage),
|
|
1612
1600
|
usage,
|
|
1613
1601
|
artifactPaths,
|
|
1614
1602
|
savedOutputPath: finalizedOutput.savedPath,
|
|
1615
1603
|
outputSaveError: [resolvedOutput.saveError, artifactErrors.outputSaveError].filter(Boolean).join("\n") || undefined,
|
|
1604
|
+
artifactOutputSaveFailed: artifactErrors.outputSaveError ? true : undefined,
|
|
1616
1605
|
metadataSaveError: artifactErrors.metadataSaveError,
|
|
1617
1606
|
transcriptPath: transcriptWriter ? artifactPaths?.transcriptPath : undefined,
|
|
1618
1607
|
transcriptError: transcriptWriter?.getError(),
|
|
@@ -1856,8 +1845,12 @@ function missingRequiredOutputAfterUsefulMutation(result: SingleStepResult): boo
|
|
|
1856
1845
|
}
|
|
1857
1846
|
|
|
1858
1847
|
function partialExecutionWithUsefulMutation(result: SingleStepResult): boolean {
|
|
1848
|
+
if (result.execution?.status !== "partial") return false;
|
|
1849
|
+
// Pane-native external results are deliberately partial from bounded terminal
|
|
1850
|
+
// evidence alone; Pi mutation-based partials retain their existing behavior.
|
|
1851
|
+
if (result.runner?.type === "external-cli" && result.runner.machine) return true;
|
|
1859
1852
|
const fileMutation = result.effects?.fileMutation;
|
|
1860
|
-
return
|
|
1853
|
+
return fileMutation?.attempted === true || Boolean(fileMutation?.evidence?.changedFiles.length);
|
|
1861
1854
|
}
|
|
1862
1855
|
|
|
1863
1856
|
function partialEvidenceResult(result: SingleStepResult): boolean {
|
|
@@ -1960,6 +1953,7 @@ export async function runSubagent(
|
|
|
1960
1953
|
let currentActivityState: ActivityState | undefined;
|
|
1961
1954
|
let activityTimer: NodeJS.Timeout | undefined;
|
|
1962
1955
|
let timeoutTimer: NodeJS.Timeout | undefined;
|
|
1956
|
+
let checkpointTimer: NodeJS.Timeout | undefined;
|
|
1963
1957
|
let timedOut = false;
|
|
1964
1958
|
let stopped = false;
|
|
1965
1959
|
let usageBudgetExceeded = false;
|
|
@@ -1968,6 +1962,7 @@ export async function runSubagent(
|
|
|
1968
1962
|
const timeoutAbortController = new AbortController();
|
|
1969
1963
|
const stopAbortController = new AbortController();
|
|
1970
1964
|
const setupInterruptController = new AbortController();
|
|
1965
|
+
const runStopSignal = AbortSignal.any([timeoutAbortController.signal, stopAbortController.signal]);
|
|
1971
1966
|
const setupSignal = AbortSignal.any([timeoutAbortController.signal, stopAbortController.signal, setupInterruptController.signal]);
|
|
1972
1967
|
let previousCumulativeTokens: TokenUsage = { input: 0, output: 0, total: 0 };
|
|
1973
1968
|
let latestSessionFile: string | undefined;
|
|
@@ -2009,7 +2004,7 @@ export async function runSubagent(
|
|
|
2009
2004
|
model: task.model,
|
|
2010
2005
|
...(task.contextLimit !== undefined ? { contextLimit: task.contextLimit } : {}),
|
|
2011
2006
|
thinking: task.thinking,
|
|
2012
|
-
|
|
2007
|
+
requestedModel: task.requestedModel,
|
|
2013
2008
|
recentTools: [],
|
|
2014
2009
|
recentOutput: [],
|
|
2015
2010
|
}));
|
|
@@ -2063,7 +2058,7 @@ export async function runSubagent(
|
|
|
2063
2058
|
model: step.model,
|
|
2064
2059
|
...(step.contextLimit !== undefined ? { contextLimit: step.contextLimit } : {}),
|
|
2065
2060
|
thinking: step.thinking,
|
|
2066
|
-
|
|
2061
|
+
requestedModel: step.requestedModel,
|
|
2067
2062
|
recentTools: [],
|
|
2068
2063
|
recentOutput: [],
|
|
2069
2064
|
}));
|
|
@@ -2290,9 +2285,7 @@ export async function runSubagent(
|
|
|
2290
2285
|
sessionFile: step.sessionFile,
|
|
2291
2286
|
model: step.model,
|
|
2292
2287
|
thinking: step.thinking,
|
|
2293
|
-
|
|
2294
|
-
modelAttempts: step.modelAttempts,
|
|
2295
|
-
usage: usageFromAttempts(step.modelAttempts),
|
|
2288
|
+
requestedModel: step.requestedModel,
|
|
2296
2289
|
contextOverflow: step.contextOverflow,
|
|
2297
2290
|
})),
|
|
2298
2291
|
exitCode: state === "complete" || state === "paused" ? 0 : 1,
|
|
@@ -2457,6 +2450,8 @@ export async function runSubagent(
|
|
|
2457
2450
|
yield* nestedRuns(child.steps?.flatMap((step) => step.children ?? []));
|
|
2458
2451
|
}
|
|
2459
2452
|
};
|
|
2453
|
+
const isNestedControlDescendant = (run: NestedRunSummary): boolean =>
|
|
2454
|
+
!config.nestedSelf || run.path.some((entry) => entry.runId === id);
|
|
2460
2455
|
const interruptNestedAsyncDescendants = (): void => {
|
|
2461
2456
|
if (!config.nestedRoute) return;
|
|
2462
2457
|
let registry: ReturnType<typeof projectNestedEvents>;
|
|
@@ -2472,7 +2467,7 @@ export async function runSubagent(
|
|
|
2472
2467
|
return;
|
|
2473
2468
|
}
|
|
2474
2469
|
for (const run of nestedRuns(registry.children)) {
|
|
2475
|
-
if (run.state !== "running" && run.state !== "queued") continue;
|
|
2470
|
+
if (!isNestedControlDescendant(run) || (run.state !== "running" && run.state !== "queued")) continue;
|
|
2476
2471
|
const nestedAsyncDir = run.asyncDir ?? resolveNestedAsyncDir(config.nestedRoute.rootRunId, run);
|
|
2477
2472
|
if (!nestedAsyncDir) continue;
|
|
2478
2473
|
try {
|
|
@@ -2503,7 +2498,7 @@ export async function runSubagent(
|
|
|
2503
2498
|
return;
|
|
2504
2499
|
}
|
|
2505
2500
|
for (const run of nestedRuns(registry.children)) {
|
|
2506
|
-
if (run.state !== "running" && run.state !== "queued") continue;
|
|
2501
|
+
if (!isNestedControlDescendant(run) || (run.state !== "running" && run.state !== "queued")) continue;
|
|
2507
2502
|
const nestedAsyncDir = run.asyncDir ?? resolveNestedAsyncDir(config.nestedRoute.rootRunId, run);
|
|
2508
2503
|
if (!nestedAsyncDir) continue;
|
|
2509
2504
|
try {
|
|
@@ -2534,7 +2529,7 @@ export async function runSubagent(
|
|
|
2534
2529
|
return;
|
|
2535
2530
|
}
|
|
2536
2531
|
for (const run of nestedRuns(registry.children)) {
|
|
2537
|
-
if (run.state !== "running" && run.state !== "queued") continue;
|
|
2532
|
+
if (!isNestedControlDescendant(run) || (run.state !== "running" && run.state !== "queued")) continue;
|
|
2538
2533
|
const nestedAsyncDir = run.asyncDir ?? resolveNestedAsyncDir(config.nestedRoute.rootRunId, run);
|
|
2539
2534
|
if (!nestedAsyncDir) continue;
|
|
2540
2535
|
try {
|
|
@@ -2590,6 +2585,7 @@ export async function runSubagent(
|
|
|
2590
2585
|
}
|
|
2591
2586
|
const appendedSteps = requests.flatMap((request) => request.steps);
|
|
2592
2587
|
steps.push(...appendedSteps);
|
|
2588
|
+
flatSteps.push(...flattenSteps(appendedSteps));
|
|
2593
2589
|
const now = Date.now();
|
|
2594
2590
|
const pendingAppends = countPendingChainAppendRequests(asyncDir);
|
|
2595
2591
|
const added = appendRunnerStepsToStatus({
|
|
@@ -2623,9 +2619,66 @@ export async function runSubagent(
|
|
|
2623
2619
|
setOptionalProperty(groupNode, "acceptanceStatus", acceptance?.status ?? groupNode.acceptanceStatus);
|
|
2624
2620
|
};
|
|
2625
2621
|
|
|
2622
|
+
const EXTERNAL_GIT_PROBE_MIN_INTERVAL_MS = 2_000;
|
|
2623
|
+
type ExternalActivityEvidence = {
|
|
2624
|
+
cwd?: string;
|
|
2625
|
+
lastStreamActivityAt?: number;
|
|
2626
|
+
fingerprint?: string;
|
|
2627
|
+
probeInFlight?: Promise<void>;
|
|
2628
|
+
lastProbeStartedAt?: number;
|
|
2629
|
+
};
|
|
2630
|
+
const externalActivityEvidence = new Map<number, ExternalActivityEvidence>();
|
|
2631
|
+
const gitProbesByCwd = new Map<string, Promise<string | undefined>>();
|
|
2632
|
+
let periodicGitProbeFailure: { index: number; error: Error; message: string } | undefined;
|
|
2633
|
+
const reportedGitProbeErrors = new Set<string>();
|
|
2634
|
+
const reportGitProbeError = (externalCwd: string, error: unknown): void => {
|
|
2635
|
+
if (reportedGitProbeErrors.has(externalCwd)) return;
|
|
2636
|
+
reportedGitProbeErrors.add(externalCwd);
|
|
2637
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
2638
|
+
console.error(`[pi-subagents] Git activity evidence unavailable for '${externalCwd.slice(-300)}'; check the cwd and Git installation: ${detail.slice(0, 500)}`);
|
|
2639
|
+
};
|
|
2640
|
+
const readSharedGitFingerprint = (externalCwd: string): Promise<string | undefined> => {
|
|
2641
|
+
const inFlight = gitProbesByCwd.get(externalCwd);
|
|
2642
|
+
if (inFlight) return inFlight;
|
|
2643
|
+
const probe = readGitFingerprint(externalCwd, runStopSignal, (error) => reportGitProbeError(externalCwd, error)).finally(() => gitProbesByCwd.delete(externalCwd));
|
|
2644
|
+
gitProbesByCwd.set(externalCwd, probe);
|
|
2645
|
+
return probe;
|
|
2646
|
+
};
|
|
2647
|
+
const recordPeriodicGitProbeFailure = (index: number, error: unknown): void => {
|
|
2648
|
+
if (periodicGitProbeFailure) return;
|
|
2649
|
+
const cause = error instanceof Error ? error : new Error(String(error));
|
|
2650
|
+
const message = `PROCESS_TREE_UNVERIFIED: ${cause.message}`;
|
|
2651
|
+
periodicGitProbeFailure = { index, error: cause, message };
|
|
2652
|
+
statusPayload.error = message;
|
|
2653
|
+
const step = statusPayload.steps[index];
|
|
2654
|
+
if (step) step.error = message;
|
|
2655
|
+
statusPayload.lastUpdate = Date.now();
|
|
2656
|
+
writeStatusPayload();
|
|
2657
|
+
appendJsonl(eventsPath, JSON.stringify({
|
|
2658
|
+
type: "subagent.run.process_tree_unverified",
|
|
2659
|
+
ts: statusPayload.lastUpdate,
|
|
2660
|
+
runId: id,
|
|
2661
|
+
index,
|
|
2662
|
+
code: "PROCESS_TREE_UNVERIFIED",
|
|
2663
|
+
message,
|
|
2664
|
+
}));
|
|
2665
|
+
};
|
|
2666
|
+
const prepareExternalActivity = async (index: number, externalCwd: string, signal: AbortSignal): Promise<void> => {
|
|
2667
|
+
if (!controlConfig.enabled) return;
|
|
2668
|
+
const resolvedCwd = path.resolve(externalCwd);
|
|
2669
|
+
const evidence: ExternalActivityEvidence = { cwd: resolvedCwd, lastProbeStartedAt: performance.now() };
|
|
2670
|
+
externalActivityEvidence.set(index, evidence);
|
|
2671
|
+
evidence.fingerprint = await readGitFingerprint(resolvedCwd, signal, (error) => reportGitProbeError(resolvedCwd, error));
|
|
2672
|
+
};
|
|
2673
|
+
const recordExternalStreamActivity = (index: number): void => {
|
|
2674
|
+
const evidence = externalActivityEvidence.get(index) ?? {};
|
|
2675
|
+
externalActivityEvidence.set(index, evidence);
|
|
2676
|
+
evidence.lastStreamActivityAt = Date.now();
|
|
2677
|
+
};
|
|
2626
2678
|
const stepOutputActivityAt = (index: number): number => {
|
|
2627
2679
|
const step = statusPayload.steps[index];
|
|
2628
2680
|
let lastActivityAt = step?.lastActivityAt ?? step?.startedAt ?? overallStartTime;
|
|
2681
|
+
lastActivityAt = Math.max(lastActivityAt, externalActivityEvidence.get(index)?.lastStreamActivityAt ?? 0);
|
|
2629
2682
|
const outputPath = path.join(asyncDir, `output-${index}.log`);
|
|
2630
2683
|
try {
|
|
2631
2684
|
lastActivityAt = Math.max(lastActivityAt, fs.statSync(outputPath).mtimeMs);
|
|
@@ -2993,7 +3046,7 @@ export async function runSubagent(
|
|
|
2993
3046
|
return;
|
|
2994
3047
|
}
|
|
2995
3048
|
if (event.type === "message_end") {
|
|
2996
|
-
const next = applyChildWatchdogMessage(step.watchdog, event.message
|
|
3049
|
+
const next = applyChildWatchdogMessage(step.watchdog, event.message);
|
|
2997
3050
|
if (next) step.watchdog = next;
|
|
2998
3051
|
if (next && (event.message as { role?: unknown } | undefined)?.role === "custom") {
|
|
2999
3052
|
statusPayload.lastUpdate = now;
|
|
@@ -3134,7 +3187,7 @@ export async function runSubagent(
|
|
|
3134
3187
|
// A sibling may keep aggregate attention unchanged; publish this step's transition.
|
|
3135
3188
|
writeStatusPayload(step.activityState !== previousActivityState);
|
|
3136
3189
|
};
|
|
3137
|
-
const updateRunnerActivityState = (now: number): boolean => {
|
|
3190
|
+
const updateRunnerActivityState = (now: number, skipExternalProbeIndex?: number): boolean => {
|
|
3138
3191
|
if (!controlConfig.enabled) return false;
|
|
3139
3192
|
let changed = false;
|
|
3140
3193
|
let runLastActivityAt = statusPayload.lastActivityAt ?? overallStartTime;
|
|
@@ -3151,12 +3204,35 @@ export async function runSubagent(
|
|
|
3151
3204
|
config: controlConfig,
|
|
3152
3205
|
startedAt: step.startedAt ?? overallStartTime,
|
|
3153
3206
|
lastActivityAt,
|
|
3154
|
-
turnCount: step.turnCount,
|
|
3207
|
+
turnCount: flatSteps[index]?.runner?.type === "external-cli" ? 1 : step.turnCount,
|
|
3155
3208
|
currentTool: step.currentTool,
|
|
3156
3209
|
thinking: step.thinking,
|
|
3157
3210
|
now,
|
|
3158
3211
|
}));
|
|
3159
3212
|
if (idleState === "needs_attention") {
|
|
3213
|
+
const evidence = externalActivityEvidence.get(index);
|
|
3214
|
+
if (index !== skipExternalProbeIndex && evidence?.cwd && evidence.fingerprint) {
|
|
3215
|
+
if (evidence.probeInFlight) continue;
|
|
3216
|
+
if (performance.now() - (evidence.lastProbeStartedAt ?? 0) >= EXTERNAL_GIT_PROBE_MIN_INTERVAL_MS) {
|
|
3217
|
+
evidence.lastProbeStartedAt = performance.now();
|
|
3218
|
+
const baseline = evidence.fingerprint;
|
|
3219
|
+
evidence.probeInFlight = readSharedGitFingerprint(evidence.cwd).then((fingerprint) => {
|
|
3220
|
+
if (requiredStatusStep(statusPayload, index).status !== "running") return;
|
|
3221
|
+
if (fingerprint && fingerprint !== baseline) {
|
|
3222
|
+
evidence.fingerprint = fingerprint;
|
|
3223
|
+
evidence.lastStreamActivityAt = Date.now();
|
|
3224
|
+
updateRunnerActivityState(Date.now());
|
|
3225
|
+
} else {
|
|
3226
|
+
updateRunnerActivityState(Date.now(), index);
|
|
3227
|
+
}
|
|
3228
|
+
}).catch((error: unknown) => {
|
|
3229
|
+
recordPeriodicGitProbeFailure(index, error);
|
|
3230
|
+
}).finally(() => {
|
|
3231
|
+
delete evidence.probeInFlight;
|
|
3232
|
+
});
|
|
3233
|
+
continue;
|
|
3234
|
+
}
|
|
3235
|
+
}
|
|
3160
3236
|
const previous = step.activityState;
|
|
3161
3237
|
step.activityState = "needs_attention";
|
|
3162
3238
|
if (previous !== "needs_attention") {
|
|
@@ -3218,11 +3294,15 @@ export async function runSubagent(
|
|
|
3218
3294
|
};
|
|
3219
3295
|
const finalizeWorktree = async (setup: WorktreeSetup, stepIndex: number, flatStartIndex: number, action: () => void, deadlineAt?: number): Promise<void> => {
|
|
3220
3296
|
let admitted = false;
|
|
3221
|
-
try {
|
|
3222
|
-
|
|
3223
|
-
|
|
3224
|
-
|
|
3225
|
-
|
|
3297
|
+
try {
|
|
3298
|
+
await Promise.all([...externalActivityEvidence.values()].map((evidence) => evidence.probeInFlight).filter((probe): probe is Promise<void> => probe !== undefined));
|
|
3299
|
+
if (periodicGitProbeFailure) throw periodicGitProbeFailure.error;
|
|
3300
|
+
await withWorktreeTransaction(() => {
|
|
3301
|
+
if (deadlineAt !== undefined && Date.now() >= deadlineAt) throw new Error("Run deadline expired before worktree cleanup");
|
|
3302
|
+
admitted = true;
|
|
3303
|
+
action();
|
|
3304
|
+
});
|
|
3305
|
+
}
|
|
3226
3306
|
catch (error) {
|
|
3227
3307
|
if (admitted) throw error;
|
|
3228
3308
|
const reason = `Worktree finalization retained; manual reconciliation required: ${error instanceof Error ? error.message : String(error)}`;
|
|
@@ -3277,16 +3357,17 @@ export async function runSubagent(
|
|
|
3277
3357
|
interruptActiveChildren();
|
|
3278
3358
|
};
|
|
3279
3359
|
const stopRunner = () => {
|
|
3280
|
-
if (stopped || timedOut ||
|
|
3360
|
+
if (stopped || timedOut || (statusPayload.state !== "running" && statusPayload.state !== "paused")) return;
|
|
3281
3361
|
stopped = true;
|
|
3362
|
+
interrupted = false;
|
|
3282
3363
|
const now = Date.now();
|
|
3283
3364
|
statusPayload.stopped = true;
|
|
3284
3365
|
statusPayload.error = stopMessage;
|
|
3285
3366
|
currentActivityState = undefined;
|
|
3286
3367
|
delete statusPayload.activityState;
|
|
3287
3368
|
statusPayload.lastUpdate = now;
|
|
3288
|
-
for (const step of statusPayload.steps) {
|
|
3289
|
-
if (step.status !== "running" && step.status !== "pending") continue;
|
|
3369
|
+
for (const [index, step] of statusPayload.steps.entries()) {
|
|
3370
|
+
if (step.status !== "running" && step.status !== "pending" && step.status !== "paused") continue;
|
|
3290
3371
|
step.status = "stopped";
|
|
3291
3372
|
step.error = stopMessage;
|
|
3292
3373
|
step.exitCode = 1;
|
|
@@ -3295,6 +3376,8 @@ export async function runSubagent(
|
|
|
3295
3376
|
step.endedAt = now;
|
|
3296
3377
|
step.durationMs = step.startedAt ? now - step.startedAt : 0;
|
|
3297
3378
|
step.lastActivityAt = now;
|
|
3379
|
+
const result = results[index];
|
|
3380
|
+
if (result?.interrupted) results[index] = { ...result, output: stopMessage, error: stopMessage, exitCode: 1, success: false, interrupted: false, stopped: true };
|
|
3298
3381
|
}
|
|
3299
3382
|
writeStatusPayload();
|
|
3300
3383
|
appendJsonl(eventsPath, JSON.stringify({
|
|
@@ -3366,6 +3449,30 @@ export async function runSubagent(
|
|
|
3366
3449
|
const remainingMs = Math.max(0, config.deadlineAt - Date.now());
|
|
3367
3450
|
timeoutTimer = setTimeout(timeoutRunner, remainingMs);
|
|
3368
3451
|
timeoutTimer.unref?.();
|
|
3452
|
+
// Route the pre-deadline checkpoint like any external steer so its lifecycle records the receipt.
|
|
3453
|
+
const checkpointBeforeDeadlineMs = config.checkpointBeforeDeadlineMs;
|
|
3454
|
+
const checkpointDelayMs = checkpointBeforeDeadlineMs === undefined || !Number.isInteger(checkpointBeforeDeadlineMs) || checkpointBeforeDeadlineMs <= 0
|
|
3455
|
+
? undefined
|
|
3456
|
+
: remainingMs - checkpointBeforeDeadlineMs;
|
|
3457
|
+
if (checkpointDelayMs !== undefined && checkpointDelayMs >= 1_000) {
|
|
3458
|
+
const deadlineAt = config.deadlineAt;
|
|
3459
|
+
checkpointTimer = setTimeout(() => {
|
|
3460
|
+
checkpointTimer = undefined;
|
|
3461
|
+
if (timedOut || stopped || interrupted) return;
|
|
3462
|
+
if (!statusPayload.steps.some((step) => step.status === "running")) return;
|
|
3463
|
+
const now = Date.now();
|
|
3464
|
+
const seconds = Math.round(Math.max(0, deadlineAt - now) / 1000);
|
|
3465
|
+
deliverSteerRequest({
|
|
3466
|
+
type: "steer",
|
|
3467
|
+
id: `deadline-checkpoint-${now}`,
|
|
3468
|
+
ts: now,
|
|
3469
|
+
mode: "steer",
|
|
3470
|
+
source: "deadline-checkpoint",
|
|
3471
|
+
message: `Deadline checkpoint from the runner: this run is killed in about ${seconds} seconds. Finish the current tool call only, then stop and reply with a handoff: changed files, build/test state, remaining work, and commit/PR state. Do not start new work.`,
|
|
3472
|
+
});
|
|
3473
|
+
}, checkpointDelayMs);
|
|
3474
|
+
checkpointTimer.unref?.();
|
|
3475
|
+
}
|
|
3369
3476
|
}
|
|
3370
3477
|
appendJsonl(
|
|
3371
3478
|
eventsPath,
|
|
@@ -3411,14 +3518,7 @@ export async function runSubagent(
|
|
|
3411
3518
|
const thinkingOverride = step.thinkingOverrides?.[itemIndex];
|
|
3412
3519
|
const model = thinkingOverride ? applyThinkingSuffix(step.parallel.model, thinkingOverride, true) : step.parallel.model;
|
|
3413
3520
|
const configThinking = thinkingOverride ? thinkingOverride : step.parallel.thinking;
|
|
3414
|
-
|
|
3415
|
-
? step.parallel.modelCandidates.length > 0
|
|
3416
|
-
? step.parallel.modelCandidates.map((candidate) => thinkingOverride ? applyThinkingSuffix(candidate, thinkingOverride, true) ?? candidate : candidate)
|
|
3417
|
-
: [undefined]
|
|
3418
|
-
: model ? [model] : [undefined];
|
|
3419
|
-
for (const candidate of candidates) {
|
|
3420
|
-
assertThinkingWithinCeiling({ model: candidate, configThinking, ceiling: step.parallel.thinkingCeiling, agent: step.parallel.agent, runId: id });
|
|
3421
|
-
}
|
|
3521
|
+
assertThinkingWithinCeiling({ model, configThinking, ceiling: step.parallel.thinkingCeiling, agent: step.parallel.agent, runId: id });
|
|
3422
3522
|
}
|
|
3423
3523
|
if (materialized.collectedOnEmpty) await validateDynamicCollection(step.collect.outputSchema, materialized.collectedOnEmpty);
|
|
3424
3524
|
if (!config.runFanoutBudget) throw new Error("Async runner is missing its run fan-out budget identity.");
|
|
@@ -3551,10 +3651,6 @@ export async function runSubagent(
|
|
|
3551
3651
|
...(thinkingOverride ? {
|
|
3552
3652
|
...(model ? { model } : {}),
|
|
3553
3653
|
...(thinking ? { thinking } : {}),
|
|
3554
|
-
...(step.parallel.modelCandidates ? { modelCandidates: step.parallel.modelCandidates.flatMap((candidate) => {
|
|
3555
|
-
const resolved = applyThinkingSuffix(candidate, thinkingOverride, true);
|
|
3556
|
-
return resolved ? [resolved] : [];
|
|
3557
|
-
}) } : {}),
|
|
3558
3654
|
} : {}),
|
|
3559
3655
|
structuredOutputSchema: step.parallel.structuredOutputSchema ?? step.parallel.structuredOutput?.schema,
|
|
3560
3656
|
});
|
|
@@ -3581,7 +3677,7 @@ export async function runSubagent(
|
|
|
3581
3677
|
...(task.contextLimit !== undefined ? { contextLimit: task.contextLimit } : {}),
|
|
3582
3678
|
...(task.thinking ? { thinking: task.thinking } : {}),
|
|
3583
3679
|
...(task.thinkingCeiling ? { thinkingCeiling: task.thinkingCeiling } : {}),
|
|
3584
|
-
...(task.
|
|
3680
|
+
...(task.requestedModel ? { requestedModel: task.requestedModel } : {}),
|
|
3585
3681
|
recentTools: [],
|
|
3586
3682
|
recentOutput: [],
|
|
3587
3683
|
});
|
|
@@ -3710,6 +3806,8 @@ export async function runSubagent(
|
|
|
3710
3806
|
onAttemptStart: (attempt) => updateStepModel(fi, attempt.model, attempt.thinking, attempt.contextLimit),
|
|
3711
3807
|
onChildEvent: (event) => updateStepFromChildEvent(fi, event),
|
|
3712
3808
|
onExternalProcess: (process) => updateExternalProcess(fi, process),
|
|
3809
|
+
prepareExternalActivity: (externalCwd, signal) => prepareExternalActivity(fi, externalCwd, signal),
|
|
3810
|
+
onExternalStreamActivity: () => recordExternalStreamActivity(fi),
|
|
3713
3811
|
onExternalJob: (externalJob) => updateExternalJob(fi, externalJob),
|
|
3714
3812
|
skipAcceptance: () => timedOut || stopped || childStopRequests.has(fi),
|
|
3715
3813
|
usageBudgetExhausted: continuationUsageBudgetExhausted,
|
|
@@ -3719,7 +3817,7 @@ export async function runSubagent(
|
|
|
3719
3817
|
const taskEndTime = Date.now();
|
|
3720
3818
|
const childInterrupted = singleResult.interrupted === true;
|
|
3721
3819
|
const childStopped = singleResult.stopped === true;
|
|
3722
|
-
requiredStatusStep(statusPayload, fi).status = stopped || childStopped ? "stopped" : timedOut ? "failed" : childInterrupted ? "paused" : singleResult.exitCode === 0 ? "complete" : "failed";
|
|
3820
|
+
requiredStatusStep(statusPayload, fi).status = stopped || childStopped ? "stopped" : timedOut ? "failed" : childInterrupted ? "paused" : singleResult.execution?.status === "partial" ? "partial" : singleResult.exitCode === 0 ? "complete" : "failed";
|
|
3723
3821
|
requiredStatusStep(statusPayload, fi).endedAt = taskEndTime;
|
|
3724
3822
|
requiredStatusStep(statusPayload, fi).durationMs = taskEndTime - taskStartTime;
|
|
3725
3823
|
requiredStatusStep(statusPayload, fi).exitCode = stopped || childStopped ? 1 : timedOut ? 1 : childInterrupted ? 0 : singleResult.exitCode;
|
|
@@ -3732,8 +3830,7 @@ export async function runSubagent(
|
|
|
3732
3830
|
setOptionalProperty(requiredStatusStep(statusPayload, fi), "sessionName", singleResult.sessionName);
|
|
3733
3831
|
setOptionalProperty(requiredStatusStep(statusPayload, fi), "model", singleResult.model);
|
|
3734
3832
|
setOptionalProperty(requiredStatusStep(statusPayload, fi), "thinking", resolveEffectiveThinking(singleResult.model, requiredStatusStep(statusPayload, fi).thinking));
|
|
3735
|
-
setOptionalProperty(requiredStatusStep(statusPayload, fi), "
|
|
3736
|
-
setOptionalProperty(requiredStatusStep(statusPayload, fi), "modelAttempts", singleResult.modelAttempts);
|
|
3833
|
+
setOptionalProperty(requiredStatusStep(statusPayload, fi), "requestedModel", singleResult.requestedModel);
|
|
3737
3834
|
setOptionalProperty(requiredStatusStep(statusPayload, fi), "contextOverflow", singleResult.contextOverflow);
|
|
3738
3835
|
setOptionalProperty(requiredStatusStep(statusPayload, fi), "totalCost", singleResult.totalCost);
|
|
3739
3836
|
if (singleResult.totalCost) {
|
|
@@ -3790,7 +3887,7 @@ export async function runSubagent(
|
|
|
3790
3887
|
output: pr.output,
|
|
3791
3888
|
outputState: pr.outputState,
|
|
3792
3889
|
error: pr.error,
|
|
3793
|
-
success: pr.stopped !== true && pr.interrupted !== true && pr.exitCode === 0,
|
|
3890
|
+
success: pr.stopped !== true && pr.interrupted !== true && pr.exitCode === 0 && pr.execution?.status !== "partial",
|
|
3794
3891
|
exitCode: pr.interrupted === true ? 0 : pr.exitCode,
|
|
3795
3892
|
skipped: pr.skipped,
|
|
3796
3893
|
interrupted: pr.interrupted,
|
|
@@ -3802,12 +3899,13 @@ export async function runSubagent(
|
|
|
3802
3899
|
intercomTarget: pr.intercomTarget,
|
|
3803
3900
|
model: pr.model,
|
|
3804
3901
|
thinking: pr.thinking,
|
|
3805
|
-
|
|
3806
|
-
modelAttempts: pr.modelAttempts,
|
|
3902
|
+
requestedModel: pr.requestedModel,
|
|
3807
3903
|
contextOverflow: pr.contextOverflow,
|
|
3808
3904
|
totalCost: pr.totalCost,
|
|
3809
3905
|
usage: pr.usage,
|
|
3810
3906
|
artifactPaths: pr.artifactPaths,
|
|
3907
|
+
outputSaveError: pr.outputSaveError,
|
|
3908
|
+
artifactOutputSaveFailed: pr.artifactOutputSaveFailed,
|
|
3811
3909
|
transcriptPath: pr.transcriptPath,
|
|
3812
3910
|
transcriptError: pr.transcriptError,
|
|
3813
3911
|
effects: pr.effects,
|
|
@@ -4122,6 +4220,8 @@ export async function runSubagent(
|
|
|
4122
4220
|
onAttemptStart: (attempt) => updateStepModel(fi, attempt.model, attempt.thinking, attempt.contextLimit),
|
|
4123
4221
|
onChildEvent: (event) => updateStepFromChildEvent(fi, event),
|
|
4124
4222
|
onExternalProcess: (process) => updateExternalProcess(fi, process),
|
|
4223
|
+
prepareExternalActivity: (externalCwd, signal) => prepareExternalActivity(fi, externalCwd, signal),
|
|
4224
|
+
onExternalStreamActivity: () => recordExternalStreamActivity(fi),
|
|
4125
4225
|
onExternalJob: (externalJob) => updateExternalJob(fi, externalJob),
|
|
4126
4226
|
skipAcceptance: () => timedOut || stopped || childStopRequests.has(fi),
|
|
4127
4227
|
usageBudgetExhausted: continuationUsageBudgetExhausted,
|
|
@@ -4137,7 +4237,7 @@ export async function runSubagent(
|
|
|
4137
4237
|
const childInterrupted = singleResult.interrupted === true;
|
|
4138
4238
|
const childStopped = singleResult.stopped === true;
|
|
4139
4239
|
|
|
4140
|
-
requiredStatusStep(statusPayload, fi).status = stopped || childStopped ? "stopped" : timedOut ? "failed" : childInterrupted ? "paused" : singleResult.exitCode === 0 ? "complete" : "failed";
|
|
4240
|
+
requiredStatusStep(statusPayload, fi).status = stopped || childStopped ? "stopped" : timedOut ? "failed" : childInterrupted ? "paused" : singleResult.execution?.status === "partial" ? "partial" : singleResult.exitCode === 0 ? "complete" : "failed";
|
|
4141
4241
|
requiredStatusStep(statusPayload, fi).endedAt = taskEndTime;
|
|
4142
4242
|
requiredStatusStep(statusPayload, fi).durationMs = taskDuration;
|
|
4143
4243
|
requiredStatusStep(statusPayload, fi).exitCode = stopped || childStopped ? 1 : timedOut ? 1 : childInterrupted ? 0 : singleResult.exitCode;
|
|
@@ -4150,8 +4250,7 @@ export async function runSubagent(
|
|
|
4150
4250
|
setOptionalProperty(requiredStatusStep(statusPayload, fi), "sessionName", singleResult.sessionName);
|
|
4151
4251
|
setOptionalProperty(requiredStatusStep(statusPayload, fi), "model", singleResult.model);
|
|
4152
4252
|
setOptionalProperty(requiredStatusStep(statusPayload, fi), "thinking", resolveEffectiveThinking(singleResult.model, requiredStatusStep(statusPayload, fi).thinking));
|
|
4153
|
-
setOptionalProperty(requiredStatusStep(statusPayload, fi), "
|
|
4154
|
-
setOptionalProperty(requiredStatusStep(statusPayload, fi), "modelAttempts", singleResult.modelAttempts);
|
|
4253
|
+
setOptionalProperty(requiredStatusStep(statusPayload, fi), "requestedModel", singleResult.requestedModel);
|
|
4155
4254
|
setOptionalProperty(requiredStatusStep(statusPayload, fi), "contextOverflow", singleResult.contextOverflow);
|
|
4156
4255
|
setOptionalProperty(requiredStatusStep(statusPayload, fi), "totalCost", singleResult.totalCost);
|
|
4157
4256
|
if (singleResult.totalCost) {
|
|
@@ -4218,7 +4317,7 @@ export async function runSubagent(
|
|
|
4218
4317
|
const sessionTokens = config.sessionDir
|
|
4219
4318
|
? parseSessionTokens(path.join(config.sessionDir, `parallel-${t}`))
|
|
4220
4319
|
: null;
|
|
4221
|
-
const fallbackTokens =
|
|
4320
|
+
const fallbackTokens = tokenUsageFromUsage(parallelResults[t]?.usage);
|
|
4222
4321
|
const observedTokens = requiredStatusStep(statusPayload, fi).tokens;
|
|
4223
4322
|
const taskTokens = sessionTokens ?? (fallbackTokens
|
|
4224
4323
|
? { ...fallbackTokens, ...(observedTokens?.window !== undefined ? { window: observedTokens.window } : {}), ...(observedTokens?.windowPeak !== undefined ? { windowPeak: observedTokens.windowPeak } : {}) }
|
|
@@ -4249,7 +4348,7 @@ export async function runSubagent(
|
|
|
4249
4348
|
output: pr.output,
|
|
4250
4349
|
outputState: pr.outputState,
|
|
4251
4350
|
error: pr.error,
|
|
4252
|
-
success: pr.stopped !== true && pr.interrupted !== true && pr.exitCode === 0,
|
|
4351
|
+
success: pr.stopped !== true && pr.interrupted !== true && pr.exitCode === 0 && pr.execution?.status !== "partial",
|
|
4253
4352
|
exitCode: pr.interrupted === true ? 0 : pr.exitCode,
|
|
4254
4353
|
skipped: pr.skipped,
|
|
4255
4354
|
interrupted: pr.interrupted,
|
|
@@ -4261,12 +4360,13 @@ export async function runSubagent(
|
|
|
4261
4360
|
intercomTarget: pr.intercomTarget,
|
|
4262
4361
|
model: pr.model,
|
|
4263
4362
|
thinking: pr.thinking,
|
|
4264
|
-
|
|
4265
|
-
modelAttempts: pr.modelAttempts,
|
|
4363
|
+
requestedModel: pr.requestedModel,
|
|
4266
4364
|
contextOverflow: pr.contextOverflow,
|
|
4267
4365
|
totalCost: pr.totalCost,
|
|
4268
4366
|
usage: pr.usage,
|
|
4269
4367
|
artifactPaths: pr.artifactPaths,
|
|
4368
|
+
outputSaveError: pr.outputSaveError,
|
|
4369
|
+
artifactOutputSaveFailed: pr.artifactOutputSaveFailed,
|
|
4270
4370
|
transcriptPath: pr.transcriptPath,
|
|
4271
4371
|
transcriptError: pr.transcriptError,
|
|
4272
4372
|
effects: pr.effects,
|
|
@@ -4299,7 +4399,7 @@ export async function runSubagent(
|
|
|
4299
4399
|
exitCode: r.exitCode,
|
|
4300
4400
|
error: r.error,
|
|
4301
4401
|
model: r.model,
|
|
4302
|
-
|
|
4402
|
+
requestedModel: r.requestedModel,
|
|
4303
4403
|
})),
|
|
4304
4404
|
);
|
|
4305
4405
|
if (worktreeSetup) {
|
|
@@ -4523,6 +4623,8 @@ export async function runSubagent(
|
|
|
4523
4623
|
onAttemptStart: (attempt) => updateStepModel(flatIndex, attempt.model, attempt.thinking, attempt.contextLimit),
|
|
4524
4624
|
onChildEvent: (event) => updateStepFromChildEvent(flatIndex, event),
|
|
4525
4625
|
onExternalProcess: (process) => updateExternalProcess(flatIndex, process),
|
|
4626
|
+
prepareExternalActivity: (externalCwd, signal) => prepareExternalActivity(flatIndex, externalCwd, signal),
|
|
4627
|
+
onExternalStreamActivity: () => recordExternalStreamActivity(flatIndex),
|
|
4526
4628
|
onExternalJob: (externalJob) => updateExternalJob(flatIndex, externalJob),
|
|
4527
4629
|
skipAcceptance: () => timedOut || stopped || childStopRequests.has(flatIndex),
|
|
4528
4630
|
usageBudgetExhausted: continuationUsageBudgetExhausted,
|
|
@@ -4550,19 +4652,20 @@ export async function runSubagent(
|
|
|
4550
4652
|
output: stopped || childStopped ? stopMessage : timedOut ? singleResult.output || (timeoutMessage ?? "Subagent timed out.") : singleResult.output,
|
|
4551
4653
|
outputState: singleResult.outputState,
|
|
4552
4654
|
error: stopped || childStopped ? stopMessage : timedOut ? (timeoutMessage ?? "Subagent timed out.") : singleResult.error,
|
|
4553
|
-
success: !stopped && !childStopped && !timedOut && singleResult.interrupted !== true && singleResult.exitCode === 0,
|
|
4655
|
+
success: !stopped && !childStopped && !timedOut && singleResult.interrupted !== true && singleResult.exitCode === 0 && singleResult.execution?.status !== "partial",
|
|
4554
4656
|
exitCode: stopped || childStopped ? 1 : timedOut ? 1 : singleResult.interrupted === true ? 0 : singleResult.exitCode,
|
|
4555
4657
|
sessionFile: singleResult.sessionFile,
|
|
4556
4658
|
intercomTarget: singleResult.intercomTarget,
|
|
4557
4659
|
model: singleResult.model,
|
|
4558
4660
|
thinking: singleResult.thinking,
|
|
4559
|
-
|
|
4560
|
-
modelAttempts: singleResult.modelAttempts,
|
|
4661
|
+
requestedModel: singleResult.requestedModel,
|
|
4561
4662
|
contextOverflow: singleResult.contextOverflow,
|
|
4562
4663
|
totalCost: singleResult.totalCost,
|
|
4563
4664
|
usage: singleResult.usage,
|
|
4564
4665
|
artifactPaths: singleResult.artifactPaths,
|
|
4565
4666
|
savedOutputPath: singleResult.savedOutputPath,
|
|
4667
|
+
outputSaveError: singleResult.outputSaveError,
|
|
4668
|
+
artifactOutputSaveFailed: singleResult.artifactOutputSaveFailed,
|
|
4566
4669
|
transcriptPath: singleResult.transcriptPath,
|
|
4567
4670
|
transcriptError: singleResult.transcriptError,
|
|
4568
4671
|
effects: singleResult.effects,
|
|
@@ -4607,7 +4710,7 @@ export async function runSubagent(
|
|
|
4607
4710
|
if (cumulativeTokens) {
|
|
4608
4711
|
previousCumulativeTokens = cumulativeTokens;
|
|
4609
4712
|
} else {
|
|
4610
|
-
const fallbackTokens =
|
|
4713
|
+
const fallbackTokens = tokenUsageFromUsage(singleResult.usage);
|
|
4611
4714
|
const observedTokens = requiredStatusStep(statusPayload, flatIndex).tokens;
|
|
4612
4715
|
stepTokens = fallbackTokens
|
|
4613
4716
|
? { ...fallbackTokens, ...(observedTokens?.window !== undefined ? { window: observedTokens.window } : {}), ...(observedTokens?.windowPeak !== undefined ? { windowPeak: observedTokens.windowPeak } : {}) }
|
|
@@ -4627,7 +4730,7 @@ export async function runSubagent(
|
|
|
4627
4730
|
|
|
4628
4731
|
const stepEndTime = Date.now();
|
|
4629
4732
|
const childInterrupted = singleResult.interrupted === true;
|
|
4630
|
-
requiredStatusStep(statusPayload, flatIndex).status = stopped || childStopped ? "stopped" : timedOut ? "failed" : childInterrupted ? "paused" : singleResult.exitCode === 0 ? "complete" : "failed";
|
|
4733
|
+
requiredStatusStep(statusPayload, flatIndex).status = stopped || childStopped ? "stopped" : timedOut ? "failed" : childInterrupted ? "paused" : singleResult.execution?.status === "partial" ? "partial" : singleResult.exitCode === 0 ? "complete" : "failed";
|
|
4631
4734
|
requiredStatusStep(statusPayload, flatIndex).endedAt = stepEndTime;
|
|
4632
4735
|
requiredStatusStep(statusPayload, flatIndex).durationMs = stepEndTime - stepStartTime;
|
|
4633
4736
|
requiredStatusStep(statusPayload, flatIndex).exitCode = stopped || childStopped ? 1 : timedOut ? 1 : childInterrupted ? 0 : singleResult.exitCode;
|
|
@@ -4640,8 +4743,7 @@ export async function runSubagent(
|
|
|
4640
4743
|
setOptionalProperty(requiredStatusStep(statusPayload, flatIndex), "sessionName", singleResult.sessionName);
|
|
4641
4744
|
setOptionalProperty(requiredStatusStep(statusPayload, flatIndex), "model", singleResult.model);
|
|
4642
4745
|
setOptionalProperty(requiredStatusStep(statusPayload, flatIndex), "thinking", resolveEffectiveThinking(singleResult.model, requiredStatusStep(statusPayload, flatIndex).thinking));
|
|
4643
|
-
setOptionalProperty(requiredStatusStep(statusPayload, flatIndex), "
|
|
4644
|
-
setOptionalProperty(requiredStatusStep(statusPayload, flatIndex), "modelAttempts", singleResult.modelAttempts);
|
|
4746
|
+
setOptionalProperty(requiredStatusStep(statusPayload, flatIndex), "requestedModel", singleResult.requestedModel);
|
|
4645
4747
|
setOptionalProperty(requiredStatusStep(statusPayload, flatIndex), "contextOverflow", singleResult.contextOverflow);
|
|
4646
4748
|
setOptionalProperty(requiredStatusStep(statusPayload, flatIndex), "totalCost", singleResult.totalCost);
|
|
4647
4749
|
setOptionalProperty(requiredStatusStep(statusPayload, flatIndex), "error", stopped || childStopped ? stopMessage : timedOut ? (timeoutMessage ?? "Subagent timed out.") : singleResult.error);
|
|
@@ -4756,6 +4858,34 @@ export async function runSubagent(
|
|
|
4756
4858
|
}
|
|
4757
4859
|
}
|
|
4758
4860
|
|
|
4861
|
+
await Promise.all([...externalActivityEvidence.values()].map((evidence) => evidence.probeInFlight).filter((probe): probe is Promise<void> => probe !== undefined));
|
|
4862
|
+
if (periodicGitProbeFailure) {
|
|
4863
|
+
stopped = false;
|
|
4864
|
+
timedOut = false;
|
|
4865
|
+
interrupted = false;
|
|
4866
|
+
delete statusPayload.stopped;
|
|
4867
|
+
delete statusPayload.timedOut;
|
|
4868
|
+
statusPayload.error = periodicGitProbeFailure.message;
|
|
4869
|
+
const step = statusPayload.steps[periodicGitProbeFailure.index];
|
|
4870
|
+
if (step) {
|
|
4871
|
+
step.status = "failed";
|
|
4872
|
+
step.error = periodicGitProbeFailure.message;
|
|
4873
|
+
step.exitCode = 1;
|
|
4874
|
+
delete step.stopped;
|
|
4875
|
+
delete step.timedOut;
|
|
4876
|
+
}
|
|
4877
|
+
const result = results[periodicGitProbeFailure.index];
|
|
4878
|
+
if (result) {
|
|
4879
|
+
result.success = false;
|
|
4880
|
+
result.exitCode = 1;
|
|
4881
|
+
result.error = periodicGitProbeFailure.message;
|
|
4882
|
+
result.output = periodicGitProbeFailure.message;
|
|
4883
|
+
result.stopped = false;
|
|
4884
|
+
result.timedOut = false;
|
|
4885
|
+
result.interrupted = false;
|
|
4886
|
+
}
|
|
4887
|
+
}
|
|
4888
|
+
|
|
4759
4889
|
let summary = results.map((r) => `${r.agent}:\n${r.output || (r.exitCode !== 0 ? r.error : undefined) || "(no output)"}`).join("\n\n");
|
|
4760
4890
|
let truncated = false;
|
|
4761
4891
|
|
|
@@ -4821,9 +4951,15 @@ export async function runSubagent(
|
|
|
4821
4951
|
clearTimeout(timeoutTimer);
|
|
4822
4952
|
timeoutTimer = undefined;
|
|
4823
4953
|
}
|
|
4954
|
+
if (checkpointTimer) {
|
|
4955
|
+
clearTimeout(checkpointTimer);
|
|
4956
|
+
checkpointTimer = undefined;
|
|
4957
|
+
}
|
|
4824
4958
|
if (!timedOut && !stopped && !interrupted && config.timeoutMs !== undefined && timeoutMessage !== undefined && results.some((result) => result.timedOut === true && result.error?.startsWith(timeoutMessage))) {
|
|
4825
4959
|
timedOut = true;
|
|
4826
4960
|
}
|
|
4961
|
+
disposeControlInbox();
|
|
4962
|
+
for (const request of consumeStopRequestPayloads(asyncDir)) stopChildStep(request);
|
|
4827
4963
|
const signalTerminated = !stopped && !timedOut && !interrupted && results.some((result) => result.exitCode !== 0 && isUnexplainedProcessSignal(omitUndefinedProperties({
|
|
4828
4964
|
processSignal: result.processSignal,
|
|
4829
4965
|
interrupted: result.interrupted,
|
|
@@ -4838,7 +4974,6 @@ export async function runSubagent(
|
|
|
4838
4974
|
});
|
|
4839
4975
|
statusPayload.state = stopped || signalTerminated ? "stopped" : timedOut || usageBudgetExceeded ? "failed" : interrupted ? "paused" : results.every((r) => r.success) ? "complete" : partialWithEvidence ? "partial" : "failed";
|
|
4840
4976
|
closeSteerInbox(asyncDir, statusPayload.state, (filePath, payload) => runPersistence.write(filePath, payload));
|
|
4841
|
-
disposeControlInbox();
|
|
4842
4977
|
for (const request of consumeSteerRequests(asyncDir)) deliverSteerRequest(request);
|
|
4843
4978
|
const effectiveSessionFile = sessionFile ?? latestSessionFile;
|
|
4844
4979
|
const steeringLifecycle = steeringStatus(statusPayload);
|
|
@@ -4874,7 +5009,7 @@ export async function runSubagent(
|
|
|
4874
5009
|
statusPayload.activityState = "needs_attention";
|
|
4875
5010
|
statusPayload.error = partialResult?.error ?? statusPayload.error;
|
|
4876
5011
|
for (const step of statusPayload.steps) {
|
|
4877
|
-
if (step.status === "failed") step.activityState = "needs_attention";
|
|
5012
|
+
if (step.status === "failed" || step.status === "partial") step.activityState = "needs_attention";
|
|
4878
5013
|
}
|
|
4879
5014
|
}
|
|
4880
5015
|
statusPayload.endedAt = runEndedAt;
|
|
@@ -4931,14 +5066,14 @@ export async function runSubagent(
|
|
|
4931
5066
|
intercomTarget: r.intercomTarget,
|
|
4932
5067
|
model: r.model,
|
|
4933
5068
|
thinking: r.thinking,
|
|
4934
|
-
|
|
4935
|
-
modelAttempts: r.modelAttempts,
|
|
5069
|
+
requestedModel: r.requestedModel,
|
|
4936
5070
|
contextOverflow: r.contextOverflow,
|
|
4937
5071
|
totalCost: r.totalCost,
|
|
4938
5072
|
usage: r.usage,
|
|
4939
5073
|
artifactPaths: r.artifactPaths,
|
|
4940
5074
|
savedOutputPath: r.savedOutputPath,
|
|
4941
5075
|
outputSaveError: r.outputSaveError,
|
|
5076
|
+
artifactOutputSaveFailed: r.artifactOutputSaveFailed,
|
|
4942
5077
|
metadataSaveError: r.metadataSaveError,
|
|
4943
5078
|
truncated: r.truncated,
|
|
4944
5079
|
transcriptPath: r.transcriptPath,
|
|
@@ -5074,7 +5209,7 @@ export async function runSubagent(
|
|
|
5074
5209
|
async function waitForStartupControl(
|
|
5075
5210
|
controlPath: string,
|
|
5076
5211
|
token: string,
|
|
5077
|
-
action: "ack" | "proceed",
|
|
5212
|
+
action: "ack" | "confirm" | "proceed",
|
|
5078
5213
|
timeoutMs = 30_000,
|
|
5079
5214
|
): Promise<void> {
|
|
5080
5215
|
const deadline = Date.now() + timeoutMs;
|
|
@@ -5088,7 +5223,7 @@ async function waitForStartupControl(
|
|
|
5088
5223
|
}
|
|
5089
5224
|
if (payload.token !== token) throw new Error("Runner startup control token does not match.");
|
|
5090
5225
|
if (payload.action === action) return;
|
|
5091
|
-
if (payload.action !== "ack" && payload.action !== "proceed") throw new Error("Runner startup control action is invalid.");
|
|
5226
|
+
if (payload.action !== "ack" && payload.action !== "confirm" && payload.action !== "proceed") throw new Error("Runner startup control action is invalid.");
|
|
5092
5227
|
}
|
|
5093
5228
|
await new Promise((resolve) => setTimeout(resolve, 20));
|
|
5094
5229
|
}
|
|
@@ -5125,6 +5260,8 @@ export async function runConfiguredSubagent(config: SubagentRunConfig, options?:
|
|
|
5125
5260
|
writeAtomicJson(startupPath, { state: "ready", token: lease.owner.token, pid: process.pid, owner: lease.owner });
|
|
5126
5261
|
await waitForStartupControl(startupAckPath, lease.owner.token, "ack");
|
|
5127
5262
|
writeAtomicJson(startupPath, { state: "acknowledged", token: lease.owner.token, pid: process.pid });
|
|
5263
|
+
await waitForStartupControl(startupAckPath, lease.owner.token, "confirm");
|
|
5264
|
+
writeAtomicJson(startupPath, { state: "confirmed", token: lease.owner.token, pid: process.pid });
|
|
5128
5265
|
await waitForStartupControl(startupProceedPath, lease.owner.token, "proceed");
|
|
5129
5266
|
startupCommitted = true;
|
|
5130
5267
|
for (const controlPath of [startupAckPath, startupProceedPath]) {
|