pi-subagents 0.66.0 → 0.68.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 +138 -0
- package/README.md +5 -4
- package/agents/evidence-auditor.md +34 -0
- package/agents/reviewer.md +3 -2
- package/docs/agents.md +43 -15
- package/docs/configuration.md +67 -23
- package/docs/extension-api.md +38 -19
- package/docs/missions.md +10 -2
- package/docs/models.md +11 -79
- package/docs/observability.md +22 -12
- package/docs/standalone-background.md +59 -0
- package/docs/tool-reference.md +33 -20
- package/docs/watchdog.md +39 -10
- package/docs/workflows.md +37 -13
- package/index.ts +5 -2
- package/inspector-runner.mjs +2 -2
- package/package.json +4 -2
- package/prompts/parallel-review.md +1 -1
- package/runner-peer-loader.mjs +24 -0
- package/runner-peer-preload.mjs +32 -0
- package/skills/pi-subagents/SKILL.md +32 -21
- package/skills/pi-subagents/references/constraints-and-recipes.md +3 -2
- package/skills/pi-subagents/references/execution-controls.md +11 -9
- 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 +17 -13
- package/skills/pi-subagents/references/review-and-validation.md +3 -3
- package/src/agents/advertised-agent-prompt.ts +34 -3
- package/src/agents/agent-management.ts +57 -58
- package/src/agents/agent-serializer.ts +4 -3
- package/src/agents/agents.ts +190 -71
- package/src/agents/builtin-names.ts +1 -0
- package/src/agents/chain-serializer.ts +5 -0
- package/src/agents/runtime-agent-registry.ts +7 -6
- package/src/api/delegation.ts +4 -0
- package/src/api/preflight.ts +94 -59
- package/src/api/required-child-extensions.ts +6 -0
- package/src/api/shared-types.ts +2 -0
- package/src/extension/config.ts +10 -37
- package/src/extension/fanout-child.ts +66 -4
- package/src/extension/herdr-pi-bridge.ts +160 -0
- package/src/extension/index.ts +62 -39
- package/src/extension/public-execution.ts +7 -5
- package/src/extension/rpc.ts +4 -0
- package/src/extension/schemas.ts +81 -81
- package/src/extension/tool-description.ts +30 -82
- package/src/inspectors/actions.ts +148 -0
- package/src/inspectors/ghostty/actions.ts +74 -0
- package/src/inspectors/ghostty/plugin.ts +17 -0
- package/src/inspectors/herdr/actions.ts +99 -179
- package/src/inspectors/herdr/plugin.ts +20 -0
- package/src/inspectors/herdr/project-panes.ts +1 -1
- package/src/inspectors/{herdr/inspector-runner.ts → inspector-runner.ts} +12 -12
- package/src/inspectors/plugins.ts +8 -0
- package/src/inspectors/{herdr/session-roots-codec.ts → session-roots-codec.ts} +3 -14
- package/src/inspectors/types.ts +51 -0
- package/src/intercom/intercom-bridge.ts +50 -8
- package/src/intercom/native-supervisor-channel.ts +44 -31
- package/src/policy/authority.ts +4 -0
- package/src/profiles/profiles.ts +12 -6
- package/src/runs/background/active-async-capacity.ts +4 -0
- package/src/runs/background/active-run-index.ts +17 -1
- package/src/runs/background/async-execution.ts +348 -176
- package/src/runs/background/async-job-tracker.ts +8 -6
- package/src/runs/background/async-resume.ts +17 -12
- package/src/runs/background/async-status.ts +15 -4
- package/src/runs/background/auto-drain.ts +23 -10
- package/src/runs/background/binary-bootstrap.ts +38 -0
- package/src/runs/background/chain-append.ts +1 -1
- package/src/runs/background/chain-root-attachment.ts +14 -33
- package/src/runs/background/fleet-view.ts +30 -2
- package/src/runs/background/notify.ts +105 -7
- package/src/runs/background/owned-process-tree.ts +29 -2
- 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 +81 -33
- package/src/runs/background/run-status.ts +3 -0
- package/src/runs/background/runner-aliases.ts +12 -33
- package/src/runs/background/runner-child-launch.ts +6 -1
- package/src/runs/background/runner-child-sessions.ts +5 -4
- package/src/runs/background/runner-http-dispatcher.ts +119 -0
- package/src/runs/background/scheduled-runs.ts +51 -18
- package/src/runs/background/stale-run-reconciler.ts +35 -11
- package/src/runs/background/steering.ts +20 -2
- package/src/runs/background/subagent-runner.ts +441 -304
- package/src/runs/background/subagent-wait.ts +176 -28
- 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 +5 -3
- package/src/runs/foreground/async-steering-action.ts +18 -7
- package/src/runs/foreground/async-stop-action.ts +93 -3
- package/src/runs/foreground/execution.ts +134 -248
- package/src/runs/foreground/foreground-history.ts +2 -1
- package/src/runs/foreground/prompt-audit.ts +3 -1
- package/src/runs/foreground/subagent-executor.ts +374 -178
- package/src/runs/foreground/workflow-detach-reconcile.ts +2 -0
- package/src/runs/foreground/workflow-foreground-steering.ts +2 -1
- package/src/runs/shared/acceptance.ts +38 -11
- package/src/runs/shared/async-status-projection.ts +127 -33
- package/src/runs/shared/capability-ceiling.ts +2 -0
- package/src/runs/shared/child-hooks.ts +25 -10
- package/src/runs/shared/child-launch-plan.ts +15 -3
- package/src/runs/shared/child-launch.ts +28 -5
- package/src/runs/shared/child-lifecycle.ts +6 -3
- package/src/runs/shared/child-runtime-config.ts +8 -1
- package/src/runs/shared/child-session.ts +127 -52
- package/src/runs/shared/child-tool-plan.ts +142 -11
- package/src/runs/shared/completion-guard.ts +5 -3
- package/src/runs/shared/dynamic-fanout.ts +2 -2
- package/src/runs/shared/effective-system-prompt.ts +33 -0
- package/src/runs/shared/external-cli-contract.ts +11 -1
- package/src/runs/shared/external-cli-preflight.ts +6 -2
- package/src/runs/shared/external-cli-runner.ts +9 -7
- 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/llm-intent-arbiter.ts +12 -3
- package/src/runs/shared/model-resolution-diagnostic.ts +76 -0
- package/src/runs/shared/{model-fallback.ts → model-resolution.ts} +22 -235
- package/src/runs/shared/model-scope.ts +1 -1
- package/src/runs/shared/nested-events.ts +11 -2
- package/src/runs/shared/orca-progress-tabs.ts +1 -1
- package/src/runs/shared/parallel-utils.ts +7 -2
- package/src/runs/shared/pi-spawn.ts +10 -0
- package/src/runs/shared/subagent-prompt-runtime.ts +12 -4
- package/src/runs/shared/task-intent.ts +46 -13
- package/src/runs/shared/workflow-async-child-guidance.ts +18 -0
- package/src/runs/shared/worktree-setup-command.ts +27 -4
- package/src/runs/shared/worktree.ts +45 -15
- package/src/shared/child-cache-retention.ts +43 -0
- package/src/shared/fork-context.ts +15 -72
- package/src/shared/launch-contract.ts +68 -8
- package/src/shared/opencode-session-headers.ts +30 -0
- 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 +74 -30
- package/src/slash/delegation-adapters.ts +3 -1
- package/src/slash/delegation-request.ts +14 -0
- package/src/slash/slash-commands.ts +2 -7
- package/src/slash/subagents-admin.ts +24 -13
- package/src/tui/fleet-status.ts +164 -19
- package/src/tui/fleet.ts +16 -14
- package/src/tui/render.ts +168 -37
- package/src/watchdog/child-status.ts +28 -28
- package/src/watchdog/lsp-diagnostics.ts +1 -1
- package/src/watchdog/model-selection.ts +21 -1
- package/src/watchdog/permission-arbiter.ts +3 -1
- package/src/watchdog/register-child.ts +10 -2
- package/src/watchdog/register-main.ts +39 -35
- package/src/watchdog/render.ts +1 -1
- package/src/watchdog/review.ts +123 -74
- package/src/watchdog/rules.ts +1 -1
- package/src/watchdog/runtime.ts +100 -27
- package/src/watchdog/scope.ts +1 -1
- package/src/watchdog/settings.ts +3 -0
- package/src/watchdog/tool-actions.ts +13 -12
- package/src/watchdog/turn-delta.ts +23 -0
- package/src/watchdog/types.ts +5 -3
- package/src/watchdog/warning-format.ts +1 -1
- package/src/workflows/scripted-workflow.ts +279 -10
- package/src/workflows/workflow-checklist.ts +2 -2
- package/src/workflows/workflow-receipt.ts +21 -3
- package/src/workflows/workflow-resources.ts +13 -2
- package/runner-server-preload.mjs +0 -13
- 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
- /package/src/inspectors/{herdr/shell-command.ts → shell-command.ts} +0 -0
|
@@ -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,
|
|
@@ -96,10 +84,8 @@ import { applyThinkingSuffix, projectLaunchResolvedChildExtensions, resolvePiLau
|
|
|
96
84
|
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
|
-
import type { ChildSessionFactory } 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";
|
|
87
|
+
import type { ChildSessionFactory, DefaultChildSessionFactoryOptions } from "../shared/child-session.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
|
-
import { createSteeringStatus, recordSteeringRequest, steeringStatus, terminalSteeringNoticeState, updateSteeringTarget } from "./steering.ts";
|
|
118
|
-
import { PROMPT_REDACTED, detectSubagentError, extractTextFromContent, extractToolArgsPreview, formatEmptyTerminalAssistantResponseError, getFinalOutput, hasEmptyTerminalAssistantResponse, readStatus } from "../../shared/utils.ts";
|
|
103
|
+
import { createSteeringStatus, recordSteeringRequest, steeringStatus, terminalSteeringNoticeState, unconsumedSteerReason, updateSteeringTarget } from "./steering.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";
|
|
@@ -147,7 +133,7 @@ import {
|
|
|
147
133
|
} from "../shared/worktree.ts";
|
|
148
134
|
import { findModelInfo, resolveEffectiveThinking, splitKnownThinkingSuffix } from "../../shared/model-info.ts";
|
|
149
135
|
import { assertThinkingWithinCeiling } from "../../shared/thinking-ceiling.ts";
|
|
150
|
-
import {
|
|
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
139
|
import { acceptanceFailureMessage, aggregateAcceptanceReport, buildSkippedAcceptanceLedger, evaluateAcceptance, formatAcceptancePrompt, resolveAcceptanceReportMode, resolveEffectiveAcceptance, stripAcceptanceReport } from "../shared/acceptance.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";
|
|
@@ -184,7 +172,7 @@ const INTERCOM_DETACH_RECEIPT = "Detached for intercom coordination before task
|
|
|
184
172
|
// child host.
|
|
185
173
|
process.env[SUBAGENT_CHILD_ENV] = "1";
|
|
186
174
|
|
|
187
|
-
interface SubagentRunConfig {
|
|
175
|
+
export interface SubagentRunConfig {
|
|
188
176
|
id: string;
|
|
189
177
|
steps: RunnerStep[];
|
|
190
178
|
resultPath: string;
|
|
@@ -224,6 +212,8 @@ 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;
|
|
@@ -232,6 +222,8 @@ interface SubagentRunConfig {
|
|
|
232
222
|
globalConcurrencyLimit?: number;
|
|
233
223
|
capabilityCeiling?: ResolvedSubagentCapabilityCeiling;
|
|
234
224
|
runFanoutBudget?: RunFanoutBudgetDescriptor;
|
|
225
|
+
/** Builtin tool names the host runtime provides; used to intersect agent-declared tools. */
|
|
226
|
+
hostAvailableBuiltins?: readonly string[];
|
|
235
227
|
launchContractDigest?: string;
|
|
236
228
|
launchResolvedExtensions?: LaunchResolvedChildExtensions;
|
|
237
229
|
runtimeAcknowledgedExtensions?: RuntimeAcknowledgedChildExtensions;
|
|
@@ -270,14 +262,15 @@ interface StepResult {
|
|
|
270
262
|
sessionFile?: string;
|
|
271
263
|
intercomTarget?: string;
|
|
272
264
|
model?: string;
|
|
265
|
+
nativeMachine?: import("../../shared/types.ts").SingleResult["nativeMachine"];
|
|
273
266
|
thinking?: string;
|
|
274
|
-
|
|
275
|
-
modelAttempts?: ModelAttempt[];
|
|
267
|
+
requestedModel?: string;
|
|
276
268
|
/** True when the dispatch failed because the input exceeded the model's context window. */
|
|
277
269
|
contextOverflow?: boolean;
|
|
278
270
|
totalCost?: CostSummary;
|
|
279
271
|
artifactPaths?: ArtifactPaths;
|
|
280
272
|
outputSaveError?: string;
|
|
273
|
+
artifactOutputSaveFailed?: true;
|
|
281
274
|
metadataSaveError?: string;
|
|
282
275
|
truncated?: boolean;
|
|
283
276
|
transcriptPath?: string;
|
|
@@ -420,50 +413,22 @@ function emptyUsage(): Usage {
|
|
|
420
413
|
return { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0, turns: 0 };
|
|
421
414
|
}
|
|
422
415
|
|
|
423
|
-
function
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
let output = 0;
|
|
427
|
-
for (const attempt of attempts) {
|
|
428
|
-
input += attempt.usage?.input ?? 0;
|
|
429
|
-
output += attempt.usage?.output ?? 0;
|
|
430
|
-
}
|
|
416
|
+
function tokenUsageFromUsage(usage: Usage | undefined): TokenUsage | null {
|
|
417
|
+
const input = usage?.input ?? 0;
|
|
418
|
+
const output = usage?.output ?? 0;
|
|
431
419
|
const total = input + output;
|
|
432
420
|
return total > 0 ? { input, output, total } : null;
|
|
433
421
|
}
|
|
434
422
|
|
|
435
|
-
function
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
let costUsd = 0;
|
|
440
|
-
for (const attempt of attempts) {
|
|
441
|
-
inputTokens += attempt.usage?.input ?? 0;
|
|
442
|
-
outputTokens += attempt.usage?.output ?? 0;
|
|
443
|
-
costUsd += attempt.usage?.cost ?? 0;
|
|
444
|
-
}
|
|
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;
|
|
445
427
|
return inputTokens > 0 || outputTokens > 0 || costUsd > 0
|
|
446
428
|
? { inputTokens, outputTokens, costUsd }
|
|
447
429
|
: undefined;
|
|
448
430
|
}
|
|
449
431
|
|
|
450
|
-
function usageFromAttempts(attempts: ModelAttempt[] | undefined): Usage | undefined {
|
|
451
|
-
if (!attempts || attempts.length === 0) return undefined;
|
|
452
|
-
const usage = emptyUsage();
|
|
453
|
-
for (const attempt of attempts) {
|
|
454
|
-
if (!attempt.usage) continue;
|
|
455
|
-
usage.input += attempt.usage.input;
|
|
456
|
-
usage.output += attempt.usage.output;
|
|
457
|
-
usage.cacheRead += attempt.usage.cacheRead;
|
|
458
|
-
usage.cacheWrite += attempt.usage.cacheWrite;
|
|
459
|
-
usage.cost += attempt.usage.cost;
|
|
460
|
-
usage.turns += attempt.usage.turns;
|
|
461
|
-
}
|
|
462
|
-
return usage.input !== 0 || usage.output !== 0 || usage.cacheRead !== 0 || usage.cacheWrite !== 0 || usage.cost !== 0 || usage.turns !== 0
|
|
463
|
-
? usage
|
|
464
|
-
: undefined;
|
|
465
|
-
}
|
|
466
|
-
|
|
467
432
|
function appendRecentStepOutput(step: RunnerStatusStep, lines: string[]): void {
|
|
468
433
|
const nonEmpty = lines.filter((line) => line.trim());
|
|
469
434
|
if (nonEmpty.length === 0) return;
|
|
@@ -657,6 +622,38 @@ function writeRunLog(
|
|
|
657
622
|
write(logPath, lines.join("\n"));
|
|
658
623
|
}
|
|
659
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
|
+
|
|
660
657
|
/** Context for running a single step */
|
|
661
658
|
interface SingleStepContext {
|
|
662
659
|
previousOutput: string;
|
|
@@ -682,6 +679,8 @@ interface SingleStepContext {
|
|
|
682
679
|
registerStop?: (stop: (() => void) | undefined) => void;
|
|
683
680
|
/** Receives the live child's steer handler while its session runs. */
|
|
684
681
|
registerSteer?: (steer: StepSteerHandler | undefined) => void;
|
|
682
|
+
/** Reports a live child's later steer consumption or unconsumed settlement. */
|
|
683
|
+
onSteerOutcome?: (request: SteerRequest, delivery: SteerDelivery) => void;
|
|
685
684
|
timeoutSignal?: AbortSignal;
|
|
686
685
|
stopSignal?: AbortSignal;
|
|
687
686
|
timeoutMessage?: string;
|
|
@@ -695,9 +694,12 @@ interface SingleStepContext {
|
|
|
695
694
|
nestedRoute?: NestedRouteInfo;
|
|
696
695
|
capabilityCeiling?: ResolvedSubagentCapabilityCeiling;
|
|
697
696
|
runFanoutBudget?: RunFanoutBudgetDescriptor;
|
|
697
|
+
hostAvailableBuiltins?: readonly string[];
|
|
698
698
|
onAttemptStart?: (attempt: { model?: string; thinking?: string; contextLimit?: number }) => void;
|
|
699
699
|
onChildEvent?: (event: ChildEvent) => void;
|
|
700
700
|
onExternalProcess?: (process: ExternalProcessStatus) => void;
|
|
701
|
+
prepareExternalActivity?: (cwd: string, signal: AbortSignal) => Promise<void>;
|
|
702
|
+
onExternalStreamActivity?: () => void;
|
|
701
703
|
onExternalJob?: (status: ExternalJobStatus) => void;
|
|
702
704
|
skipAcceptance?: () => boolean;
|
|
703
705
|
/** Authoritative owner decision after event delivery; undefined includes incomplete run-wide usage. */
|
|
@@ -709,7 +711,26 @@ interface SingleStepContext {
|
|
|
709
711
|
orcaProgressTab?: OrcaProgressTab;
|
|
710
712
|
}
|
|
711
713
|
|
|
712
|
-
/**
|
|
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
|
+
|
|
713
734
|
export async function runSingleStepInner(
|
|
714
735
|
step: SubagentStep,
|
|
715
736
|
ctx: SingleStepContext,
|
|
@@ -768,8 +789,7 @@ export async function runSingleStepInner(
|
|
|
768
789
|
sessionFile: imported.sessionFile,
|
|
769
790
|
intercomTarget: imported.intercomTarget,
|
|
770
791
|
model: imported.model,
|
|
771
|
-
|
|
772
|
-
modelAttempts: imported.modelAttempts,
|
|
792
|
+
requestedModel: imported.requestedModel,
|
|
773
793
|
contextOverflow: imported.contextOverflow,
|
|
774
794
|
totalCost: imported.totalCost,
|
|
775
795
|
usage: imported.usage,
|
|
@@ -803,14 +823,15 @@ export async function runSingleStepInner(
|
|
|
803
823
|
subagentOnlyExtensions: step.subagentOnlyExtensions,
|
|
804
824
|
fast: step.fast,
|
|
805
825
|
model: step.model,
|
|
806
|
-
modelCandidates: step.modelCandidates,
|
|
807
826
|
mcpDirectTools: step.mcpDirectTools,
|
|
808
827
|
cwd: step.cwd ?? ctx.cwd,
|
|
809
828
|
requireReadTool: Boolean(step.skills?.length),
|
|
810
829
|
structuredOutput: Boolean(effectiveStructuredOutput),
|
|
811
830
|
capabilityCeiling: step.capabilityCeiling ?? ctx.capabilityCeiling,
|
|
812
831
|
inheritedCapabilityCeiling: ctx.inheritedChildRuntime?.capabilityCeiling,
|
|
832
|
+
requiredExtensions: step.requiredExtensions ?? ctx.inheritedChildRuntime?.requiredExtensions,
|
|
813
833
|
permissionRules: step.permissionRules,
|
|
834
|
+
hostAvailableBuiltins: ctx.hostAvailableBuiltins,
|
|
814
835
|
}));
|
|
815
836
|
const contractTools = resolvedTaskToolPlan.explicitToolAllowlist ? resolvedTaskToolPlan.effectiveToolAllowlist : undefined;
|
|
816
837
|
const contractError = validateImplementationToolContract({
|
|
@@ -868,7 +889,53 @@ export async function runSingleStepInner(
|
|
|
868
889
|
transcriptWriter?.writeInitialUserMessage(`${PROMPT_REDACTED}; live Prompt Audit only.`);
|
|
869
890
|
|
|
870
891
|
if (step.runner?.type === "external-cli") {
|
|
871
|
-
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
|
+
}
|
|
872
939
|
const adapterLaunch = step.runner.adapter === "codex-exec" || step.runner.adapter === "codex-exec-writer"
|
|
873
940
|
? resolveCodexExecLaunch({ adapter: step.runner.adapter, command: step.runner.command, asyncDir: path.dirname(ctx.outputFile), stepIndex: ctx.flatIndex })
|
|
874
941
|
: step.runner.adapter === "claude-code" || step.runner.adapter === "claude-code-writer"
|
|
@@ -876,9 +943,13 @@ export async function runSingleStepInner(
|
|
|
876
943
|
: step.runner.adapter === "cursor-agent" || step.runner.adapter === "cursor-agent-writer"
|
|
877
944
|
? resolveCursorAgentLaunch({ adapter: step.runner.adapter, command: step.runner.command, cwd: externalCwd, asyncDir: path.dirname(ctx.outputFile), stepIndex: ctx.flatIndex })
|
|
878
945
|
: undefined;
|
|
879
|
-
const runner = resolveExternalCliRunnerStatus({ ...step.runner, ...(adapterLaunch ? { args: adapterLaunch.args } : {}) });
|
|
946
|
+
const runner = resolveExternalCliRunnerStatus({ ...step.runner, ...(adapterLaunch ? { args: adapterLaunch.args } : {}), ...(step.machine ? { machine: step.machine } : {}) });
|
|
880
947
|
const outputSnapshot = captureSingleOutputSnapshot(step.outputPath);
|
|
881
|
-
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({
|
|
882
953
|
command: adapterLaunch?.command ?? runner.command,
|
|
883
954
|
args: adapterLaunch?.args ?? runner.args,
|
|
884
955
|
cwd: externalCwd,
|
|
@@ -896,9 +967,13 @@ export async function runSingleStepInner(
|
|
|
896
967
|
timeoutMessage: ctx.timeoutMessage,
|
|
897
968
|
stopMessage: ctx.stopMessage,
|
|
898
969
|
onProcess: ctx.onExternalProcess,
|
|
899
|
-
onStdout:
|
|
900
|
-
onStderr:
|
|
901
|
-
})
|
|
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;
|
|
902
977
|
try { fs.writeFileSync(ctx.outputFile, external.output, "utf-8"); } catch { /* Observability output is best-effort. */ }
|
|
903
978
|
const resolvedOutput = step.outputPath && external.exitCode === 0
|
|
904
979
|
? resolveSingleOutput(step.outputPath, external.output, outputSnapshot, step.outputClaimPath)
|
|
@@ -922,7 +997,7 @@ export async function runSingleStepInner(
|
|
|
922
997
|
artifactPaths,
|
|
923
998
|
artifactConfig: ctx.artifactConfig,
|
|
924
999
|
output: formatOutputArtifactContent(omitUndefinedProperties({ output: resolvedOutput.fullOutput, error: external.error, metadataPath: ctx.artifactConfig?.includeMetadata === false ? undefined : artifactPaths.metadataPath })),
|
|
925
|
-
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() },
|
|
926
1001
|
})
|
|
927
1002
|
: {};
|
|
928
1003
|
return omitUndefinedProperties({
|
|
@@ -938,9 +1013,10 @@ export async function runSingleStepInner(
|
|
|
938
1013
|
processSignal: external.processSignal,
|
|
939
1014
|
artifactPaths,
|
|
940
1015
|
outputSaveError: [resolvedOutput.saveError, artifactErrors.outputSaveError].filter(Boolean).join("\n") || undefined,
|
|
1016
|
+
artifactOutputSaveFailed: artifactErrors.outputSaveError ? true : undefined,
|
|
941
1017
|
metadataSaveError: artifactErrors.metadataSaveError,
|
|
942
1018
|
runner,
|
|
943
|
-
externalProcess:
|
|
1019
|
+
externalProcess: externalProcess,
|
|
944
1020
|
});
|
|
945
1021
|
}
|
|
946
1022
|
|
|
@@ -1007,6 +1083,7 @@ export async function runSingleStepInner(
|
|
|
1007
1083
|
stopped: external.stopped,
|
|
1008
1084
|
artifactPaths,
|
|
1009
1085
|
outputSaveError: [resolvedOutput.saveError, artifactErrors.outputSaveError].filter(Boolean).join("\n") || undefined,
|
|
1086
|
+
artifactOutputSaveFailed: artifactErrors.outputSaveError ? true : undefined,
|
|
1010
1087
|
metadataSaveError: artifactErrors.metadataSaveError,
|
|
1011
1088
|
runner,
|
|
1012
1089
|
externalJob: external.externalJob,
|
|
@@ -1020,16 +1097,9 @@ export async function runSingleStepInner(
|
|
|
1020
1097
|
alignForkedSessionCwd(step.sessionFile, effectiveCwd);
|
|
1021
1098
|
}
|
|
1022
1099
|
|
|
1023
|
-
const
|
|
1024
|
-
? step.modelCandidates.length > 0 ? step.modelCandidates : [undefined]
|
|
1025
|
-
: step.model
|
|
1026
|
-
? [step.model]
|
|
1027
|
-
: [undefined];
|
|
1028
|
-
const attemptedModels: string[] = [];
|
|
1100
|
+
const candidate = step.model;
|
|
1029
1101
|
let capabilityAudit: import("../shared/capability-ceiling.ts").SubagentCapabilityAudit | undefined;
|
|
1030
1102
|
let launchResolvedExtensions = step.launchResolvedExtensions;
|
|
1031
|
-
const modelAttempts: ModelAttempt[] = [];
|
|
1032
|
-
const attemptNotes: string[] = [];
|
|
1033
1103
|
let finalRequiredOutputMissing: boolean | undefined;
|
|
1034
1104
|
const eventsPath = path.join(path.dirname(ctx.outputFile), "events.jsonl");
|
|
1035
1105
|
let finalResult: RunChildSessionResult | undefined;
|
|
@@ -1040,48 +1110,21 @@ export async function runSingleStepInner(
|
|
|
1040
1110
|
let toolBudget = step.toolBudget ? initialToolBudgetState(step.toolBudget) : undefined;
|
|
1041
1111
|
let toolBudgetBlocked = false;
|
|
1042
1112
|
let actualLaunchContractDigest = step.launchContractDigest;
|
|
1043
|
-
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);
|
|
1044
1114
|
let finalMutationEvidence = collectTrackedMutationEvidence(mutationSnapshot, step.cwd ?? ctx.cwd);
|
|
1045
1115
|
|
|
1046
|
-
let modelIndex = 0;
|
|
1047
1116
|
let contextOverflow = false;
|
|
1048
1117
|
let launchWarningsEmitted = false;
|
|
1049
|
-
|
|
1050
|
-
let
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
const
|
|
1055
|
-
if (exhausted === true) return "exhausted" as const;
|
|
1056
|
-
if (!ctx.usageBudget) return "unconfigured" as const;
|
|
1057
|
-
if (ctx.usageBudget.costUsd || !ctx.onChildEvent || exhausted !== false) return "unknown" as const;
|
|
1058
|
-
return "available" as const;
|
|
1059
|
-
};
|
|
1060
|
-
const lifecycleAllowsContinuation = () => !ctx.timeoutSignal?.aborted && !ctx.stopSignal?.aborted
|
|
1061
|
-
&& !ctx.skipAcceptance?.() && (ctx.deadlineAt === undefined || Date.now() < ctx.deadlineAt);
|
|
1062
|
-
const canContinue = () => lifecycleAllowsContinuation() && ["available", "unconfigured"].includes(continuationBudget());
|
|
1063
|
-
const failContinuationLaunch = (candidate: string | undefined, error: unknown) => {
|
|
1064
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
1065
|
-
modelAttempts.push({ model: candidate ?? "default", success: false, exitCode: 1, error: message });
|
|
1066
|
-
if (candidate) attemptedModels.push(candidate);
|
|
1067
|
-
if (finalResult) finalResult = { ...finalResult, exitCode: 1, error: message };
|
|
1068
|
-
};
|
|
1069
|
-
let nextAttemptTask = task;
|
|
1070
|
-
modelAttemptsLoop: while (modelIndex < candidates.length) {
|
|
1071
|
-
if (ctx.timeoutSignal?.aborted || ctx.stopSignal?.aborted || ctx.skipAcceptance?.()) break;
|
|
1072
|
-
if (readonlyContinuation && !canContinue()) break;
|
|
1073
|
-
const recoveringAbort = recoveryState === "abort-recovery";
|
|
1074
|
-
const attemptTask = nextAttemptTask;
|
|
1075
|
-
const candidate = candidates[modelIndex];
|
|
1076
|
-
const expectedModelForVerification = candidate && !(step.skipPrimaryModelVerification && modelIndex === 0) ? candidate : undefined;
|
|
1118
|
+
const aggregateUsage = emptyUsage();
|
|
1119
|
+
let launched = false;
|
|
1120
|
+
let recoveryTask = task;
|
|
1121
|
+
singleLaunch: for (let attemptIndex = 0; attemptIndex < 2; attemptIndex++) {
|
|
1122
|
+
if (ctx.timeoutSignal?.aborted || ctx.stopSignal?.aborted || ctx.skipAcceptance?.()) break singleLaunch;
|
|
1123
|
+
const expectedModelForVerification = candidate && !step.skipPrimaryModelVerification ? candidate : undefined;
|
|
1077
1124
|
try {
|
|
1078
1125
|
assertThinkingWithinCeiling({ model: candidate, configThinking: step.thinking, ceiling: step.thinkingCeiling, agent: step.agent, runId: ctx.id });
|
|
1079
1126
|
} catch (error) {
|
|
1080
1127
|
const message = error instanceof Error ? error.message : String(error);
|
|
1081
|
-
if (readonlyContinuation) {
|
|
1082
|
-
failContinuationLaunch(candidate, error);
|
|
1083
|
-
break modelAttemptsLoop;
|
|
1084
|
-
}
|
|
1085
1128
|
return omitUndefinedProperties({ agent: step.agent, output: message, error: message, exitCode: 1, context: step.context, thinkingCeiling: step.thinkingCeiling });
|
|
1086
1129
|
}
|
|
1087
1130
|
ctx.onAttemptStart?.(omitUndefinedProperties({
|
|
@@ -1118,11 +1161,7 @@ export async function runSingleStepInner(
|
|
|
1118
1161
|
childWatchdog,
|
|
1119
1162
|
watchdogStatus: (event) => watchdogSink?.(event),
|
|
1120
1163
|
});
|
|
1121
|
-
} catch (error) {
|
|
1122
|
-
if (!readonlyContinuation) throw error;
|
|
1123
|
-
failContinuationLaunch(candidate, error);
|
|
1124
|
-
break modelAttemptsLoop;
|
|
1125
|
-
}
|
|
1164
|
+
} catch (error) { throw error; }
|
|
1126
1165
|
if (effectiveStructuredOutput && launch.config.structuredOutput) {
|
|
1127
1166
|
// The runner reads the value back from the runtime's files after the run.
|
|
1128
1167
|
launch.config.structuredOutput.capture = createStructuredOutputFileCapture(effectiveStructuredOutput);
|
|
@@ -1141,39 +1180,35 @@ export async function runSingleStepInner(
|
|
|
1141
1180
|
subagentOnlyExtensions: step.subagentOnlyExtensions,
|
|
1142
1181
|
fast: step.fast,
|
|
1143
1182
|
model: step.model,
|
|
1144
|
-
modelCandidates: step.modelCandidates,
|
|
1145
1183
|
mcpDirectTools: step.mcpDirectTools,
|
|
1146
1184
|
cwd: step.cwd ?? ctx.cwd,
|
|
1147
1185
|
requireReadTool: Boolean(step.skills?.length),
|
|
1148
1186
|
structuredOutput: Boolean(effectiveStructuredOutput),
|
|
1149
1187
|
capabilityCeiling: step.capabilityCeiling ?? ctx.capabilityCeiling,
|
|
1150
1188
|
inheritedCapabilityCeiling: ctx.inheritedChildRuntime?.capabilityCeiling,
|
|
1189
|
+
requiredExtensions: step.requiredExtensions ?? ctx.inheritedChildRuntime?.requiredExtensions,
|
|
1151
1190
|
permissionRules: step.permissionRules,
|
|
1191
|
+
hostAvailableBuiltins: ctx.hostAvailableBuiltins,
|
|
1152
1192
|
}));
|
|
1153
1193
|
launchResolvedExtensions = projectLaunchResolvedChildExtensions(toolPlan);
|
|
1154
|
-
actualLaunchContractDigest =
|
|
1194
|
+
actualLaunchContractDigest = resolveLaunchBinding({
|
|
1155
1195
|
definitionDigest: step.definitionDigest,
|
|
1156
|
-
task: step.launchBindingTask ?? task,
|
|
1157
|
-
...(candidate ? { model: candidate } : {}),
|
|
1158
|
-
modelCandidates: candidates as string[],
|
|
1159
|
-
...(step.fast !== undefined ? { fast: step.fast } : {}),
|
|
1160
|
-
...(resolveEffectiveThinking(candidate, step.thinking) ? { thinking: resolveEffectiveThinking(candidate, step.thinking) } : {}),
|
|
1161
|
-
...(step.thinkingCeiling ? { thinkingCeiling: step.thinkingCeiling } : {}),
|
|
1162
|
-
systemPrompt: step.systemPrompt ?? "",
|
|
1163
1196
|
systemPromptMode: step.systemPromptMode,
|
|
1164
1197
|
inheritProjectContext: step.inheritProjectContext,
|
|
1165
1198
|
inheritGlobalContext: step.inheritGlobalContext,
|
|
1166
1199
|
inheritSkills: step.inheritSkills,
|
|
1200
|
+
task: step.launchBindingTask ?? task,
|
|
1201
|
+
model: candidate,
|
|
1202
|
+
fast: step.fast,
|
|
1203
|
+
thinking: resolveEffectiveThinking(candidate, step.thinking),
|
|
1204
|
+
systemPrompt: step.systemPrompt ?? "",
|
|
1167
1205
|
skills: step.skills,
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
...(step.structuredOutputSchema ? { structuredOutputSchema: step.structuredOutputSchema } : {}),
|
|
1175
|
-
...(extensionBindings ? { extensionBindings } : {}),
|
|
1176
|
-
}));
|
|
1206
|
+
toolPlan,
|
|
1207
|
+
outputPath: step.outputPath,
|
|
1208
|
+
outputMode: step.outputMode,
|
|
1209
|
+
structuredOutputSchema: step.structuredOutputSchema,
|
|
1210
|
+
extensionBindings,
|
|
1211
|
+
}).launchContractDigest;
|
|
1177
1212
|
}
|
|
1178
1213
|
capabilityAudit = attemptCapabilityAudit;
|
|
1179
1214
|
// Each attempt rewrites the step output log; synchronous appends keep a
|
|
@@ -1182,10 +1217,7 @@ export async function runSingleStepInner(
|
|
|
1182
1217
|
const run = await runChildSession(omitUndefinedProperties({
|
|
1183
1218
|
factory: ctx.childSessions,
|
|
1184
1219
|
launch,
|
|
1185
|
-
|
|
1186
|
-
readonlyContinuation,
|
|
1187
|
-
canContinue,
|
|
1188
|
-
prompt: `Task: ${attemptTask}`,
|
|
1220
|
+
prompt: `Task: ${recoveryTask}`,
|
|
1189
1221
|
childWatchdog,
|
|
1190
1222
|
childEventContext: { runId: ctx.id, stepIndex: ctx.flatIndex, agent: step.agent },
|
|
1191
1223
|
appendChildEvent: (event) => appendDiagnosticJsonl(eventsPath, JSON.stringify(event), typeof event.type === "string" ? event.type : undefined),
|
|
@@ -1201,6 +1233,7 @@ export async function runSingleStepInner(
|
|
|
1201
1233
|
registerTimeout: ctx.registerTimeout,
|
|
1202
1234
|
registerStop: ctx.registerStop,
|
|
1203
1235
|
registerSteer: ctx.registerSteer,
|
|
1236
|
+
onSteerOutcome: ctx.onSteerOutcome,
|
|
1204
1237
|
registerWatchdogStatus: (sink) => { watchdogSink = sink; },
|
|
1205
1238
|
timeoutMessage: ctx.timeoutMessage,
|
|
1206
1239
|
stopMessage: ctx.stopMessage,
|
|
@@ -1213,6 +1246,16 @@ export async function runSingleStepInner(
|
|
|
1213
1246
|
modelResponseAliases: step.modelResponseAliases,
|
|
1214
1247
|
mutationTools: step.mutationTools,
|
|
1215
1248
|
}));
|
|
1249
|
+
launched = true;
|
|
1250
|
+
aggregateUsage.input += run.usage.input;
|
|
1251
|
+
aggregateUsage.output += run.usage.output;
|
|
1252
|
+
aggregateUsage.cacheRead += run.usage.cacheRead;
|
|
1253
|
+
aggregateUsage.cacheWrite += run.usage.cacheWrite;
|
|
1254
|
+
aggregateUsage.cost += run.usage.cost;
|
|
1255
|
+
aggregateUsage.turns += run.usage.turns;
|
|
1256
|
+
// A parked run still owes completion evidence when it actually finishes.
|
|
1257
|
+
// Stopped/timedOut runs already have terminal failures; checking completion evidence there is meaningless.
|
|
1258
|
+
const completionDiagnosticsEligible = !run.interrupted && !run.stopped && !run.timedOut;
|
|
1216
1259
|
const toolDiagnostic = run.exitCode === 0 && !run.error ? launch.capture.toolDiagnostic() : undefined;
|
|
1217
1260
|
const toolAvailabilityError = toolDiagnostic ? formatChildToolDiagnostic(toolDiagnostic) : undefined;
|
|
1218
1261
|
const runtimeAcknowledgedExtensions = launch.capture.runtimeAcknowledgedExtensions();
|
|
@@ -1228,7 +1271,7 @@ export async function runSingleStepInner(
|
|
|
1228
1271
|
let structuredOutput: unknown;
|
|
1229
1272
|
let structuredError: string | undefined;
|
|
1230
1273
|
let validatedStructuredOutput = false;
|
|
1231
|
-
if (effectiveStructuredOutput && run.exitCode === 0 && !run.error && !toolAvailabilityError && !midToolExitError) {
|
|
1274
|
+
if (completionDiagnosticsEligible && effectiveStructuredOutput && run.exitCode === 0 && !run.error && !toolAvailabilityError && !midToolExitError) {
|
|
1232
1275
|
if (!run.structuredOutputToolInvoked) {
|
|
1233
1276
|
structuredError = MISSING_STRUCTURED_OUTPUT_CALL_ERROR;
|
|
1234
1277
|
} else {
|
|
@@ -1250,13 +1293,13 @@ export async function runSingleStepInner(
|
|
|
1250
1293
|
const errorMessages = validatedStructuredOutput
|
|
1251
1294
|
? run.messages.slice(run.structuredOutputMessageStartIndex ?? run.messages.length)
|
|
1252
1295
|
: run.messages;
|
|
1253
|
-
const hiddenError = run.exitCode === 0 && !run.error && !toolAvailabilityError && !structuredError && !midToolExitError
|
|
1296
|
+
const hiddenError = completionDiagnosticsEligible && run.exitCode === 0 && !run.error && !toolAvailabilityError && !structuredError && !midToolExitError
|
|
1254
1297
|
? detectSubagentError(errorMessages)
|
|
1255
1298
|
: null;
|
|
1256
1299
|
const terminalEmptyAfterUsefulWork = !validatedStructuredOutput
|
|
1257
1300
|
&& hasEmptyTerminalAssistantResponse(run.messages)
|
|
1258
1301
|
&& (run.toolCount > 0 || Boolean(run.finalOutput.trim()));
|
|
1259
|
-
const emptyOutputError = run.exitCode === 0
|
|
1302
|
+
const emptyOutputError = completionDiagnosticsEligible && run.exitCode === 0
|
|
1260
1303
|
&& !run.error
|
|
1261
1304
|
&& !toolAvailabilityError
|
|
1262
1305
|
&& !structuredError
|
|
@@ -1268,10 +1311,11 @@ export async function runSingleStepInner(
|
|
|
1268
1311
|
const completionGuardEnabled = isAgentContract(step.agentContract) ? step.completionGuard === true : step.completionGuard !== false;
|
|
1269
1312
|
const completionToolPlan = resolvedTaskToolPlan;
|
|
1270
1313
|
const completionTools = completionToolPlan ? (completionToolPlan.explicitToolAllowlist ? completionToolPlan.effectiveToolAllowlist : undefined) : step.tools;
|
|
1271
|
-
const
|
|
1314
|
+
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;
|
|
1315
|
+
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);
|
|
1272
1316
|
finalMutationEvidence = mutationEvidence;
|
|
1273
1317
|
const completionMutationEvidence = ctx.trackedMutationEvidenceForCompletionGuard === false ? undefined : mutationEvidence;
|
|
1274
|
-
const completionGuard = run.exitCode === 0 && !run.error && !structuredError && !hiddenError?.hasError && !midToolExitError && !emptyOutputError && completionGuardEnabled
|
|
1318
|
+
const completionGuard = completionDiagnosticsEligible && run.exitCode === 0 && !run.error && !structuredError && !hiddenError?.hasError && !midToolExitError && !emptyOutputError && completionGuardEnabled
|
|
1275
1319
|
? evaluateCompletionMutationGuard(omitUndefinedProperties({
|
|
1276
1320
|
agent: step.agent,
|
|
1277
1321
|
task: taskForCompletionGuard,
|
|
@@ -1313,7 +1357,7 @@ export async function runSingleStepInner(
|
|
|
1313
1357
|
? { kind: "structured" as const, path: effectiveStructuredOutput.outputPath, missing: !fs.existsSync(effectiveStructuredOutput.outputPath) }
|
|
1314
1358
|
: undefined;
|
|
1315
1359
|
finalRequiredOutputMissing = requiredOutput?.missing;
|
|
1316
|
-
const missingRequiredOutputError = formatRequiredOutputError(requiredOutput);
|
|
1360
|
+
const missingRequiredOutputError = completionDiagnosticsEligible ? formatRequiredOutputError(requiredOutput) : undefined;
|
|
1317
1361
|
const missingRequiredOutputAfterMutation = Boolean(missingRequiredOutputError) && (mutationAttemptObserved || Boolean(mutationEvidence.changedFiles.length));
|
|
1318
1362
|
const effectiveExitCode = toolAvailabilityError || completionEvidence.legacyFailureError || midToolExitError || structuredError || emptyOutputError || missingRequiredOutputError
|
|
1319
1363
|
? 1
|
|
@@ -1334,15 +1378,6 @@ export async function runSingleStepInner(
|
|
|
1334
1378
|
: `${hiddenError.errorType} failed with exit code ${effectiveExitCode}`
|
|
1335
1379
|
: undefined);
|
|
1336
1380
|
const error = underlyingError ?? missingRequiredOutputError ?? completionEvidence.legacyFailureError;
|
|
1337
|
-
const attempt: ModelAttempt = omitUndefinedProperties({
|
|
1338
|
-
model: candidate ?? run.model ?? step.model ?? "default",
|
|
1339
|
-
success: effectiveExitCode === 0 && !error,
|
|
1340
|
-
exitCode: effectiveExitCode,
|
|
1341
|
-
error,
|
|
1342
|
-
usage: run.usage,
|
|
1343
|
-
});
|
|
1344
|
-
modelAttempts.push(attempt);
|
|
1345
|
-
if (!recoveringAbort && candidate) attemptedModels.push(candidate);
|
|
1346
1381
|
completionGuardTriggeredFinal = completionEvidence.guardTriggered && !underlyingError && !missingRequiredOutputError;
|
|
1347
1382
|
finalOutputSnapshot = outputSnapshot;
|
|
1348
1383
|
if (step.toolBudget) {
|
|
@@ -1360,11 +1395,14 @@ export async function runSingleStepInner(
|
|
|
1360
1395
|
});
|
|
1361
1396
|
const fileMutationEffect = completionEvidence.fileMutation ?? (missingRequiredOutputAfterMutation ? { status: "observed" as const, expected: completionEvidence.mutationExpected, attempted: true, evidence: mutationEvidence } : undefined);
|
|
1362
1397
|
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;
|
|
1363
|
-
|
|
1398
|
+
if (run.stopped || run.timedOut || ctx.timeoutSignal?.aborted || ctx.stopSignal?.aborted || ctx.skipAcceptance?.()) break singleLaunch;
|
|
1399
|
+
if (effectiveExitCode === 0 && !error) break singleLaunch;
|
|
1400
|
+
if (completionEvidence.guardTriggered) break singleLaunch;
|
|
1401
|
+
const recovery = planAbortRecovery({
|
|
1364
1402
|
messages: run.messages,
|
|
1365
1403
|
error,
|
|
1366
1404
|
sessionAvailable: Boolean(step.sessionFile && fs.existsSync(step.sessionFile)),
|
|
1367
|
-
alreadyResumed:
|
|
1405
|
+
alreadyResumed: attemptIndex > 0,
|
|
1368
1406
|
stopped: run.stopped || ctx.stopSignal?.aborted || ctx.skipAcceptance?.(),
|
|
1369
1407
|
interrupted: run.interrupted,
|
|
1370
1408
|
timedOut: run.timedOut || ctx.timeoutSignal?.aborted,
|
|
@@ -1374,83 +1412,26 @@ export async function runSingleStepInner(
|
|
|
1374
1412
|
acceptanceFailed: false,
|
|
1375
1413
|
currentTool: run.currentTool,
|
|
1376
1414
|
afterCompactionSettlement: run.afterCompactionSettlement,
|
|
1377
|
-
})
|
|
1378
|
-
if (
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
: abortRecovery.diagnostic;
|
|
1382
|
-
if (finalResult) finalResult.abortRecoveryDiagnostic = abortRecovery.diagnostic;
|
|
1415
|
+
});
|
|
1416
|
+
if (recovery.action === "resume") {
|
|
1417
|
+
recoveryTask = recovery.prompt;
|
|
1418
|
+
continue singleLaunch;
|
|
1383
1419
|
}
|
|
1384
|
-
if (
|
|
1385
|
-
|
|
1386
|
-
if (attempt.success) break modelAttemptsLoop;
|
|
1387
|
-
// The shared token is consumed before sibling creation; no third dispatch of any kind.
|
|
1388
|
-
if (recoveryState === "readonly-continuation") break modelAttemptsLoop;
|
|
1389
|
-
if (recoveringAbort) break modelAttemptsLoop;
|
|
1390
|
-
if (abortRecovery?.action === "resume") {
|
|
1391
|
-
recoveryState = "abort-recovery";
|
|
1392
|
-
nextAttemptTask = abortRecovery.prompt;
|
|
1393
|
-
attemptNotes.push("[abort-recovery] provider/transport abort after useful progress; resuming the retained child session once.");
|
|
1394
|
-
continue;
|
|
1420
|
+
if (recovery.diagnostic) {
|
|
1421
|
+
finalResult.abortRecoveryDiagnostic = recovery.diagnostic;
|
|
1395
1422
|
}
|
|
1396
|
-
if (completionEvidence.guardTriggered) break modelAttemptsLoop;
|
|
1397
1423
|
|
|
1398
|
-
const source = getSettledReadonlyChild(run);
|
|
1399
|
-
if (source) {
|
|
1400
|
-
const captured = launch.capture.completionIntentContext?.();
|
|
1401
|
-
const sourceModel = captured?.model;
|
|
1402
|
-
const retained = getReadonlySessionEvidence(source)!;
|
|
1403
|
-
// Modality/capacity assessment, not another history/provenance validator.
|
|
1404
|
-
// Images and unknown content have no certified token bound in this host slice.
|
|
1405
|
-
const textOnly = (JSON.parse(retained.contextJson) as Message[]).every((message) => typeof message.content === "string"
|
|
1406
|
-
|| (Array.isArray(message.content) && message.content.every((block) => ["text", "thinking", "toolCall"].includes(block.type))));
|
|
1407
|
-
const retainedBytes = Buffer.byteLength(retained.contextJson, "utf8");
|
|
1408
|
-
const resolvedCandidates = candidates.map((reference, index) => {
|
|
1409
|
-
// Only exact registry identities qualify; aliases/default guesses remain ineligible.
|
|
1410
|
-
const base = reference ? splitKnownThinkingSuffix(reference).baseModel : "";
|
|
1411
|
-
const slash = base.indexOf("/");
|
|
1412
|
-
const model = slash > 0 ? captured?.modelRegistry.find(base.slice(0, slash), base.slice(slash + 1)) : undefined;
|
|
1413
|
-
// Reserve the source window for runtime/system/tool overhead, plus a conservative
|
|
1414
|
-
// byte bound for the complete retained text and new prompt; never assume a fit
|
|
1415
|
-
// merely because a 429 request was sent. Equal/smaller windows remain denied.
|
|
1416
|
-
const compatible = textOnly && model && sourceModel && model.input?.includes("text")
|
|
1417
|
-
&& Number.isFinite(sourceModel.contextWindow) && sourceModel.contextWindow > 0
|
|
1418
|
-
&& model.contextWindow >= sourceModel.contextWindow + retainedBytes + Buffer.byteLength(READONLY_CONTINUATION_PROMPT, "utf8")
|
|
1419
|
-
&& model.maxTokens > 0 && model.maxTokens <= sourceModel.maxTokens;
|
|
1420
|
-
return { resolved: model ? { provider: model.provider, model: model.id, api: model.api } : undefined,
|
|
1421
|
-
tried: index <= modelIndex, compatibility: compatible ? "compatible" as const : "unknown" as const };
|
|
1422
|
-
});
|
|
1423
|
-
const plan = planReadonlyModelContinuation({ source, recoveryState, candidates: resolvedCandidates, currentIndex: modelIndex,
|
|
1424
|
-
lifecycleAllowsContinuation: lifecycleAllowsContinuation() && !run.interrupted && !run.stopped && !run.timedOut,
|
|
1425
|
-
effectsAllowContinuation: !run.currentTool && !run.observedMutationAttempt && !mutationEvidence.attemptedMutation
|
|
1426
|
-
&& !structuredError && !effectiveStructuredOutput && !missingRequiredOutputError && !toolAvailabilityError
|
|
1427
|
-
&& !completionEvidence.guardTriggered && !midToolExitError && !hiddenError?.hasError,
|
|
1428
|
-
budget: continuationBudget(), knownContextOverflow: isContextOverflow(error) });
|
|
1429
|
-
if (plan.kind === "continue" && canContinue()) {
|
|
1430
|
-
recoveryState = plan.recoveryState;
|
|
1431
|
-
const selected = resolvedCandidates[plan.candidateIndex]!.resolved!;
|
|
1432
|
-
readonlyContinuation = { source, expected: plan.expected, modelId: `${selected.provider}/${selected.model}` };
|
|
1433
|
-
nextAttemptTask = plan.prompt;
|
|
1434
|
-
modelIndex = plan.candidateIndex;
|
|
1435
|
-
attemptNotes.push(`[readonly-continuation] ${attempt.model} returned HTTP 429 after read-only progress; continuing the retained session once with ${candidates[modelIndex]}.`);
|
|
1436
|
-
continue;
|
|
1437
|
-
}
|
|
1438
|
-
}
|
|
1439
|
-
|
|
1440
|
-
const retryableModelFailure = isRetryableModelFailureAttempt({ error, messages: run.messages, toolCount: run.toolCount });
|
|
1441
|
-
if (retryableModelFailure) recordRetryableModelFailure(candidate ?? run.model ?? step.model, error);
|
|
1442
1424
|
if (isContextOverflow(error)) {
|
|
1443
1425
|
contextOverflow = true;
|
|
1444
|
-
|
|
1445
|
-
break modelAttemptsLoop;
|
|
1426
|
+
break singleLaunch;
|
|
1446
1427
|
}
|
|
1447
|
-
|
|
1448
|
-
attemptNotes.push(formatModelAttemptNote(attempt, candidates[modelIndex + 1]));
|
|
1449
|
-
modelIndex += 1;
|
|
1428
|
+
break singleLaunch;
|
|
1450
1429
|
}
|
|
1451
1430
|
|
|
1452
1431
|
const rawOutput = finalResult?.finalOutput ?? "";
|
|
1453
|
-
|
|
1432
|
+
let outputForPersistence = stripAcceptanceReport(rawOutput);
|
|
1433
|
+
if (!outputForPersistence.trim() && finalResult?.structuredOutput !== undefined)
|
|
1434
|
+
outputForPersistence = JSON.stringify(finalResult.structuredOutput, null, 2);
|
|
1454
1435
|
const resolvedOutput = step.outputPath && finalResult?.exitCode === 0
|
|
1455
1436
|
? resolveSingleOutput(step.outputPath, outputForPersistence, finalOutputSnapshot, step.outputClaimPath)
|
|
1456
1437
|
: { fullOutput: outputForPersistence };
|
|
@@ -1463,9 +1444,6 @@ export async function runSingleStepInner(
|
|
|
1463
1444
|
const output = stripAcceptanceReport(resolvedOutput.fullOutput);
|
|
1464
1445
|
const outputReference = resolvedOutput.savedPath ? formatSavedOutputReference(resolvedOutput.savedPath, output) : undefined;
|
|
1465
1446
|
let outputForSummary = output;
|
|
1466
|
-
if (attemptNotes.length > 0) {
|
|
1467
|
-
outputForSummary = `${attemptNotes.join("\n")}\n\n${outputForSummary}`.trim();
|
|
1468
|
-
}
|
|
1469
1447
|
if (finalResult?.stopped && !outputForSummary.trim()) {
|
|
1470
1448
|
outputForSummary = ctx.stopMessage ?? "Subagent stopped by user.";
|
|
1471
1449
|
}
|
|
@@ -1550,7 +1528,7 @@ export async function runSingleStepInner(
|
|
|
1550
1528
|
abortRecoveryDiagnostic: effectiveFinalExitCode !== 0 ? finalResult?.abortRecoveryDiagnostic : undefined,
|
|
1551
1529
|
requiredOutput: effectiveFinalExitCode !== 0 ? finalResult?.effects?.settlementDiagnostic?.requiredOutput : undefined,
|
|
1552
1530
|
});
|
|
1553
|
-
const usage =
|
|
1531
|
+
const usage = launched ? aggregateUsage : undefined;
|
|
1554
1532
|
|
|
1555
1533
|
const artifactErrors = artifactPaths && ctx.artifactConfig?.enabled !== false
|
|
1556
1534
|
? persistStepArtifacts({
|
|
@@ -1568,8 +1546,8 @@ export async function runSingleStepInner(
|
|
|
1568
1546
|
task: PROMPT_REDACTED,
|
|
1569
1547
|
exitCode: effectiveFinalExitCode,
|
|
1570
1548
|
model: finalResult?.model,
|
|
1571
|
-
|
|
1572
|
-
|
|
1549
|
+
nativeMachine: finalResult?.nativeMachine,
|
|
1550
|
+
requestedModel: step.requestedModel,
|
|
1573
1551
|
usage,
|
|
1574
1552
|
error: effectiveFinalError,
|
|
1575
1553
|
acceptance: effectiveAcceptance,
|
|
@@ -1598,15 +1576,16 @@ export async function runSingleStepInner(
|
|
|
1598
1576
|
sessionFile: step.sessionFile,
|
|
1599
1577
|
intercomTarget: ctx.childIntercomTarget,
|
|
1600
1578
|
model: finalResult?.model,
|
|
1579
|
+
nativeMachine: finalResult?.nativeMachine,
|
|
1601
1580
|
thinking: resolveEffectiveThinking(finalResult?.model, step.thinking),
|
|
1602
|
-
|
|
1603
|
-
modelAttempts,
|
|
1581
|
+
requestedModel: step.requestedModel,
|
|
1604
1582
|
contextOverflow: contextOverflow || undefined,
|
|
1605
|
-
totalCost:
|
|
1583
|
+
totalCost: costSummaryFromUsage(usage),
|
|
1606
1584
|
usage,
|
|
1607
1585
|
artifactPaths,
|
|
1608
1586
|
savedOutputPath: finalizedOutput.savedPath,
|
|
1609
1587
|
outputSaveError: [resolvedOutput.saveError, artifactErrors.outputSaveError].filter(Boolean).join("\n") || undefined,
|
|
1588
|
+
artifactOutputSaveFailed: artifactErrors.outputSaveError ? true : undefined,
|
|
1610
1589
|
metadataSaveError: artifactErrors.metadataSaveError,
|
|
1611
1590
|
transcriptPath: transcriptWriter ? artifactPaths?.transcriptPath : undefined,
|
|
1612
1591
|
transcriptError: transcriptWriter?.getError(),
|
|
@@ -1850,8 +1829,12 @@ function missingRequiredOutputAfterUsefulMutation(result: SingleStepResult): boo
|
|
|
1850
1829
|
}
|
|
1851
1830
|
|
|
1852
1831
|
function partialExecutionWithUsefulMutation(result: SingleStepResult): boolean {
|
|
1832
|
+
if (result.execution?.status !== "partial") return false;
|
|
1833
|
+
// Pane-native external results are deliberately partial from bounded terminal
|
|
1834
|
+
// evidence alone; Pi mutation-based partials retain their existing behavior.
|
|
1835
|
+
if (result.runner?.type === "external-cli" && result.runner.machine) return true;
|
|
1853
1836
|
const fileMutation = result.effects?.fileMutation;
|
|
1854
|
-
return
|
|
1837
|
+
return fileMutation?.attempted === true || Boolean(fileMutation?.evidence?.changedFiles.length);
|
|
1855
1838
|
}
|
|
1856
1839
|
|
|
1857
1840
|
function partialEvidenceResult(result: SingleStepResult): boolean {
|
|
@@ -1954,6 +1937,7 @@ export async function runSubagent(
|
|
|
1954
1937
|
let currentActivityState: ActivityState | undefined;
|
|
1955
1938
|
let activityTimer: NodeJS.Timeout | undefined;
|
|
1956
1939
|
let timeoutTimer: NodeJS.Timeout | undefined;
|
|
1940
|
+
let checkpointTimer: NodeJS.Timeout | undefined;
|
|
1957
1941
|
let timedOut = false;
|
|
1958
1942
|
let stopped = false;
|
|
1959
1943
|
let usageBudgetExceeded = false;
|
|
@@ -1962,6 +1946,7 @@ export async function runSubagent(
|
|
|
1962
1946
|
const timeoutAbortController = new AbortController();
|
|
1963
1947
|
const stopAbortController = new AbortController();
|
|
1964
1948
|
const setupInterruptController = new AbortController();
|
|
1949
|
+
const runStopSignal = AbortSignal.any([timeoutAbortController.signal, stopAbortController.signal]);
|
|
1965
1950
|
const setupSignal = AbortSignal.any([timeoutAbortController.signal, stopAbortController.signal, setupInterruptController.signal]);
|
|
1966
1951
|
let previousCumulativeTokens: TokenUsage = { input: 0, output: 0, total: 0 };
|
|
1967
1952
|
let latestSessionFile: string | undefined;
|
|
@@ -2003,7 +1988,7 @@ export async function runSubagent(
|
|
|
2003
1988
|
model: task.model,
|
|
2004
1989
|
...(task.contextLimit !== undefined ? { contextLimit: task.contextLimit } : {}),
|
|
2005
1990
|
thinking: task.thinking,
|
|
2006
|
-
|
|
1991
|
+
requestedModel: task.requestedModel,
|
|
2007
1992
|
recentTools: [],
|
|
2008
1993
|
recentOutput: [],
|
|
2009
1994
|
}));
|
|
@@ -2057,7 +2042,7 @@ export async function runSubagent(
|
|
|
2057
2042
|
model: step.model,
|
|
2058
2043
|
...(step.contextLimit !== undefined ? { contextLimit: step.contextLimit } : {}),
|
|
2059
2044
|
thinking: step.thinking,
|
|
2060
|
-
|
|
2045
|
+
requestedModel: step.requestedModel,
|
|
2061
2046
|
recentTools: [],
|
|
2062
2047
|
recentOutput: [],
|
|
2063
2048
|
}));
|
|
@@ -2284,9 +2269,7 @@ export async function runSubagent(
|
|
|
2284
2269
|
sessionFile: step.sessionFile,
|
|
2285
2270
|
model: step.model,
|
|
2286
2271
|
thinking: step.thinking,
|
|
2287
|
-
|
|
2288
|
-
modelAttempts: step.modelAttempts,
|
|
2289
|
-
usage: usageFromAttempts(step.modelAttempts),
|
|
2272
|
+
requestedModel: step.requestedModel,
|
|
2290
2273
|
contextOverflow: step.contextOverflow,
|
|
2291
2274
|
})),
|
|
2292
2275
|
exitCode: state === "complete" || state === "paused" ? 0 : 1,
|
|
@@ -2451,6 +2434,8 @@ export async function runSubagent(
|
|
|
2451
2434
|
yield* nestedRuns(child.steps?.flatMap((step) => step.children ?? []));
|
|
2452
2435
|
}
|
|
2453
2436
|
};
|
|
2437
|
+
const isNestedControlDescendant = (run: NestedRunSummary): boolean =>
|
|
2438
|
+
!config.nestedSelf || run.path.some((entry) => entry.runId === id);
|
|
2454
2439
|
const interruptNestedAsyncDescendants = (): void => {
|
|
2455
2440
|
if (!config.nestedRoute) return;
|
|
2456
2441
|
let registry: ReturnType<typeof projectNestedEvents>;
|
|
@@ -2466,7 +2451,7 @@ export async function runSubagent(
|
|
|
2466
2451
|
return;
|
|
2467
2452
|
}
|
|
2468
2453
|
for (const run of nestedRuns(registry.children)) {
|
|
2469
|
-
if (run.state !== "running" && run.state !== "queued") continue;
|
|
2454
|
+
if (!isNestedControlDescendant(run) || (run.state !== "running" && run.state !== "queued")) continue;
|
|
2470
2455
|
const nestedAsyncDir = run.asyncDir ?? resolveNestedAsyncDir(config.nestedRoute.rootRunId, run);
|
|
2471
2456
|
if (!nestedAsyncDir) continue;
|
|
2472
2457
|
try {
|
|
@@ -2497,7 +2482,7 @@ export async function runSubagent(
|
|
|
2497
2482
|
return;
|
|
2498
2483
|
}
|
|
2499
2484
|
for (const run of nestedRuns(registry.children)) {
|
|
2500
|
-
if (run.state !== "running" && run.state !== "queued") continue;
|
|
2485
|
+
if (!isNestedControlDescendant(run) || (run.state !== "running" && run.state !== "queued")) continue;
|
|
2501
2486
|
const nestedAsyncDir = run.asyncDir ?? resolveNestedAsyncDir(config.nestedRoute.rootRunId, run);
|
|
2502
2487
|
if (!nestedAsyncDir) continue;
|
|
2503
2488
|
try {
|
|
@@ -2528,7 +2513,7 @@ export async function runSubagent(
|
|
|
2528
2513
|
return;
|
|
2529
2514
|
}
|
|
2530
2515
|
for (const run of nestedRuns(registry.children)) {
|
|
2531
|
-
if (run.state !== "running" && run.state !== "queued") continue;
|
|
2516
|
+
if (!isNestedControlDescendant(run) || (run.state !== "running" && run.state !== "queued")) continue;
|
|
2532
2517
|
const nestedAsyncDir = run.asyncDir ?? resolveNestedAsyncDir(config.nestedRoute.rootRunId, run);
|
|
2533
2518
|
if (!nestedAsyncDir) continue;
|
|
2534
2519
|
try {
|
|
@@ -2584,6 +2569,7 @@ export async function runSubagent(
|
|
|
2584
2569
|
}
|
|
2585
2570
|
const appendedSteps = requests.flatMap((request) => request.steps);
|
|
2586
2571
|
steps.push(...appendedSteps);
|
|
2572
|
+
flatSteps.push(...flattenSteps(appendedSteps));
|
|
2587
2573
|
const now = Date.now();
|
|
2588
2574
|
const pendingAppends = countPendingChainAppendRequests(asyncDir);
|
|
2589
2575
|
const added = appendRunnerStepsToStatus({
|
|
@@ -2617,9 +2603,66 @@ export async function runSubagent(
|
|
|
2617
2603
|
setOptionalProperty(groupNode, "acceptanceStatus", acceptance?.status ?? groupNode.acceptanceStatus);
|
|
2618
2604
|
};
|
|
2619
2605
|
|
|
2606
|
+
const EXTERNAL_GIT_PROBE_MIN_INTERVAL_MS = 2_000;
|
|
2607
|
+
type ExternalActivityEvidence = {
|
|
2608
|
+
cwd?: string;
|
|
2609
|
+
lastStreamActivityAt?: number;
|
|
2610
|
+
fingerprint?: string;
|
|
2611
|
+
probeInFlight?: Promise<void>;
|
|
2612
|
+
lastProbeStartedAt?: number;
|
|
2613
|
+
};
|
|
2614
|
+
const externalActivityEvidence = new Map<number, ExternalActivityEvidence>();
|
|
2615
|
+
const gitProbesByCwd = new Map<string, Promise<string | undefined>>();
|
|
2616
|
+
let periodicGitProbeFailure: { index: number; error: Error; message: string } | undefined;
|
|
2617
|
+
const reportedGitProbeErrors = new Set<string>();
|
|
2618
|
+
const reportGitProbeError = (externalCwd: string, error: unknown): void => {
|
|
2619
|
+
if (reportedGitProbeErrors.has(externalCwd)) return;
|
|
2620
|
+
reportedGitProbeErrors.add(externalCwd);
|
|
2621
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
2622
|
+
console.error(`[pi-subagents] Git activity evidence unavailable for '${externalCwd.slice(-300)}'; check the cwd and Git installation: ${detail.slice(0, 500)}`);
|
|
2623
|
+
};
|
|
2624
|
+
const readSharedGitFingerprint = (externalCwd: string): Promise<string | undefined> => {
|
|
2625
|
+
const inFlight = gitProbesByCwd.get(externalCwd);
|
|
2626
|
+
if (inFlight) return inFlight;
|
|
2627
|
+
const probe = readGitFingerprint(externalCwd, runStopSignal, (error) => reportGitProbeError(externalCwd, error)).finally(() => gitProbesByCwd.delete(externalCwd));
|
|
2628
|
+
gitProbesByCwd.set(externalCwd, probe);
|
|
2629
|
+
return probe;
|
|
2630
|
+
};
|
|
2631
|
+
const recordPeriodicGitProbeFailure = (index: number, error: unknown): void => {
|
|
2632
|
+
if (periodicGitProbeFailure) return;
|
|
2633
|
+
const cause = error instanceof Error ? error : new Error(String(error));
|
|
2634
|
+
const message = `PROCESS_TREE_UNVERIFIED: ${cause.message}`;
|
|
2635
|
+
periodicGitProbeFailure = { index, error: cause, message };
|
|
2636
|
+
statusPayload.error = message;
|
|
2637
|
+
const step = statusPayload.steps[index];
|
|
2638
|
+
if (step) step.error = message;
|
|
2639
|
+
statusPayload.lastUpdate = Date.now();
|
|
2640
|
+
writeStatusPayload();
|
|
2641
|
+
appendJsonl(eventsPath, JSON.stringify({
|
|
2642
|
+
type: "subagent.run.process_tree_unverified",
|
|
2643
|
+
ts: statusPayload.lastUpdate,
|
|
2644
|
+
runId: id,
|
|
2645
|
+
index,
|
|
2646
|
+
code: "PROCESS_TREE_UNVERIFIED",
|
|
2647
|
+
message,
|
|
2648
|
+
}));
|
|
2649
|
+
};
|
|
2650
|
+
const prepareExternalActivity = async (index: number, externalCwd: string, signal: AbortSignal): Promise<void> => {
|
|
2651
|
+
if (!controlConfig.enabled) return;
|
|
2652
|
+
const resolvedCwd = path.resolve(externalCwd);
|
|
2653
|
+
const evidence: ExternalActivityEvidence = { cwd: resolvedCwd, lastProbeStartedAt: performance.now() };
|
|
2654
|
+
externalActivityEvidence.set(index, evidence);
|
|
2655
|
+
evidence.fingerprint = await readGitFingerprint(resolvedCwd, signal, (error) => reportGitProbeError(resolvedCwd, error));
|
|
2656
|
+
};
|
|
2657
|
+
const recordExternalStreamActivity = (index: number): void => {
|
|
2658
|
+
const evidence = externalActivityEvidence.get(index) ?? {};
|
|
2659
|
+
externalActivityEvidence.set(index, evidence);
|
|
2660
|
+
evidence.lastStreamActivityAt = Date.now();
|
|
2661
|
+
};
|
|
2620
2662
|
const stepOutputActivityAt = (index: number): number => {
|
|
2621
2663
|
const step = statusPayload.steps[index];
|
|
2622
2664
|
let lastActivityAt = step?.lastActivityAt ?? step?.startedAt ?? overallStartTime;
|
|
2665
|
+
lastActivityAt = Math.max(lastActivityAt, externalActivityEvidence.get(index)?.lastStreamActivityAt ?? 0);
|
|
2623
2666
|
const outputPath = path.join(asyncDir, `output-${index}.log`);
|
|
2624
2667
|
try {
|
|
2625
2668
|
lastActivityAt = Math.max(lastActivityAt, fs.statSync(outputPath).mtimeMs);
|
|
@@ -2911,7 +2954,10 @@ export async function runSubagent(
|
|
|
2911
2954
|
const applySteerDelivery = (requestId: string, index: number, delivery: { state: "delivered" | "queued" | "failed"; message: string }): void => {
|
|
2912
2955
|
const lifecycle = steeringStatus(statusPayload);
|
|
2913
2956
|
const request = lifecycle.recent.find((candidate) => candidate.id === requestId);
|
|
2914
|
-
|
|
2957
|
+
const target = request?.targets.find((candidate) => candidate.index === index);
|
|
2958
|
+
if (!request || !target) return;
|
|
2959
|
+
if (target.state === "delivered" || target.state === "late" || target.state === "failed") return;
|
|
2960
|
+
if (target.state === delivery.state) return;
|
|
2915
2961
|
const late = fs.existsSync(steeringMarkerPath(requestId));
|
|
2916
2962
|
const now = Date.now();
|
|
2917
2963
|
if (delivery.state === "delivered") {
|
|
@@ -2984,7 +3030,7 @@ export async function runSubagent(
|
|
|
2984
3030
|
return;
|
|
2985
3031
|
}
|
|
2986
3032
|
if (event.type === "message_end") {
|
|
2987
|
-
const next = applyChildWatchdogMessage(step.watchdog, event.message
|
|
3033
|
+
const next = applyChildWatchdogMessage(step.watchdog, event.message);
|
|
2988
3034
|
if (next) step.watchdog = next;
|
|
2989
3035
|
if (next && (event.message as { role?: unknown } | undefined)?.role === "custom") {
|
|
2990
3036
|
statusPayload.lastUpdate = now;
|
|
@@ -3125,7 +3171,7 @@ export async function runSubagent(
|
|
|
3125
3171
|
// A sibling may keep aggregate attention unchanged; publish this step's transition.
|
|
3126
3172
|
writeStatusPayload(step.activityState !== previousActivityState);
|
|
3127
3173
|
};
|
|
3128
|
-
const updateRunnerActivityState = (now: number): boolean => {
|
|
3174
|
+
const updateRunnerActivityState = (now: number, skipExternalProbeIndex?: number): boolean => {
|
|
3129
3175
|
if (!controlConfig.enabled) return false;
|
|
3130
3176
|
let changed = false;
|
|
3131
3177
|
let runLastActivityAt = statusPayload.lastActivityAt ?? overallStartTime;
|
|
@@ -3142,12 +3188,35 @@ export async function runSubagent(
|
|
|
3142
3188
|
config: controlConfig,
|
|
3143
3189
|
startedAt: step.startedAt ?? overallStartTime,
|
|
3144
3190
|
lastActivityAt,
|
|
3145
|
-
turnCount: step.turnCount,
|
|
3191
|
+
turnCount: flatSteps[index]?.runner?.type === "external-cli" ? 1 : step.turnCount,
|
|
3146
3192
|
currentTool: step.currentTool,
|
|
3147
3193
|
thinking: step.thinking,
|
|
3148
3194
|
now,
|
|
3149
3195
|
}));
|
|
3150
3196
|
if (idleState === "needs_attention") {
|
|
3197
|
+
const evidence = externalActivityEvidence.get(index);
|
|
3198
|
+
if (index !== skipExternalProbeIndex && evidence?.cwd && evidence.fingerprint) {
|
|
3199
|
+
if (evidence.probeInFlight) continue;
|
|
3200
|
+
if (performance.now() - (evidence.lastProbeStartedAt ?? 0) >= EXTERNAL_GIT_PROBE_MIN_INTERVAL_MS) {
|
|
3201
|
+
evidence.lastProbeStartedAt = performance.now();
|
|
3202
|
+
const baseline = evidence.fingerprint;
|
|
3203
|
+
evidence.probeInFlight = readSharedGitFingerprint(evidence.cwd).then((fingerprint) => {
|
|
3204
|
+
if (requiredStatusStep(statusPayload, index).status !== "running") return;
|
|
3205
|
+
if (fingerprint && fingerprint !== baseline) {
|
|
3206
|
+
evidence.fingerprint = fingerprint;
|
|
3207
|
+
evidence.lastStreamActivityAt = Date.now();
|
|
3208
|
+
updateRunnerActivityState(Date.now());
|
|
3209
|
+
} else {
|
|
3210
|
+
updateRunnerActivityState(Date.now(), index);
|
|
3211
|
+
}
|
|
3212
|
+
}).catch((error: unknown) => {
|
|
3213
|
+
recordPeriodicGitProbeFailure(index, error);
|
|
3214
|
+
}).finally(() => {
|
|
3215
|
+
delete evidence.probeInFlight;
|
|
3216
|
+
});
|
|
3217
|
+
continue;
|
|
3218
|
+
}
|
|
3219
|
+
}
|
|
3151
3220
|
const previous = step.activityState;
|
|
3152
3221
|
step.activityState = "needs_attention";
|
|
3153
3222
|
if (previous !== "needs_attention") {
|
|
@@ -3209,11 +3278,15 @@ export async function runSubagent(
|
|
|
3209
3278
|
};
|
|
3210
3279
|
const finalizeWorktree = async (setup: WorktreeSetup, stepIndex: number, flatStartIndex: number, action: () => void, deadlineAt?: number): Promise<void> => {
|
|
3211
3280
|
let admitted = false;
|
|
3212
|
-
try {
|
|
3213
|
-
|
|
3214
|
-
|
|
3215
|
-
|
|
3216
|
-
|
|
3281
|
+
try {
|
|
3282
|
+
await Promise.all([...externalActivityEvidence.values()].map((evidence) => evidence.probeInFlight).filter((probe): probe is Promise<void> => probe !== undefined));
|
|
3283
|
+
if (periodicGitProbeFailure) throw periodicGitProbeFailure.error;
|
|
3284
|
+
await withWorktreeTransaction(() => {
|
|
3285
|
+
if (deadlineAt !== undefined && Date.now() >= deadlineAt) throw new Error("Run deadline expired before worktree cleanup");
|
|
3286
|
+
admitted = true;
|
|
3287
|
+
action();
|
|
3288
|
+
});
|
|
3289
|
+
}
|
|
3217
3290
|
catch (error) {
|
|
3218
3291
|
if (admitted) throw error;
|
|
3219
3292
|
const reason = `Worktree finalization retained; manual reconciliation required: ${error instanceof Error ? error.message : String(error)}`;
|
|
@@ -3268,16 +3341,17 @@ export async function runSubagent(
|
|
|
3268
3341
|
interruptActiveChildren();
|
|
3269
3342
|
};
|
|
3270
3343
|
const stopRunner = () => {
|
|
3271
|
-
if (stopped || timedOut ||
|
|
3344
|
+
if (stopped || timedOut || (statusPayload.state !== "running" && statusPayload.state !== "paused")) return;
|
|
3272
3345
|
stopped = true;
|
|
3346
|
+
interrupted = false;
|
|
3273
3347
|
const now = Date.now();
|
|
3274
3348
|
statusPayload.stopped = true;
|
|
3275
3349
|
statusPayload.error = stopMessage;
|
|
3276
3350
|
currentActivityState = undefined;
|
|
3277
3351
|
delete statusPayload.activityState;
|
|
3278
3352
|
statusPayload.lastUpdate = now;
|
|
3279
|
-
for (const step of statusPayload.steps) {
|
|
3280
|
-
if (step.status !== "running" && step.status !== "pending") continue;
|
|
3353
|
+
for (const [index, step] of statusPayload.steps.entries()) {
|
|
3354
|
+
if (step.status !== "running" && step.status !== "pending" && step.status !== "paused") continue;
|
|
3281
3355
|
step.status = "stopped";
|
|
3282
3356
|
step.error = stopMessage;
|
|
3283
3357
|
step.exitCode = 1;
|
|
@@ -3286,6 +3360,8 @@ export async function runSubagent(
|
|
|
3286
3360
|
step.endedAt = now;
|
|
3287
3361
|
step.durationMs = step.startedAt ? now - step.startedAt : 0;
|
|
3288
3362
|
step.lastActivityAt = now;
|
|
3363
|
+
const result = results[index];
|
|
3364
|
+
if (result?.interrupted) results[index] = { ...result, output: stopMessage, error: stopMessage, exitCode: 1, success: false, interrupted: false, stopped: true };
|
|
3289
3365
|
}
|
|
3290
3366
|
writeStatusPayload();
|
|
3291
3367
|
appendJsonl(eventsPath, JSON.stringify({
|
|
@@ -3357,6 +3433,30 @@ export async function runSubagent(
|
|
|
3357
3433
|
const remainingMs = Math.max(0, config.deadlineAt - Date.now());
|
|
3358
3434
|
timeoutTimer = setTimeout(timeoutRunner, remainingMs);
|
|
3359
3435
|
timeoutTimer.unref?.();
|
|
3436
|
+
// Route the pre-deadline checkpoint like any external steer so its lifecycle records the receipt.
|
|
3437
|
+
const checkpointBeforeDeadlineMs = config.checkpointBeforeDeadlineMs;
|
|
3438
|
+
const checkpointDelayMs = checkpointBeforeDeadlineMs === undefined || !Number.isInteger(checkpointBeforeDeadlineMs) || checkpointBeforeDeadlineMs <= 0
|
|
3439
|
+
? undefined
|
|
3440
|
+
: remainingMs - checkpointBeforeDeadlineMs;
|
|
3441
|
+
if (checkpointDelayMs !== undefined && checkpointDelayMs >= 1_000) {
|
|
3442
|
+
const deadlineAt = config.deadlineAt;
|
|
3443
|
+
checkpointTimer = setTimeout(() => {
|
|
3444
|
+
checkpointTimer = undefined;
|
|
3445
|
+
if (timedOut || stopped || interrupted) return;
|
|
3446
|
+
if (!statusPayload.steps.some((step) => step.status === "running")) return;
|
|
3447
|
+
const now = Date.now();
|
|
3448
|
+
const seconds = Math.round(Math.max(0, deadlineAt - now) / 1000);
|
|
3449
|
+
deliverSteerRequest({
|
|
3450
|
+
type: "steer",
|
|
3451
|
+
id: `deadline-checkpoint-${now}`,
|
|
3452
|
+
ts: now,
|
|
3453
|
+
mode: "steer",
|
|
3454
|
+
source: "deadline-checkpoint",
|
|
3455
|
+
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.`,
|
|
3456
|
+
});
|
|
3457
|
+
}, checkpointDelayMs);
|
|
3458
|
+
checkpointTimer.unref?.();
|
|
3459
|
+
}
|
|
3360
3460
|
}
|
|
3361
3461
|
appendJsonl(
|
|
3362
3462
|
eventsPath,
|
|
@@ -3402,14 +3502,7 @@ export async function runSubagent(
|
|
|
3402
3502
|
const thinkingOverride = step.thinkingOverrides?.[itemIndex];
|
|
3403
3503
|
const model = thinkingOverride ? applyThinkingSuffix(step.parallel.model, thinkingOverride, true) : step.parallel.model;
|
|
3404
3504
|
const configThinking = thinkingOverride ? thinkingOverride : step.parallel.thinking;
|
|
3405
|
-
|
|
3406
|
-
? step.parallel.modelCandidates.length > 0
|
|
3407
|
-
? step.parallel.modelCandidates.map((candidate) => thinkingOverride ? applyThinkingSuffix(candidate, thinkingOverride, true) ?? candidate : candidate)
|
|
3408
|
-
: [undefined]
|
|
3409
|
-
: model ? [model] : [undefined];
|
|
3410
|
-
for (const candidate of candidates) {
|
|
3411
|
-
assertThinkingWithinCeiling({ model: candidate, configThinking, ceiling: step.parallel.thinkingCeiling, agent: step.parallel.agent, runId: id });
|
|
3412
|
-
}
|
|
3505
|
+
assertThinkingWithinCeiling({ model, configThinking, ceiling: step.parallel.thinkingCeiling, agent: step.parallel.agent, runId: id });
|
|
3413
3506
|
}
|
|
3414
3507
|
if (materialized.collectedOnEmpty) await validateDynamicCollection(step.collect.outputSchema, materialized.collectedOnEmpty);
|
|
3415
3508
|
if (!config.runFanoutBudget) throw new Error("Async runner is missing its run fan-out budget identity.");
|
|
@@ -3542,10 +3635,6 @@ export async function runSubagent(
|
|
|
3542
3635
|
...(thinkingOverride ? {
|
|
3543
3636
|
...(model ? { model } : {}),
|
|
3544
3637
|
...(thinking ? { thinking } : {}),
|
|
3545
|
-
...(step.parallel.modelCandidates ? { modelCandidates: step.parallel.modelCandidates.flatMap((candidate) => {
|
|
3546
|
-
const resolved = applyThinkingSuffix(candidate, thinkingOverride, true);
|
|
3547
|
-
return resolved ? [resolved] : [];
|
|
3548
|
-
}) } : {}),
|
|
3549
3638
|
} : {}),
|
|
3550
3639
|
structuredOutputSchema: step.parallel.structuredOutputSchema ?? step.parallel.structuredOutput?.schema,
|
|
3551
3640
|
});
|
|
@@ -3572,7 +3661,7 @@ export async function runSubagent(
|
|
|
3572
3661
|
...(task.contextLimit !== undefined ? { contextLimit: task.contextLimit } : {}),
|
|
3573
3662
|
...(task.thinking ? { thinking: task.thinking } : {}),
|
|
3574
3663
|
...(task.thinkingCeiling ? { thinkingCeiling: task.thinkingCeiling } : {}),
|
|
3575
|
-
...(task.
|
|
3664
|
+
...(task.requestedModel ? { requestedModel: task.requestedModel } : {}),
|
|
3576
3665
|
recentTools: [],
|
|
3577
3666
|
recentOutput: [],
|
|
3578
3667
|
});
|
|
@@ -3686,10 +3775,12 @@ export async function runSubagent(
|
|
|
3686
3775
|
nestedRoute: config.nestedRoute,
|
|
3687
3776
|
capabilityCeiling: config.capabilityCeiling,
|
|
3688
3777
|
runFanoutBudget: config.runFanoutBudget,
|
|
3778
|
+
hostAvailableBuiltins: config.hostAvailableBuiltins,
|
|
3689
3779
|
registerInterrupt: (interrupt) => registerStepInterrupt(fi, interrupt),
|
|
3690
3780
|
registerTimeout: (interrupt) => registerStepTimeout(fi, interrupt),
|
|
3691
3781
|
registerStop: (stop) => registerStepStop(fi, stop),
|
|
3692
3782
|
registerSteer: (steer) => registerStepSteer(fi, steer),
|
|
3783
|
+
onSteerOutcome: (request, delivery) => applySteerDelivery(request.id, fi, delivery),
|
|
3693
3784
|
timeoutSignal: timeoutAbortController.signal,
|
|
3694
3785
|
stopSignal: stopAbortController.signal,
|
|
3695
3786
|
trackedMutationEvidenceForCompletionGuard: false,
|
|
@@ -3699,6 +3790,8 @@ export async function runSubagent(
|
|
|
3699
3790
|
onAttemptStart: (attempt) => updateStepModel(fi, attempt.model, attempt.thinking, attempt.contextLimit),
|
|
3700
3791
|
onChildEvent: (event) => updateStepFromChildEvent(fi, event),
|
|
3701
3792
|
onExternalProcess: (process) => updateExternalProcess(fi, process),
|
|
3793
|
+
prepareExternalActivity: (externalCwd, signal) => prepareExternalActivity(fi, externalCwd, signal),
|
|
3794
|
+
onExternalStreamActivity: () => recordExternalStreamActivity(fi),
|
|
3702
3795
|
onExternalJob: (externalJob) => updateExternalJob(fi, externalJob),
|
|
3703
3796
|
skipAcceptance: () => timedOut || stopped || childStopRequests.has(fi),
|
|
3704
3797
|
usageBudgetExhausted: continuationUsageBudgetExhausted,
|
|
@@ -3708,7 +3801,7 @@ export async function runSubagent(
|
|
|
3708
3801
|
const taskEndTime = Date.now();
|
|
3709
3802
|
const childInterrupted = singleResult.interrupted === true;
|
|
3710
3803
|
const childStopped = singleResult.stopped === true;
|
|
3711
|
-
requiredStatusStep(statusPayload, fi).status = stopped || childStopped ? "stopped" : timedOut ? "failed" : childInterrupted ? "paused" : singleResult.exitCode === 0 ? "complete" : "failed";
|
|
3804
|
+
requiredStatusStep(statusPayload, fi).status = stopped || childStopped ? "stopped" : timedOut ? "failed" : childInterrupted ? "paused" : singleResult.execution?.status === "partial" ? "partial" : singleResult.exitCode === 0 ? "complete" : "failed";
|
|
3712
3805
|
requiredStatusStep(statusPayload, fi).endedAt = taskEndTime;
|
|
3713
3806
|
requiredStatusStep(statusPayload, fi).durationMs = taskEndTime - taskStartTime;
|
|
3714
3807
|
requiredStatusStep(statusPayload, fi).exitCode = stopped || childStopped ? 1 : timedOut ? 1 : childInterrupted ? 0 : singleResult.exitCode;
|
|
@@ -3721,8 +3814,7 @@ export async function runSubagent(
|
|
|
3721
3814
|
setOptionalProperty(requiredStatusStep(statusPayload, fi), "sessionName", singleResult.sessionName);
|
|
3722
3815
|
setOptionalProperty(requiredStatusStep(statusPayload, fi), "model", singleResult.model);
|
|
3723
3816
|
setOptionalProperty(requiredStatusStep(statusPayload, fi), "thinking", resolveEffectiveThinking(singleResult.model, requiredStatusStep(statusPayload, fi).thinking));
|
|
3724
|
-
setOptionalProperty(requiredStatusStep(statusPayload, fi), "
|
|
3725
|
-
setOptionalProperty(requiredStatusStep(statusPayload, fi), "modelAttempts", singleResult.modelAttempts);
|
|
3817
|
+
setOptionalProperty(requiredStatusStep(statusPayload, fi), "requestedModel", singleResult.requestedModel);
|
|
3726
3818
|
setOptionalProperty(requiredStatusStep(statusPayload, fi), "contextOverflow", singleResult.contextOverflow);
|
|
3727
3819
|
setOptionalProperty(requiredStatusStep(statusPayload, fi), "totalCost", singleResult.totalCost);
|
|
3728
3820
|
if (singleResult.totalCost) {
|
|
@@ -3779,7 +3871,7 @@ export async function runSubagent(
|
|
|
3779
3871
|
output: pr.output,
|
|
3780
3872
|
outputState: pr.outputState,
|
|
3781
3873
|
error: pr.error,
|
|
3782
|
-
success: pr.stopped !== true && pr.interrupted !== true && pr.exitCode === 0,
|
|
3874
|
+
success: pr.stopped !== true && pr.interrupted !== true && pr.exitCode === 0 && pr.execution?.status !== "partial",
|
|
3783
3875
|
exitCode: pr.interrupted === true ? 0 : pr.exitCode,
|
|
3784
3876
|
skipped: pr.skipped,
|
|
3785
3877
|
interrupted: pr.interrupted,
|
|
@@ -3791,12 +3883,13 @@ export async function runSubagent(
|
|
|
3791
3883
|
intercomTarget: pr.intercomTarget,
|
|
3792
3884
|
model: pr.model,
|
|
3793
3885
|
thinking: pr.thinking,
|
|
3794
|
-
|
|
3795
|
-
modelAttempts: pr.modelAttempts,
|
|
3886
|
+
requestedModel: pr.requestedModel,
|
|
3796
3887
|
contextOverflow: pr.contextOverflow,
|
|
3797
3888
|
totalCost: pr.totalCost,
|
|
3798
3889
|
usage: pr.usage,
|
|
3799
3890
|
artifactPaths: pr.artifactPaths,
|
|
3891
|
+
outputSaveError: pr.outputSaveError,
|
|
3892
|
+
artifactOutputSaveFailed: pr.artifactOutputSaveFailed,
|
|
3800
3893
|
transcriptPath: pr.transcriptPath,
|
|
3801
3894
|
transcriptError: pr.transcriptError,
|
|
3802
3895
|
effects: pr.effects,
|
|
@@ -4096,10 +4189,12 @@ export async function runSubagent(
|
|
|
4096
4189
|
nestedRoute: config.nestedRoute,
|
|
4097
4190
|
capabilityCeiling: config.capabilityCeiling,
|
|
4098
4191
|
runFanoutBudget: config.runFanoutBudget,
|
|
4192
|
+
hostAvailableBuiltins: config.hostAvailableBuiltins,
|
|
4099
4193
|
registerInterrupt: (interrupt) => registerStepInterrupt(fi, interrupt),
|
|
4100
4194
|
registerTimeout: (interrupt) => registerStepTimeout(fi, interrupt),
|
|
4101
4195
|
registerStop: (stop) => registerStepStop(fi, stop),
|
|
4102
4196
|
registerSteer: (steer) => registerStepSteer(fi, steer),
|
|
4197
|
+
onSteerOutcome: (request, delivery) => applySteerDelivery(request.id, fi, delivery),
|
|
4103
4198
|
timeoutSignal: timeoutAbortController.signal,
|
|
4104
4199
|
stopSignal: stopAbortController.signal,
|
|
4105
4200
|
trackedMutationEvidenceForCompletionGuard: Boolean(worktreeSetup),
|
|
@@ -4109,6 +4204,8 @@ export async function runSubagent(
|
|
|
4109
4204
|
onAttemptStart: (attempt) => updateStepModel(fi, attempt.model, attempt.thinking, attempt.contextLimit),
|
|
4110
4205
|
onChildEvent: (event) => updateStepFromChildEvent(fi, event),
|
|
4111
4206
|
onExternalProcess: (process) => updateExternalProcess(fi, process),
|
|
4207
|
+
prepareExternalActivity: (externalCwd, signal) => prepareExternalActivity(fi, externalCwd, signal),
|
|
4208
|
+
onExternalStreamActivity: () => recordExternalStreamActivity(fi),
|
|
4112
4209
|
onExternalJob: (externalJob) => updateExternalJob(fi, externalJob),
|
|
4113
4210
|
skipAcceptance: () => timedOut || stopped || childStopRequests.has(fi),
|
|
4114
4211
|
usageBudgetExhausted: continuationUsageBudgetExhausted,
|
|
@@ -4124,7 +4221,7 @@ export async function runSubagent(
|
|
|
4124
4221
|
const childInterrupted = singleResult.interrupted === true;
|
|
4125
4222
|
const childStopped = singleResult.stopped === true;
|
|
4126
4223
|
|
|
4127
|
-
requiredStatusStep(statusPayload, fi).status = stopped || childStopped ? "stopped" : timedOut ? "failed" : childInterrupted ? "paused" : singleResult.exitCode === 0 ? "complete" : "failed";
|
|
4224
|
+
requiredStatusStep(statusPayload, fi).status = stopped || childStopped ? "stopped" : timedOut ? "failed" : childInterrupted ? "paused" : singleResult.execution?.status === "partial" ? "partial" : singleResult.exitCode === 0 ? "complete" : "failed";
|
|
4128
4225
|
requiredStatusStep(statusPayload, fi).endedAt = taskEndTime;
|
|
4129
4226
|
requiredStatusStep(statusPayload, fi).durationMs = taskDuration;
|
|
4130
4227
|
requiredStatusStep(statusPayload, fi).exitCode = stopped || childStopped ? 1 : timedOut ? 1 : childInterrupted ? 0 : singleResult.exitCode;
|
|
@@ -4137,8 +4234,7 @@ export async function runSubagent(
|
|
|
4137
4234
|
setOptionalProperty(requiredStatusStep(statusPayload, fi), "sessionName", singleResult.sessionName);
|
|
4138
4235
|
setOptionalProperty(requiredStatusStep(statusPayload, fi), "model", singleResult.model);
|
|
4139
4236
|
setOptionalProperty(requiredStatusStep(statusPayload, fi), "thinking", resolveEffectiveThinking(singleResult.model, requiredStatusStep(statusPayload, fi).thinking));
|
|
4140
|
-
setOptionalProperty(requiredStatusStep(statusPayload, fi), "
|
|
4141
|
-
setOptionalProperty(requiredStatusStep(statusPayload, fi), "modelAttempts", singleResult.modelAttempts);
|
|
4237
|
+
setOptionalProperty(requiredStatusStep(statusPayload, fi), "requestedModel", singleResult.requestedModel);
|
|
4142
4238
|
setOptionalProperty(requiredStatusStep(statusPayload, fi), "contextOverflow", singleResult.contextOverflow);
|
|
4143
4239
|
setOptionalProperty(requiredStatusStep(statusPayload, fi), "totalCost", singleResult.totalCost);
|
|
4144
4240
|
if (singleResult.totalCost) {
|
|
@@ -4205,7 +4301,7 @@ export async function runSubagent(
|
|
|
4205
4301
|
const sessionTokens = config.sessionDir
|
|
4206
4302
|
? parseSessionTokens(path.join(config.sessionDir, `parallel-${t}`))
|
|
4207
4303
|
: null;
|
|
4208
|
-
const fallbackTokens =
|
|
4304
|
+
const fallbackTokens = tokenUsageFromUsage(parallelResults[t]?.usage);
|
|
4209
4305
|
const observedTokens = requiredStatusStep(statusPayload, fi).tokens;
|
|
4210
4306
|
const taskTokens = sessionTokens ?? (fallbackTokens
|
|
4211
4307
|
? { ...fallbackTokens, ...(observedTokens?.window !== undefined ? { window: observedTokens.window } : {}), ...(observedTokens?.windowPeak !== undefined ? { windowPeak: observedTokens.windowPeak } : {}) }
|
|
@@ -4236,7 +4332,7 @@ export async function runSubagent(
|
|
|
4236
4332
|
output: pr.output,
|
|
4237
4333
|
outputState: pr.outputState,
|
|
4238
4334
|
error: pr.error,
|
|
4239
|
-
success: pr.stopped !== true && pr.interrupted !== true && pr.exitCode === 0,
|
|
4335
|
+
success: pr.stopped !== true && pr.interrupted !== true && pr.exitCode === 0 && pr.execution?.status !== "partial",
|
|
4240
4336
|
exitCode: pr.interrupted === true ? 0 : pr.exitCode,
|
|
4241
4337
|
skipped: pr.skipped,
|
|
4242
4338
|
interrupted: pr.interrupted,
|
|
@@ -4248,12 +4344,13 @@ export async function runSubagent(
|
|
|
4248
4344
|
intercomTarget: pr.intercomTarget,
|
|
4249
4345
|
model: pr.model,
|
|
4250
4346
|
thinking: pr.thinking,
|
|
4251
|
-
|
|
4252
|
-
modelAttempts: pr.modelAttempts,
|
|
4347
|
+
requestedModel: pr.requestedModel,
|
|
4253
4348
|
contextOverflow: pr.contextOverflow,
|
|
4254
4349
|
totalCost: pr.totalCost,
|
|
4255
4350
|
usage: pr.usage,
|
|
4256
4351
|
artifactPaths: pr.artifactPaths,
|
|
4352
|
+
outputSaveError: pr.outputSaveError,
|
|
4353
|
+
artifactOutputSaveFailed: pr.artifactOutputSaveFailed,
|
|
4257
4354
|
transcriptPath: pr.transcriptPath,
|
|
4258
4355
|
transcriptError: pr.transcriptError,
|
|
4259
4356
|
effects: pr.effects,
|
|
@@ -4286,7 +4383,7 @@ export async function runSubagent(
|
|
|
4286
4383
|
exitCode: r.exitCode,
|
|
4287
4384
|
error: r.error,
|
|
4288
4385
|
model: r.model,
|
|
4289
|
-
|
|
4386
|
+
requestedModel: r.requestedModel,
|
|
4290
4387
|
})),
|
|
4291
4388
|
);
|
|
4292
4389
|
if (worktreeSetup) {
|
|
@@ -4496,10 +4593,12 @@ export async function runSubagent(
|
|
|
4496
4593
|
nestedRoute: config.nestedRoute,
|
|
4497
4594
|
capabilityCeiling: config.capabilityCeiling,
|
|
4498
4595
|
runFanoutBudget: config.runFanoutBudget,
|
|
4596
|
+
hostAvailableBuiltins: config.hostAvailableBuiltins,
|
|
4499
4597
|
registerInterrupt: (interrupt) => registerStepInterrupt(flatIndex, interrupt),
|
|
4500
4598
|
registerTimeout: (interrupt) => registerStepTimeout(flatIndex, interrupt),
|
|
4501
4599
|
registerStop: (stop) => registerStepStop(flatIndex, stop),
|
|
4502
4600
|
registerSteer: (steer) => registerStepSteer(flatIndex, steer),
|
|
4601
|
+
onSteerOutcome: (request, delivery) => applySteerDelivery(request.id, flatIndex, delivery),
|
|
4503
4602
|
timeoutSignal: timeoutAbortController.signal,
|
|
4504
4603
|
stopSignal: stopAbortController.signal,
|
|
4505
4604
|
timeoutMessage,
|
|
@@ -4508,6 +4607,8 @@ export async function runSubagent(
|
|
|
4508
4607
|
onAttemptStart: (attempt) => updateStepModel(flatIndex, attempt.model, attempt.thinking, attempt.contextLimit),
|
|
4509
4608
|
onChildEvent: (event) => updateStepFromChildEvent(flatIndex, event),
|
|
4510
4609
|
onExternalProcess: (process) => updateExternalProcess(flatIndex, process),
|
|
4610
|
+
prepareExternalActivity: (externalCwd, signal) => prepareExternalActivity(flatIndex, externalCwd, signal),
|
|
4611
|
+
onExternalStreamActivity: () => recordExternalStreamActivity(flatIndex),
|
|
4511
4612
|
onExternalJob: (externalJob) => updateExternalJob(flatIndex, externalJob),
|
|
4512
4613
|
skipAcceptance: () => timedOut || stopped || childStopRequests.has(flatIndex),
|
|
4513
4614
|
usageBudgetExhausted: continuationUsageBudgetExhausted,
|
|
@@ -4535,19 +4636,20 @@ export async function runSubagent(
|
|
|
4535
4636
|
output: stopped || childStopped ? stopMessage : timedOut ? singleResult.output || (timeoutMessage ?? "Subagent timed out.") : singleResult.output,
|
|
4536
4637
|
outputState: singleResult.outputState,
|
|
4537
4638
|
error: stopped || childStopped ? stopMessage : timedOut ? (timeoutMessage ?? "Subagent timed out.") : singleResult.error,
|
|
4538
|
-
success: !stopped && !childStopped && !timedOut && singleResult.interrupted !== true && singleResult.exitCode === 0,
|
|
4639
|
+
success: !stopped && !childStopped && !timedOut && singleResult.interrupted !== true && singleResult.exitCode === 0 && singleResult.execution?.status !== "partial",
|
|
4539
4640
|
exitCode: stopped || childStopped ? 1 : timedOut ? 1 : singleResult.interrupted === true ? 0 : singleResult.exitCode,
|
|
4540
4641
|
sessionFile: singleResult.sessionFile,
|
|
4541
4642
|
intercomTarget: singleResult.intercomTarget,
|
|
4542
4643
|
model: singleResult.model,
|
|
4543
4644
|
thinking: singleResult.thinking,
|
|
4544
|
-
|
|
4545
|
-
modelAttempts: singleResult.modelAttempts,
|
|
4645
|
+
requestedModel: singleResult.requestedModel,
|
|
4546
4646
|
contextOverflow: singleResult.contextOverflow,
|
|
4547
4647
|
totalCost: singleResult.totalCost,
|
|
4548
4648
|
usage: singleResult.usage,
|
|
4549
4649
|
artifactPaths: singleResult.artifactPaths,
|
|
4550
4650
|
savedOutputPath: singleResult.savedOutputPath,
|
|
4651
|
+
outputSaveError: singleResult.outputSaveError,
|
|
4652
|
+
artifactOutputSaveFailed: singleResult.artifactOutputSaveFailed,
|
|
4551
4653
|
transcriptPath: singleResult.transcriptPath,
|
|
4552
4654
|
transcriptError: singleResult.transcriptError,
|
|
4553
4655
|
effects: singleResult.effects,
|
|
@@ -4592,7 +4694,7 @@ export async function runSubagent(
|
|
|
4592
4694
|
if (cumulativeTokens) {
|
|
4593
4695
|
previousCumulativeTokens = cumulativeTokens;
|
|
4594
4696
|
} else {
|
|
4595
|
-
const fallbackTokens =
|
|
4697
|
+
const fallbackTokens = tokenUsageFromUsage(singleResult.usage);
|
|
4596
4698
|
const observedTokens = requiredStatusStep(statusPayload, flatIndex).tokens;
|
|
4597
4699
|
stepTokens = fallbackTokens
|
|
4598
4700
|
? { ...fallbackTokens, ...(observedTokens?.window !== undefined ? { window: observedTokens.window } : {}), ...(observedTokens?.windowPeak !== undefined ? { windowPeak: observedTokens.windowPeak } : {}) }
|
|
@@ -4612,7 +4714,7 @@ export async function runSubagent(
|
|
|
4612
4714
|
|
|
4613
4715
|
const stepEndTime = Date.now();
|
|
4614
4716
|
const childInterrupted = singleResult.interrupted === true;
|
|
4615
|
-
requiredStatusStep(statusPayload, flatIndex).status = stopped || childStopped ? "stopped" : timedOut ? "failed" : childInterrupted ? "paused" : singleResult.exitCode === 0 ? "complete" : "failed";
|
|
4717
|
+
requiredStatusStep(statusPayload, flatIndex).status = stopped || childStopped ? "stopped" : timedOut ? "failed" : childInterrupted ? "paused" : singleResult.execution?.status === "partial" ? "partial" : singleResult.exitCode === 0 ? "complete" : "failed";
|
|
4616
4718
|
requiredStatusStep(statusPayload, flatIndex).endedAt = stepEndTime;
|
|
4617
4719
|
requiredStatusStep(statusPayload, flatIndex).durationMs = stepEndTime - stepStartTime;
|
|
4618
4720
|
requiredStatusStep(statusPayload, flatIndex).exitCode = stopped || childStopped ? 1 : timedOut ? 1 : childInterrupted ? 0 : singleResult.exitCode;
|
|
@@ -4625,8 +4727,7 @@ export async function runSubagent(
|
|
|
4625
4727
|
setOptionalProperty(requiredStatusStep(statusPayload, flatIndex), "sessionName", singleResult.sessionName);
|
|
4626
4728
|
setOptionalProperty(requiredStatusStep(statusPayload, flatIndex), "model", singleResult.model);
|
|
4627
4729
|
setOptionalProperty(requiredStatusStep(statusPayload, flatIndex), "thinking", resolveEffectiveThinking(singleResult.model, requiredStatusStep(statusPayload, flatIndex).thinking));
|
|
4628
|
-
setOptionalProperty(requiredStatusStep(statusPayload, flatIndex), "
|
|
4629
|
-
setOptionalProperty(requiredStatusStep(statusPayload, flatIndex), "modelAttempts", singleResult.modelAttempts);
|
|
4730
|
+
setOptionalProperty(requiredStatusStep(statusPayload, flatIndex), "requestedModel", singleResult.requestedModel);
|
|
4630
4731
|
setOptionalProperty(requiredStatusStep(statusPayload, flatIndex), "contextOverflow", singleResult.contextOverflow);
|
|
4631
4732
|
setOptionalProperty(requiredStatusStep(statusPayload, flatIndex), "totalCost", singleResult.totalCost);
|
|
4632
4733
|
setOptionalProperty(requiredStatusStep(statusPayload, flatIndex), "error", stopped || childStopped ? stopMessage : timedOut ? (timeoutMessage ?? "Subagent timed out.") : singleResult.error);
|
|
@@ -4741,6 +4842,34 @@ export async function runSubagent(
|
|
|
4741
4842
|
}
|
|
4742
4843
|
}
|
|
4743
4844
|
|
|
4845
|
+
await Promise.all([...externalActivityEvidence.values()].map((evidence) => evidence.probeInFlight).filter((probe): probe is Promise<void> => probe !== undefined));
|
|
4846
|
+
if (periodicGitProbeFailure) {
|
|
4847
|
+
stopped = false;
|
|
4848
|
+
timedOut = false;
|
|
4849
|
+
interrupted = false;
|
|
4850
|
+
delete statusPayload.stopped;
|
|
4851
|
+
delete statusPayload.timedOut;
|
|
4852
|
+
statusPayload.error = periodicGitProbeFailure.message;
|
|
4853
|
+
const step = statusPayload.steps[periodicGitProbeFailure.index];
|
|
4854
|
+
if (step) {
|
|
4855
|
+
step.status = "failed";
|
|
4856
|
+
step.error = periodicGitProbeFailure.message;
|
|
4857
|
+
step.exitCode = 1;
|
|
4858
|
+
delete step.stopped;
|
|
4859
|
+
delete step.timedOut;
|
|
4860
|
+
}
|
|
4861
|
+
const result = results[periodicGitProbeFailure.index];
|
|
4862
|
+
if (result) {
|
|
4863
|
+
result.success = false;
|
|
4864
|
+
result.exitCode = 1;
|
|
4865
|
+
result.error = periodicGitProbeFailure.message;
|
|
4866
|
+
result.output = periodicGitProbeFailure.message;
|
|
4867
|
+
result.stopped = false;
|
|
4868
|
+
result.timedOut = false;
|
|
4869
|
+
result.interrupted = false;
|
|
4870
|
+
}
|
|
4871
|
+
}
|
|
4872
|
+
|
|
4744
4873
|
let summary = results.map((r) => `${r.agent}:\n${r.output || (r.exitCode !== 0 ? r.error : undefined) || "(no output)"}`).join("\n\n");
|
|
4745
4874
|
let truncated = false;
|
|
4746
4875
|
|
|
@@ -4806,9 +4935,15 @@ export async function runSubagent(
|
|
|
4806
4935
|
clearTimeout(timeoutTimer);
|
|
4807
4936
|
timeoutTimer = undefined;
|
|
4808
4937
|
}
|
|
4938
|
+
if (checkpointTimer) {
|
|
4939
|
+
clearTimeout(checkpointTimer);
|
|
4940
|
+
checkpointTimer = undefined;
|
|
4941
|
+
}
|
|
4809
4942
|
if (!timedOut && !stopped && !interrupted && config.timeoutMs !== undefined && timeoutMessage !== undefined && results.some((result) => result.timedOut === true && result.error?.startsWith(timeoutMessage))) {
|
|
4810
4943
|
timedOut = true;
|
|
4811
4944
|
}
|
|
4945
|
+
disposeControlInbox();
|
|
4946
|
+
for (const request of consumeStopRequestPayloads(asyncDir)) stopChildStep(request);
|
|
4812
4947
|
const signalTerminated = !stopped && !timedOut && !interrupted && results.some((result) => result.exitCode !== 0 && isUnexplainedProcessSignal(omitUndefinedProperties({
|
|
4813
4948
|
processSignal: result.processSignal,
|
|
4814
4949
|
interrupted: result.interrupted,
|
|
@@ -4823,7 +4958,6 @@ export async function runSubagent(
|
|
|
4823
4958
|
});
|
|
4824
4959
|
statusPayload.state = stopped || signalTerminated ? "stopped" : timedOut || usageBudgetExceeded ? "failed" : interrupted ? "paused" : results.every((r) => r.success) ? "complete" : partialWithEvidence ? "partial" : "failed";
|
|
4825
4960
|
closeSteerInbox(asyncDir, statusPayload.state, (filePath, payload) => runPersistence.write(filePath, payload));
|
|
4826
|
-
disposeControlInbox();
|
|
4827
4961
|
for (const request of consumeSteerRequests(asyncDir)) deliverSteerRequest(request);
|
|
4828
4962
|
const effectiveSessionFile = sessionFile ?? latestSessionFile;
|
|
4829
4963
|
const steeringLifecycle = steeringStatus(statusPayload);
|
|
@@ -4832,7 +4966,7 @@ export async function runSubagent(
|
|
|
4832
4966
|
for (const target of request.targets) {
|
|
4833
4967
|
if (target.state !== "scheduled" && target.state !== "routed" && target.state !== "queued") continue;
|
|
4834
4968
|
changed = true;
|
|
4835
|
-
const reason = target.state === "queued" ?
|
|
4969
|
+
const reason = target.state === "queued" ? unconsumedSteerReason() : "child terminated before steering delivery";
|
|
4836
4970
|
updateSteeringLifecycleTarget(request.id, target.index, "failed", Date.now(), { reason });
|
|
4837
4971
|
markSteeringAttention(target.index);
|
|
4838
4972
|
emitSteeringEvent("subagent.steer.failed", { type: "steer", id: request.id, ts: request.requestedAt, message: reason }, target.index, { reason });
|
|
@@ -4859,7 +4993,7 @@ export async function runSubagent(
|
|
|
4859
4993
|
statusPayload.activityState = "needs_attention";
|
|
4860
4994
|
statusPayload.error = partialResult?.error ?? statusPayload.error;
|
|
4861
4995
|
for (const step of statusPayload.steps) {
|
|
4862
|
-
if (step.status === "failed") step.activityState = "needs_attention";
|
|
4996
|
+
if (step.status === "failed" || step.status === "partial") step.activityState = "needs_attention";
|
|
4863
4997
|
}
|
|
4864
4998
|
}
|
|
4865
4999
|
statusPayload.endedAt = runEndedAt;
|
|
@@ -4916,14 +5050,14 @@ export async function runSubagent(
|
|
|
4916
5050
|
intercomTarget: r.intercomTarget,
|
|
4917
5051
|
model: r.model,
|
|
4918
5052
|
thinking: r.thinking,
|
|
4919
|
-
|
|
4920
|
-
modelAttempts: r.modelAttempts,
|
|
5053
|
+
requestedModel: r.requestedModel,
|
|
4921
5054
|
contextOverflow: r.contextOverflow,
|
|
4922
5055
|
totalCost: r.totalCost,
|
|
4923
5056
|
usage: r.usage,
|
|
4924
5057
|
artifactPaths: r.artifactPaths,
|
|
4925
5058
|
savedOutputPath: r.savedOutputPath,
|
|
4926
5059
|
outputSaveError: r.outputSaveError,
|
|
5060
|
+
artifactOutputSaveFailed: r.artifactOutputSaveFailed,
|
|
4927
5061
|
metadataSaveError: r.metadataSaveError,
|
|
4928
5062
|
truncated: r.truncated,
|
|
4929
5063
|
transcriptPath: r.transcriptPath,
|
|
@@ -5059,7 +5193,7 @@ export async function runSubagent(
|
|
|
5059
5193
|
async function waitForStartupControl(
|
|
5060
5194
|
controlPath: string,
|
|
5061
5195
|
token: string,
|
|
5062
|
-
action: "ack" | "proceed",
|
|
5196
|
+
action: "ack" | "confirm" | "proceed",
|
|
5063
5197
|
timeoutMs = 30_000,
|
|
5064
5198
|
): Promise<void> {
|
|
5065
5199
|
const deadline = Date.now() + timeoutMs;
|
|
@@ -5073,14 +5207,15 @@ async function waitForStartupControl(
|
|
|
5073
5207
|
}
|
|
5074
5208
|
if (payload.token !== token) throw new Error("Runner startup control token does not match.");
|
|
5075
5209
|
if (payload.action === action) return;
|
|
5076
|
-
if (payload.action !== "ack" && payload.action !== "proceed") throw new Error("Runner startup control action is invalid.");
|
|
5210
|
+
if (payload.action !== "ack" && payload.action !== "confirm" && payload.action !== "proceed") throw new Error("Runner startup control action is invalid.");
|
|
5077
5211
|
}
|
|
5078
5212
|
await new Promise((resolve) => setTimeout(resolve, 20));
|
|
5079
5213
|
}
|
|
5080
5214
|
throw new Error(`Timed out after ${timeoutMs}ms waiting for runner startup control '${action}'.`);
|
|
5081
5215
|
}
|
|
5082
5216
|
|
|
5083
|
-
|
|
5217
|
+
// Both hosts enter here: startup authorization, revival leases and disposal stay shared.
|
|
5218
|
+
export async function runConfiguredSubagent(config: SubagentRunConfig, options?: DefaultChildSessionFactoryOptions): Promise<void> {
|
|
5084
5219
|
let lease: ReturnType<typeof acquireSessionLease> | undefined;
|
|
5085
5220
|
let startupCommitted = config.revivalLease === undefined && config.launchBarrierToken === undefined;
|
|
5086
5221
|
const startupPath = path.join(config.asyncDir, "runner-startup.json");
|
|
@@ -5109,6 +5244,8 @@ async function runConfiguredSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
5109
5244
|
writeAtomicJson(startupPath, { state: "ready", token: lease.owner.token, pid: process.pid, owner: lease.owner });
|
|
5110
5245
|
await waitForStartupControl(startupAckPath, lease.owner.token, "ack");
|
|
5111
5246
|
writeAtomicJson(startupPath, { state: "acknowledged", token: lease.owner.token, pid: process.pid });
|
|
5247
|
+
await waitForStartupControl(startupAckPath, lease.owner.token, "confirm");
|
|
5248
|
+
writeAtomicJson(startupPath, { state: "confirmed", token: lease.owner.token, pid: process.pid });
|
|
5112
5249
|
await waitForStartupControl(startupProceedPath, lease.owner.token, "proceed");
|
|
5113
5250
|
startupCommitted = true;
|
|
5114
5251
|
for (const controlPath of [startupAckPath, startupProceedPath]) {
|
|
@@ -5119,7 +5256,7 @@ async function runConfiguredSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
5119
5256
|
}
|
|
5120
5257
|
}
|
|
5121
5258
|
}
|
|
5122
|
-
const childSessions = await loadRunnerChildSessionFactory(config);
|
|
5259
|
+
const childSessions = await loadRunnerChildSessionFactory(config, options);
|
|
5123
5260
|
try {
|
|
5124
5261
|
await runSubagent(config, childSessions);
|
|
5125
5262
|
} finally {
|