pi-subagents 0.47.0 → 0.48.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 +37 -0
- package/README.md +2 -0
- package/agents/reviewer.md +3 -4
- package/docs/configuration.md +46 -2
- package/docs/observability.md +1 -1
- package/docs/tool-reference.md +1 -1
- package/package.json +1 -1
- package/src/agents/agents.ts +9 -3
- package/src/extension/config.ts +16 -0
- package/src/extension/doctor.ts +40 -0
- package/src/extension/index.ts +30 -11
- package/src/extension/public-execution.ts +5 -0
- package/src/extension/rpc.ts +7 -9
- package/src/extension/schemas.ts +4 -4
- package/src/intercom/intercom-bridge.ts +4 -1
- package/src/intercom/native-supervisor-channel.ts +102 -4
- package/src/missions/lifecycle.ts +4 -7
- package/src/missions/store.ts +4 -4
- package/src/missions/workflow-state.ts +6 -2
- package/src/runs/background/active-async-capacity.ts +374 -0
- package/src/runs/background/active-run-index.ts +46 -0
- package/src/runs/background/async-execution.ts +117 -29
- package/src/runs/background/async-job-tracker.ts +279 -134
- package/src/runs/background/async-resume.ts +7 -1
- package/src/runs/background/async-status.ts +33 -4
- package/src/runs/background/chain-append.ts +33 -15
- package/src/runs/background/control-channel.ts +55 -17
- package/src/runs/background/owned-process-tree.ts +104 -0
- package/src/runs/background/process-terminal.ts +17 -3
- package/src/runs/background/result-watcher.ts +87 -6
- package/src/runs/background/run-status.ts +23 -1
- package/src/runs/background/scheduled-runs.ts +23 -0
- package/src/runs/background/stale-run-reconciler.ts +10 -3
- package/src/runs/background/subagent-runner.ts +75 -33
- package/src/runs/foreground/async-dismiss-action.ts +85 -0
- package/src/runs/foreground/async-steering-action.ts +6 -7
- package/src/runs/foreground/chain-execution.ts +37 -2
- package/src/runs/foreground/execution.ts +90 -19
- package/src/runs/foreground/foreground-control.ts +12 -0
- package/src/runs/foreground/prompt-audit.ts +171 -0
- package/src/runs/foreground/subagent-executor.ts +648 -195
- package/src/runs/shared/acceptance.ts +13 -4
- package/src/runs/shared/llm-intent-arbiter.ts +286 -0
- package/src/runs/shared/parallel-utils.ts +2 -0
- package/src/runs/shared/pi-args.ts +44 -1
- package/src/runs/shared/run-fanout-budget.ts +280 -0
- package/src/runs/shared/single-output.ts +4 -2
- package/src/runs/shared/subagent-prompt-runtime.ts +24 -5
- package/src/runs/shared/task-intent.ts +19 -3
- package/src/runs/shared/worktree.ts +17 -5
- package/src/shared/artifacts.ts +1 -1
- package/src/shared/file-coalescer.ts +9 -0
- package/src/shared/types.ts +97 -1
- package/src/shared/utils.ts +3 -1
- package/src/tui/fleet-status.ts +7 -5
- package/src/tui/fleet.ts +225 -12
- package/src/workflows/scripted-workflow.ts +26 -6
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
2
|
import * as path from "node:path";
|
|
3
3
|
import { writeAtomicJson } from "../../shared/atomic-json.ts";
|
|
4
|
+
import { releaseActiveRunIndex, updateActiveRunIndex } from "./active-run-index.ts";
|
|
4
5
|
import { readStatus } from "../../shared/utils.ts";
|
|
5
6
|
import { DIRS, type AsyncParallelGroupStatus, type AsyncStatus, type NestedRunSummary, type SubagentRunMode } from "../../shared/types.ts";
|
|
6
7
|
import { resolveEffectiveThinking } from "../../shared/model-info.ts";
|
|
@@ -153,7 +154,7 @@ function terminalStatusFromResult(status: AsyncStatus, resultPath: string, now:
|
|
|
153
154
|
modelAttempts: child?.modelAttempts ?? step.modelAttempts,
|
|
154
155
|
};
|
|
155
156
|
});
|
|
156
|
-
|
|
157
|
+
const terminalStatus: AsyncStatus = {
|
|
157
158
|
...status,
|
|
158
159
|
state: repair.state,
|
|
159
160
|
...(status.lifecycleArtifactVersion === 3 && (!status.processTerminal || status.processTerminal.state === "pending") ? {
|
|
@@ -165,6 +166,8 @@ function terminalStatusFromResult(status: AsyncStatus, resultPath: string, now:
|
|
|
165
166
|
endedAt: status.endedAt ?? now,
|
|
166
167
|
steps,
|
|
167
168
|
};
|
|
169
|
+
delete terminalStatus.displayDismissedAt;
|
|
170
|
+
return terminalStatus;
|
|
168
171
|
}
|
|
169
172
|
|
|
170
173
|
function buildStartedStatus(asyncDir: string, startedRun: StartedRunMetadata, now: number): AsyncStatus {
|
|
@@ -258,6 +261,7 @@ function writeFailedRepair(asyncDir: string, status: AsyncStatus, resultPath: st
|
|
|
258
261
|
const repair = buildFailedRepair(status, asyncDir, now, reason);
|
|
259
262
|
writeAtomicJson(resultPath, repair.result);
|
|
260
263
|
writeAtomicJson(path.join(asyncDir, "status.json"), repair.status);
|
|
264
|
+
if (repair.status.processTerminal?.state === "observed") releaseActiveRunIndex(asyncDir);
|
|
261
265
|
appendJsonlBestEffort(path.join(asyncDir, "events.jsonl"), {
|
|
262
266
|
type: "subagent.run.repaired_stale",
|
|
263
267
|
ts: now,
|
|
@@ -339,7 +343,6 @@ export function reconcileAsyncRun(asyncDir: string, options: ReconcileAsyncRunOp
|
|
|
339
343
|
if (stepRecord.model !== undefined && typeof stepRecord.model !== "string") throw new Error(`Invalid async status file '${statusPath}': steps[${index}].model must be a string.`);
|
|
340
344
|
if (stepRecord.thinking !== undefined && typeof stepRecord.thinking !== "string") throw new Error(`Invalid async status file '${statusPath}': steps[${index}].thinking must be a string.`);
|
|
341
345
|
}
|
|
342
|
-
|
|
343
346
|
const runId = effectiveStatus.runId || path.basename(asyncDir);
|
|
344
347
|
const resultPath = path.join(options.resultsDir ?? DIRS.results, `${runId}.json`);
|
|
345
348
|
if (fs.existsSync(resultPath)) {
|
|
@@ -348,9 +351,13 @@ export function reconcileAsyncRun(asyncDir: string, options: ReconcileAsyncRunOp
|
|
|
348
351
|
: undefined;
|
|
349
352
|
if (terminalStatus) {
|
|
350
353
|
writeAtomicJson(path.join(asyncDir, "status.json"), terminalStatus);
|
|
354
|
+
if (terminalStatus.processTerminal?.state === "observed") releaseActiveRunIndex(asyncDir);
|
|
351
355
|
return { status: terminalStatus, repaired: true, resultPath, message: "Existing async result file was used to repair stale running status." };
|
|
352
356
|
}
|
|
353
|
-
return { status: effectiveStatus, repaired: false, resultPath };
|
|
357
|
+
if (effectiveStatus.displayDismissedAt === undefined) return { status: effectiveStatus, repaired: false, resultPath };
|
|
358
|
+
}
|
|
359
|
+
if (effectiveStatus.displayDismissedAt !== undefined) {
|
|
360
|
+
return { status: null, repaired: false, resultPath };
|
|
354
361
|
}
|
|
355
362
|
|
|
356
363
|
if (effectiveStatus.state !== "running" || typeof effectiveStatus.pid !== "number") {
|
|
@@ -5,6 +5,8 @@ import * as path from "node:path";
|
|
|
5
5
|
import { pathToFileURL } from "node:url";
|
|
6
6
|
import type { Message } from "@earendil-works/pi-ai";
|
|
7
7
|
import { writeAtomicJson } from "../../shared/atomic-json.ts";
|
|
8
|
+
import { createFileCoalescer } from "../../shared/file-coalescer.ts";
|
|
9
|
+
import { isActiveAsyncState, updateActiveRunIndex } from "./active-run-index.ts";
|
|
8
10
|
import { createChildTranscriptWriter, type ChildTranscriptWriter } from "../../shared/child-transcript.ts";
|
|
9
11
|
import { closeSteerInbox, consumeInterruptRequest, consumeSteerRequests, deliverInterruptRequest, deliverStopRequest, deliverTimeoutRequest, enqueueStepSteer, steerAcksDir, steerCapabilityPath, stepSteerInboxDir, watchAsyncControlInbox, type SteerAck, type SteerCapability, type SteerRequest } from "./control-channel.ts";
|
|
10
12
|
import { appendJsonl as appendRawJsonl, formatOutputArtifactContent, getArtifactPaths, writeArtifact, writeMetadata } from "../../shared/artifacts.ts";
|
|
@@ -23,11 +25,14 @@ import {
|
|
|
23
25
|
type LaunchResolvedChildExtensionsV1,
|
|
24
26
|
type RuntimeAcknowledgedChildExtensionsV1,
|
|
25
27
|
type ModelAttempt,
|
|
28
|
+
type PiWriterProcessInstanceExitV1,
|
|
29
|
+
type ProcessTreeTerminalV1,
|
|
26
30
|
type NestedRouteInfo,
|
|
27
31
|
type NestedRunSummary,
|
|
28
32
|
type ResolvedControlConfig,
|
|
29
33
|
type ResolvedTurnBudget,
|
|
30
34
|
type ResolvedToolBudget,
|
|
35
|
+
type RunFanoutBudgetDescriptor,
|
|
31
36
|
type SubagentRunMode,
|
|
32
37
|
type SubagentOutputState,
|
|
33
38
|
type UsageBudgetConfig,
|
|
@@ -66,13 +71,14 @@ import {
|
|
|
66
71
|
DEFAULT_GLOBAL_CONCURRENCY_LIMIT,
|
|
67
72
|
Semaphore,
|
|
68
73
|
} from "../shared/parallel-utils.ts";
|
|
69
|
-
import { applyThinkingSuffix, buildPiArgs, cleanupTempDir, projectLaunchResolvedChildExtensions, resolvePiLaunchToolPlan } from "../shared/pi-args.ts";
|
|
74
|
+
import { applyThinkingSuffix, buildPiArgs, cleanupTempDir, projectLaunchResolvedChildExtensions, resolvePiLaunchToolPlan, type SubagentTaskDelivery } from "../shared/pi-args.ts";
|
|
70
75
|
import { readRuntimeAcknowledgedExtensions } from "../shared/runtime-acknowledged-extensions.ts";
|
|
71
76
|
import { outputEntryFromAsyncResult, resolveOutputReferences } from "../shared/chain-outputs.ts";
|
|
72
77
|
import { createStructuredOutputRuntime, MISSING_STRUCTURED_OUTPUT_CALL_ERROR, readStructuredOutput } from "../shared/structured-output.ts";
|
|
73
78
|
import { formatProcessSignalError, isUnexplainedProcessSignal } from "../shared/process-signal.ts";
|
|
74
79
|
import { readChildToolDiagnosticError } from "../shared/tool-availability.ts";
|
|
75
80
|
import { collectDynamicResults, DynamicFanoutError, materializeDynamicParallelStep, validateDynamicCollection } from "../shared/dynamic-fanout.ts";
|
|
81
|
+
import { claimRunFanoutBatch, getRunFanoutBudgetSnapshot } from "../shared/run-fanout-budget.ts";
|
|
76
82
|
import { nestedSummaryFromAsyncStatus, projectNestedEvents, resolveNestedAsyncDir, writeNestedEvent } from "../shared/nested-events.ts";
|
|
77
83
|
import { formatModelAttemptNote, isRetryableModelFailure } from "../shared/model-fallback.ts";
|
|
78
84
|
import {
|
|
@@ -83,9 +89,10 @@ import {
|
|
|
83
89
|
waitForSubagentStartupRetry,
|
|
84
90
|
} from "../shared/subagent-startup-retry.ts";
|
|
85
91
|
import { markProcessTerminalCandidateLeaseRelease, writeProcessTerminalCandidate, type ProcessTerminalCandidate } from "./process-terminal.ts";
|
|
92
|
+
import { createOwnedProcessTreeController, type OwnedProcessTreeController } from "./owned-process-tree.ts";
|
|
86
93
|
import { createSteeringStatus, recordSteeringRequest, steeringStatus, terminalSteeringNoticeState, updateSteeringTarget } from "./steering.ts";
|
|
87
94
|
import { attachPostExitStdioGuard, trySignalChild } from "../../shared/post-exit-stdio-guard.ts";
|
|
88
|
-
import { detectSubagentError, extractTextFromContent, extractToolArgsPreview, getFinalOutput, hasEmptyTerminalAssistantResponse, readStatus } from "../../shared/utils.ts";
|
|
95
|
+
import { PROMPT_REDACTED, detectSubagentError, extractTextFromContent, extractToolArgsPreview, getFinalOutput, hasEmptyTerminalAssistantResponse, readStatus } from "../../shared/utils.ts";
|
|
89
96
|
import { evaluateCompletionMutationGuard } from "../shared/completion-guard.ts";
|
|
90
97
|
import {
|
|
91
98
|
createMutatingFailureState,
|
|
@@ -175,6 +182,7 @@ interface SubagentRunConfig {
|
|
|
175
182
|
/** Global cap on simultaneously-running subagent tasks within this run. */
|
|
176
183
|
globalConcurrencyLimit?: number;
|
|
177
184
|
capabilityCeiling?: ResolvedSubagentCapabilityCeiling;
|
|
185
|
+
runFanoutBudget?: RunFanoutBudgetDescriptor;
|
|
178
186
|
launchContractDigest?: string;
|
|
179
187
|
launchResolvedExtensions?: LaunchResolvedChildExtensionsV1;
|
|
180
188
|
runtimeAcknowledgedExtensions?: RuntimeAcknowledgedChildExtensionsV1;
|
|
@@ -230,7 +238,7 @@ interface StepResult {
|
|
|
230
238
|
structuredOutputSchemaPath?: string;
|
|
231
239
|
acceptance?: import("../../shared/types.ts").AcceptanceLedger;
|
|
232
240
|
watchdog?: import("../../shared/types.ts").ChildWatchdogProgress;
|
|
233
|
-
writerProcesses?:
|
|
241
|
+
writerProcesses?: PiWriterProcessInstanceExitV1[];
|
|
234
242
|
writerAttemptCount?: number;
|
|
235
243
|
runner?: ExternalCliRunnerStatus;
|
|
236
244
|
externalProcess?: ExternalProcessStatus;
|
|
@@ -510,6 +518,7 @@ interface RunPiStreamingResult {
|
|
|
510
518
|
processInstanceId: string;
|
|
511
519
|
processCloseObservedAt?: number;
|
|
512
520
|
processSignal?: string | null;
|
|
521
|
+
processTree: ProcessTreeTerminalV1;
|
|
513
522
|
}
|
|
514
523
|
|
|
515
524
|
function runPiStreaming(
|
|
@@ -546,7 +555,9 @@ function runPiStreaming(
|
|
|
546
555
|
stdio: ["ignore", "pipe", "pipe"],
|
|
547
556
|
env: spawnEnv,
|
|
548
557
|
windowsHide: true,
|
|
558
|
+
detached: process.platform !== "win32",
|
|
549
559
|
});
|
|
560
|
+
let processTreeController: OwnedProcessTreeController | undefined;
|
|
550
561
|
const stderrTail = createBoundedByteTail();
|
|
551
562
|
const rawStdoutTail = createBoundedByteTail();
|
|
552
563
|
const messages: Message[] = [];
|
|
@@ -554,6 +565,7 @@ function runPiStreaming(
|
|
|
554
565
|
let model: string | undefined;
|
|
555
566
|
let writerRegistrationError: string | undefined;
|
|
556
567
|
if (typeof child.pid === "number") {
|
|
568
|
+
processTreeController = createOwnedProcessTreeController(child.pid);
|
|
557
569
|
try {
|
|
558
570
|
onWriterProcess?.({ state: "running", pid: child.pid });
|
|
559
571
|
} catch (writerError) {
|
|
@@ -701,7 +713,6 @@ function runPiStreaming(
|
|
|
701
713
|
// a lingering stdio holder after `exit`, or a child that never exits.
|
|
702
714
|
const FINAL_STOP_GRACE_MS = 1000;
|
|
703
715
|
const HARD_KILL_MS = 3000;
|
|
704
|
-
const TIMEOUT_HARD_KILL_MS = 3000;
|
|
705
716
|
let childExited = false;
|
|
706
717
|
let forcedTerminationSignal = false;
|
|
707
718
|
let cleanTerminalAssistantStopReceived = false;
|
|
@@ -709,7 +720,6 @@ function runPiStreaming(
|
|
|
709
720
|
let finalDrainTimer: NodeJS.Timeout | undefined;
|
|
710
721
|
let finalHardKillTimer: NodeJS.Timeout | undefined;
|
|
711
722
|
let watchdogTailTimer: NodeJS.Timeout | undefined;
|
|
712
|
-
let timeoutHardKillTimer: NodeJS.Timeout | undefined;
|
|
713
723
|
let turnBudgetTerminationTimer: NodeJS.Timeout | undefined;
|
|
714
724
|
let turnBudgetHardKillTimer: NodeJS.Timeout | undefined;
|
|
715
725
|
let protocolHardKillTimer: NodeJS.Timeout | undefined;
|
|
@@ -774,22 +784,16 @@ function runPiStreaming(
|
|
|
774
784
|
timedOut = true;
|
|
775
785
|
interrupted = false;
|
|
776
786
|
error = timeoutMessage ?? "Subagent timed out.";
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
if (!settled) trySignalChild(child, "SIGKILL");
|
|
780
|
-
}, TIMEOUT_HARD_KILL_MS);
|
|
781
|
-
timeoutHardKillTimer.unref?.();
|
|
787
|
+
if (processTreeController) void processTreeController.terminate();
|
|
788
|
+
else trySignalChild(child, "SIGTERM");
|
|
782
789
|
});
|
|
783
790
|
registerStop?.(() => {
|
|
784
791
|
if (settled || timedOut || stopped) return;
|
|
785
792
|
stopped = true;
|
|
786
793
|
interrupted = false;
|
|
787
794
|
error = stopMessage ?? "Subagent stopped by user.";
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
if (!settled) trySignalChild(child, "SIGKILL");
|
|
791
|
-
}, TIMEOUT_HARD_KILL_MS);
|
|
792
|
-
timeoutHardKillTimer.unref?.();
|
|
795
|
+
if (processTreeController) void processTreeController.terminate();
|
|
796
|
+
else trySignalChild(child, "SIGTERM");
|
|
793
797
|
});
|
|
794
798
|
registerTurnBudgetAbort?.((message, state) => {
|
|
795
799
|
if (settled || timedOut || stopped || turnBudgetExceeded) return;
|
|
@@ -818,10 +822,6 @@ function runPiStreaming(
|
|
|
818
822
|
finalHardKillTimer = undefined;
|
|
819
823
|
}
|
|
820
824
|
clearWatchdogTailTimer();
|
|
821
|
-
if (timeoutHardKillTimer) {
|
|
822
|
-
clearTimeout(timeoutHardKillTimer);
|
|
823
|
-
timeoutHardKillTimer = undefined;
|
|
824
|
-
}
|
|
825
825
|
if (turnBudgetTerminationTimer) {
|
|
826
826
|
clearTimeout(turnBudgetTerminationTimer);
|
|
827
827
|
turnBudgetTerminationTimer = undefined;
|
|
@@ -883,9 +883,12 @@ function runPiStreaming(
|
|
|
883
883
|
childExited = true;
|
|
884
884
|
clearDrainTimers();
|
|
885
885
|
});
|
|
886
|
-
child.on("close", (exitCode, signal) => {
|
|
886
|
+
child.on("close", async (exitCode, signal) => {
|
|
887
887
|
settled = true;
|
|
888
888
|
const processCloseObservedAt = Date.now();
|
|
889
|
+
const processTree = processTreeController
|
|
890
|
+
? await processTreeController.finishAfterWriterClose()
|
|
891
|
+
: { state: "unknown" as const, reason: "verification-failed" as const, diagnostic: "Writer PID was unavailable." };
|
|
889
892
|
try {
|
|
890
893
|
onWriterProcess?.({ state: "none" });
|
|
891
894
|
} catch {
|
|
@@ -937,6 +940,7 @@ function runPiStreaming(
|
|
|
937
940
|
processInstanceId,
|
|
938
941
|
processCloseObservedAt,
|
|
939
942
|
processSignal: signal,
|
|
943
|
+
processTree,
|
|
940
944
|
}));
|
|
941
945
|
});
|
|
942
946
|
|
|
@@ -959,7 +963,7 @@ function runPiStreaming(
|
|
|
959
963
|
const stderr = stderrTail.text();
|
|
960
964
|
const finalOutput = getFinalOutput(messages) || rawStdoutTail.text().trim();
|
|
961
965
|
const spawnErrorMessage = spawnError instanceof Error ? spawnError.message : String(spawnError);
|
|
962
|
-
resolve(omitUndefinedProperties({ stderr, exitCode: 1, messages, usage, toolCount, durationMs: Date.now() - startedAt, model, error: stopped ? (stopMessage ?? "Subagent stopped by user.") : timedOut ? (timeoutMessage ?? "Subagent timed out.") : turnBudgetExceeded ? turnBudgetMessage : error ?? assistantError ?? spawnErrorMessage, protocolError, finalOutput: (timedOut || stopped) && !finalOutput.trim() ? (stopped ? stopMessage ?? "Subagent stopped by user." : timeoutMessage ?? "Subagent timed out.") : finalOutput, outputState: finalOutput.trim() ? "present" : "absent", timedOut, stopped, turnBudget, turnBudgetExceeded, wrapUpRequested: turnBudget?.outcome === "wrap-up-requested" || turnBudget?.outcome === "termination-deferred" || turnBudgetExceeded || undefined, observedMutationAttempt, structuredOutputToolInvoked, structuredOutputMessageStartIndex, watchdog: childWatchdogState, processInstanceId }));
|
|
966
|
+
resolve(omitUndefinedProperties({ stderr, exitCode: 1, messages, usage, toolCount, durationMs: Date.now() - startedAt, model, error: stopped ? (stopMessage ?? "Subagent stopped by user.") : timedOut ? (timeoutMessage ?? "Subagent timed out.") : turnBudgetExceeded ? turnBudgetMessage : error ?? assistantError ?? spawnErrorMessage, protocolError, finalOutput: (timedOut || stopped) && !finalOutput.trim() ? (stopped ? stopMessage ?? "Subagent stopped by user." : timeoutMessage ?? "Subagent timed out.") : finalOutput, outputState: finalOutput.trim() ? "present" : "absent", timedOut, stopped, turnBudget, turnBudgetExceeded, wrapUpRequested: turnBudget?.outcome === "wrap-up-requested" || turnBudget?.outcome === "termination-deferred" || turnBudgetExceeded || undefined, observedMutationAttempt, structuredOutputToolInvoked, structuredOutputMessageStartIndex, watchdog: childWatchdogState, processInstanceId, processTree: { state: "unknown", reason: "verification-failed", diagnostic: spawnErrorMessage } }));
|
|
963
967
|
});
|
|
964
968
|
});
|
|
965
969
|
}
|
|
@@ -1103,6 +1107,7 @@ interface SingleStepContext {
|
|
|
1103
1107
|
orchestratorIntercomTarget?: string;
|
|
1104
1108
|
nestedRoute?: NestedRouteInfo;
|
|
1105
1109
|
capabilityCeiling?: ResolvedSubagentCapabilityCeiling;
|
|
1110
|
+
runFanoutBudget?: RunFanoutBudgetDescriptor;
|
|
1106
1111
|
onAttemptStart?: (attempt: { model?: string; thinking?: string }) => void;
|
|
1107
1112
|
onChildEvent?: (event: ChildEvent) => void;
|
|
1108
1113
|
onWriterProcess?: (writer: { state: "none" | "spawning" } | { state: "running"; pid: number }) => void;
|
|
@@ -1204,7 +1209,7 @@ async function runSingleStep(
|
|
|
1204
1209
|
artifactPaths = getArtifactPaths(ctx.artifactsDir, ctx.id, step.agent, index);
|
|
1205
1210
|
fs.mkdirSync(ctx.artifactsDir, { recursive: true });
|
|
1206
1211
|
if (ctx.artifactConfig?.includeInput !== false) {
|
|
1207
|
-
fs.writeFileSync(artifactPaths.inputPath, `# Task for ${step.agent}\n\n${
|
|
1212
|
+
fs.writeFileSync(artifactPaths.inputPath, `# Task for ${step.agent}\n\n${PROMPT_REDACTED}; live Prompt Audit only.\n`, "utf-8");
|
|
1208
1213
|
}
|
|
1209
1214
|
if (ctx.artifactConfig?.includeTranscript !== false) {
|
|
1210
1215
|
transcriptWriter = createChildTranscriptWriter({
|
|
@@ -1217,7 +1222,7 @@ async function runSingleStep(
|
|
|
1217
1222
|
});
|
|
1218
1223
|
}
|
|
1219
1224
|
}
|
|
1220
|
-
transcriptWriter?.writeInitialUserMessage(
|
|
1225
|
+
transcriptWriter?.writeInitialUserMessage(`${PROMPT_REDACTED}; live Prompt Audit only.`);
|
|
1221
1226
|
|
|
1222
1227
|
if (step.runner?.type === "external-cli") {
|
|
1223
1228
|
const runner: ExternalCliRunnerStatus = {
|
|
@@ -1260,7 +1265,7 @@ async function runSingleStep(
|
|
|
1260
1265
|
artifactPaths,
|
|
1261
1266
|
artifactConfig: ctx.artifactConfig,
|
|
1262
1267
|
output: formatOutputArtifactContent(omitUndefinedProperties({ output: resolvedOutput.fullOutput, error: external.error, metadataPath: ctx.artifactConfig?.includeMetadata === false ? undefined : artifactPaths.metadataPath })),
|
|
1263
|
-
metadata: { runId: ctx.id, agent: step.agent, task, runner, externalProcess: external.externalProcess, exitCode: external.exitCode, error: external.error, timestamp: Date.now() },
|
|
1268
|
+
metadata: { runId: ctx.id, agent: step.agent, task: PROMPT_REDACTED, runner, externalProcess: external.externalProcess, exitCode: external.exitCode, error: external.error, timestamp: Date.now() },
|
|
1264
1269
|
})
|
|
1265
1270
|
: {};
|
|
1266
1271
|
return omitUndefinedProperties({
|
|
@@ -1290,7 +1295,7 @@ async function runSingleStep(
|
|
|
1290
1295
|
let capabilityAudit: import("../shared/capability-ceiling.ts").SubagentCapabilityAudit | undefined;
|
|
1291
1296
|
let launchResolvedExtensions = step.launchResolvedExtensions;
|
|
1292
1297
|
const modelAttempts: ModelAttempt[] = [];
|
|
1293
|
-
const writerProcesses:
|
|
1298
|
+
const writerProcesses: PiWriterProcessInstanceExitV1[] = [];
|
|
1294
1299
|
let writerAttemptCount = 0;
|
|
1295
1300
|
const attemptNotes: string[] = [];
|
|
1296
1301
|
const eventsPath = path.join(path.dirname(ctx.outputFile), "events.jsonl");
|
|
@@ -1304,6 +1309,9 @@ async function runSingleStep(
|
|
|
1304
1309
|
|
|
1305
1310
|
let modelIndex = 0;
|
|
1306
1311
|
let startupAttemptIndex = 0;
|
|
1312
|
+
// Escalated to "file" after an unexplained zero-activity startup failure so
|
|
1313
|
+
// retries keep the task text out of argv (endpoint pre-exec scans may deny it).
|
|
1314
|
+
let taskDeliveryOverride: SubagentTaskDelivery | undefined;
|
|
1307
1315
|
modelAttemptsLoop: while (modelIndex < candidates.length) {
|
|
1308
1316
|
if (ctx.timeoutSignal?.aborted || ctx.stopSignal?.aborted || ctx.skipAcceptance?.()) break;
|
|
1309
1317
|
const candidate = candidates[modelIndex];
|
|
@@ -1329,6 +1337,7 @@ async function runSingleStep(
|
|
|
1329
1337
|
parentSessionId: step.parentSessionId,
|
|
1330
1338
|
baseArgs: ["--mode", "json", "-p"],
|
|
1331
1339
|
task,
|
|
1340
|
+
taskDelivery: taskDeliveryOverride,
|
|
1332
1341
|
sessionEnabled,
|
|
1333
1342
|
sessionDir,
|
|
1334
1343
|
sessionFile: step.sessionFile,
|
|
@@ -1354,6 +1363,10 @@ async function runSingleStep(
|
|
|
1354
1363
|
parentControlInbox: ctx.nestedRoute?.controlInbox,
|
|
1355
1364
|
parentRootRunId: ctx.nestedRoute?.rootRunId,
|
|
1356
1365
|
parentCapabilityToken: ctx.nestedRoute?.capabilityToken,
|
|
1366
|
+
runFanoutBudget: ctx.runFanoutBudget ? {
|
|
1367
|
+
...ctx.runFanoutBudget,
|
|
1368
|
+
...(step.runFanoutPath ? { parentPath: `${ctx.runFanoutBudget.parentPath ? `${ctx.runFanoutBudget.parentPath}/` : ""}${step.runFanoutPath}` } : {}),
|
|
1369
|
+
} : undefined,
|
|
1357
1370
|
steerInboxDir: ctx.steerInboxDir,
|
|
1358
1371
|
steerCapabilityPath: ctx.steerCapabilityPath,
|
|
1359
1372
|
steerAckDir: ctx.steerAckDir,
|
|
@@ -1427,6 +1440,7 @@ async function runSingleStep(
|
|
|
1427
1440
|
closeObservedAt: run.processCloseObservedAt,
|
|
1428
1441
|
exitCode: run.exitCode,
|
|
1429
1442
|
signal: run.processSignal ?? null,
|
|
1443
|
+
processTree: run.processTree,
|
|
1430
1444
|
});
|
|
1431
1445
|
}
|
|
1432
1446
|
if (run.turnBudget) turnBudget = run.turnBudget;
|
|
@@ -1581,6 +1595,10 @@ async function runSingleStep(
|
|
|
1581
1595
|
});
|
|
1582
1596
|
const shouldRetry = await waitForSubagentStartupRetry(retryDelayMs, [ctx.timeoutSignal, ctx.stopSignal]);
|
|
1583
1597
|
if (!shouldRetry || ctx.skipAcceptance?.()) break modelAttemptsLoop;
|
|
1598
|
+
if (!taskDeliveryOverride && run.processSignal === "SIGKILL") {
|
|
1599
|
+
taskDeliveryOverride = "file";
|
|
1600
|
+
attemptNotes.push("[startup-retry] retrying with file task delivery to keep the task text out of the child process argv.");
|
|
1601
|
+
}
|
|
1584
1602
|
attempt.error = retryNote;
|
|
1585
1603
|
attemptNotes.push(retryNote);
|
|
1586
1604
|
startupAttemptIndex += 1;
|
|
@@ -1698,7 +1716,7 @@ async function runSingleStep(
|
|
|
1698
1716
|
metadata: {
|
|
1699
1717
|
runId: ctx.id,
|
|
1700
1718
|
agent: step.agent,
|
|
1701
|
-
task,
|
|
1719
|
+
task: PROMPT_REDACTED,
|
|
1702
1720
|
exitCode: effectiveFinalExitCode,
|
|
1703
1721
|
model: finalResult?.model,
|
|
1704
1722
|
attemptedModels: attemptedModels.length > 0 ? attemptedModels : undefined,
|
|
@@ -2169,6 +2187,7 @@ async function runSubagent(
|
|
|
2169
2187
|
...(config.launchContractDigest ? { launchContractDigest: config.launchContractDigest } : {}),
|
|
2170
2188
|
...(config.launchResolvedExtensions ? { launchResolvedExtensions: config.launchResolvedExtensions } : {}),
|
|
2171
2189
|
...(config.capabilityCeiling ? { capabilityCeiling: config.capabilityCeiling } : {}),
|
|
2190
|
+
...(config.runFanoutBudget ? { runFanoutBudget: getRunFanoutBudgetSnapshot(config.runFanoutBudget) } : {}),
|
|
2172
2191
|
...(config.parentWorkflowRunId ? { parentWorkflowRunId: config.parentWorkflowRunId } : {}),
|
|
2173
2192
|
...(config.workflowKey ? { workflowKey: config.workflowKey } : {}),
|
|
2174
2193
|
...(config.runnerProcessInstanceId ? { processTerminal: { version: 1 as const, state: "pending" as const, runId: id, runnerProcessInstanceId: config.runnerProcessInstanceId } } : {}),
|
|
@@ -2180,6 +2199,7 @@ async function runSubagent(
|
|
|
2180
2199
|
|
|
2181
2200
|
fs.mkdirSync(asyncDir, { recursive: true });
|
|
2182
2201
|
writeAtomicJson(statusPath, statusPayload);
|
|
2202
|
+
updateActiveRunIndex(asyncDir, statusPayload.state);
|
|
2183
2203
|
let pendingParallelUsageCost: CostSummary = { inputTokens: 0, outputTokens: 0, costUsd: 0 };
|
|
2184
2204
|
const currentUsageTotals = (): CostSummary => {
|
|
2185
2205
|
const cost = results.reduce<CostSummary>((sum, result) => ({
|
|
@@ -2252,11 +2272,25 @@ async function runSubagent(
|
|
|
2252
2272
|
for (const node of graph.nodes) updateNode(node);
|
|
2253
2273
|
statusPayload.workflowGraph = graph;
|
|
2254
2274
|
};
|
|
2255
|
-
|
|
2275
|
+
let lastIndexedActiveState = isActiveAsyncState(statusPayload.state);
|
|
2276
|
+
const writeStatusPayloadNow = (): void => {
|
|
2256
2277
|
refreshWorkflowGraph();
|
|
2257
2278
|
writeAtomicJson(statusPath, statusPayload);
|
|
2279
|
+
const activeState = isActiveAsyncState(statusPayload.state);
|
|
2280
|
+
if (activeState && activeState !== lastIndexedActiveState) {
|
|
2281
|
+
updateActiveRunIndex(asyncDir, statusPayload.state);
|
|
2282
|
+
}
|
|
2283
|
+
lastIndexedActiveState = activeState;
|
|
2258
2284
|
emitNestedSelfEvent(statusPayload.state === "running" || statusPayload.state === "queued" ? "subagent.nested.updated" : "subagent.nested.completed");
|
|
2259
2285
|
};
|
|
2286
|
+
const statusWriteCoalescer = createFileCoalescer(writeStatusPayloadNow, 100);
|
|
2287
|
+
const writeStatusPayload = (immediate = true): void => {
|
|
2288
|
+
if (immediate || statusPayload.state !== "running" || statusPayload.activityState !== undefined) {
|
|
2289
|
+
if (!statusWriteCoalescer.flush(statusPath)) writeStatusPayloadNow();
|
|
2290
|
+
return;
|
|
2291
|
+
}
|
|
2292
|
+
statusWriteCoalescer.schedule(statusPath);
|
|
2293
|
+
};
|
|
2260
2294
|
const updateExternalProcess = (index: number, process: ExternalProcessStatus): void => {
|
|
2261
2295
|
requiredStatusStep(statusPayload, index).externalProcess = process;
|
|
2262
2296
|
statusPayload.lastUpdate = Date.now();
|
|
@@ -2783,7 +2817,7 @@ async function runSubagent(
|
|
|
2783
2817
|
step.lastActivityAt = now;
|
|
2784
2818
|
statusPayload.lastActivityAt = now;
|
|
2785
2819
|
statusPayload.lastUpdate = now;
|
|
2786
|
-
writeStatusPayload();
|
|
2820
|
+
writeStatusPayload(false);
|
|
2787
2821
|
return;
|
|
2788
2822
|
}
|
|
2789
2823
|
if (event.type === "tool_execution_start" && event.toolName) {
|
|
@@ -2919,7 +2953,7 @@ async function runSubagent(
|
|
|
2919
2953
|
statusPayload.lastActivityAt = now;
|
|
2920
2954
|
statusPayload.lastUpdate = now;
|
|
2921
2955
|
maybeEmitActiveLongRunning(flatIndex, now);
|
|
2922
|
-
writeStatusPayload();
|
|
2956
|
+
writeStatusPayload(false);
|
|
2923
2957
|
};
|
|
2924
2958
|
const updateRunnerActivityState = (now: number): boolean => {
|
|
2925
2959
|
if (!controlConfig.enabled) return false;
|
|
@@ -3018,7 +3052,6 @@ async function runSubagent(
|
|
|
3018
3052
|
if (stopped || timedOut || interrupted || statusPayload.state !== "running") return;
|
|
3019
3053
|
stopped = true;
|
|
3020
3054
|
const now = Date.now();
|
|
3021
|
-
statusPayload.state = "stopped";
|
|
3022
3055
|
statusPayload.stopped = true;
|
|
3023
3056
|
statusPayload.error = stopMessage;
|
|
3024
3057
|
currentActivityState = undefined;
|
|
@@ -3051,7 +3084,6 @@ async function runSubagent(
|
|
|
3051
3084
|
timedOut = true;
|
|
3052
3085
|
const now = Date.now();
|
|
3053
3086
|
const message = timeoutMessage ?? "Subagent timed out.";
|
|
3054
|
-
statusPayload.state = "failed";
|
|
3055
3087
|
statusPayload.timedOut = true;
|
|
3056
3088
|
statusPayload.error = message;
|
|
3057
3089
|
currentActivityState = undefined;
|
|
@@ -3241,6 +3273,9 @@ async function runSubagent(
|
|
|
3241
3273
|
throw new DynamicFanoutError(`Dynamic chain step ${stepIndex + 1} materialized ${materialized.parallel.length} items that resolve output to the same path: ${step.parallel.outputPath}. Remove the explicit output path or use an inherited relative agent output so each item can be isolated.`);
|
|
3242
3274
|
}
|
|
3243
3275
|
if (materialized.collectedOnEmpty) await validateDynamicCollection(step.collect.outputSchema, materialized.collectedOnEmpty);
|
|
3276
|
+
if (!config.runFanoutBudget) throw new Error("Async runner is missing its run fan-out budget identity.");
|
|
3277
|
+
const runFanoutBudget = claimRunFanoutBatch(config.runFanoutBudget, materialized.parallel.map((_, itemIndex) => `chain[${stepIndex}].expand[${itemIndex}]`));
|
|
3278
|
+
statusPayload.runFanoutBudget = runFanoutBudget;
|
|
3244
3279
|
} catch (error) {
|
|
3245
3280
|
const now = Date.now();
|
|
3246
3281
|
const message = error instanceof DynamicFanoutError ? error.message : error instanceof Error ? error.message : String(error);
|
|
@@ -3347,6 +3382,7 @@ async function runSubagent(
|
|
|
3347
3382
|
const materializedTask = step.parallel.namespaceOutputPath ? injectSingleOutputInstruction(taskText, outputPath, step.parallel) : taskText;
|
|
3348
3383
|
return omitUndefinedProperties({
|
|
3349
3384
|
...step.parallel,
|
|
3385
|
+
runFanoutPath: `chain[${stepIndex}].expand[${itemIndex}]`,
|
|
3350
3386
|
task: materializedTask,
|
|
3351
3387
|
effectiveAcceptance: resolveEffectiveAcceptance(omitUndefinedProperties({
|
|
3352
3388
|
explicit: step.parallel.acceptanceInput,
|
|
@@ -3506,6 +3542,7 @@ async function runSubagent(
|
|
|
3506
3542
|
orchestratorIntercomTarget: config.controlIntercomTarget,
|
|
3507
3543
|
nestedRoute: config.nestedRoute,
|
|
3508
3544
|
capabilityCeiling: config.capabilityCeiling,
|
|
3545
|
+
runFanoutBudget: config.runFanoutBudget,
|
|
3509
3546
|
registerInterrupt: (interrupt) => registerStepInterrupt(fi, interrupt),
|
|
3510
3547
|
registerTimeout: (interrupt) => registerStepTimeout(fi, interrupt),
|
|
3511
3548
|
registerStop: (stop) => registerStepStop(fi, stop),
|
|
@@ -3888,6 +3925,7 @@ async function runSubagent(
|
|
|
3888
3925
|
orchestratorIntercomTarget: config.controlIntercomTarget,
|
|
3889
3926
|
nestedRoute: config.nestedRoute,
|
|
3890
3927
|
capabilityCeiling: config.capabilityCeiling,
|
|
3928
|
+
runFanoutBudget: config.runFanoutBudget,
|
|
3891
3929
|
registerInterrupt: (interrupt) => registerStepInterrupt(fi, interrupt),
|
|
3892
3930
|
registerTimeout: (interrupt) => registerStepTimeout(fi, interrupt),
|
|
3893
3931
|
registerStop: (stop) => registerStepStop(fi, stop),
|
|
@@ -4177,6 +4215,7 @@ async function runSubagent(
|
|
|
4177
4215
|
orchestratorIntercomTarget: config.controlIntercomTarget,
|
|
4178
4216
|
nestedRoute: config.nestedRoute,
|
|
4179
4217
|
capabilityCeiling: config.capabilityCeiling,
|
|
4218
|
+
runFanoutBudget: config.runFanoutBudget,
|
|
4180
4219
|
registerInterrupt: (interrupt) => registerStepInterrupt(flatIndex, interrupt),
|
|
4181
4220
|
registerTimeout: (interrupt) => registerStepTimeout(flatIndex, interrupt),
|
|
4182
4221
|
registerStop: (stop) => registerStepStop(flatIndex, stop),
|
|
@@ -4421,6 +4460,9 @@ async function runSubagent(
|
|
|
4421
4460
|
clearTimeout(timeoutTimer);
|
|
4422
4461
|
timeoutTimer = undefined;
|
|
4423
4462
|
}
|
|
4463
|
+
if (!timedOut && !stopped && !interrupted && config.timeoutMs !== undefined && results.some((result) => result.timedOut === true && result.error === timeoutMessage)) {
|
|
4464
|
+
timedOut = true;
|
|
4465
|
+
}
|
|
4424
4466
|
const signalTerminated = !stopped && !timedOut && !turnBudgetExceeded && !interrupted && results.some((result) => result.exitCode !== 0 && isUnexplainedProcessSignal(omitUndefinedProperties({
|
|
4425
4467
|
processSignal: result.processSignal,
|
|
4426
4468
|
interrupted: result.interrupted,
|
|
@@ -4480,6 +4522,7 @@ async function runSubagent(
|
|
|
4480
4522
|
statusPayload.error = `Step failed: ${failedStep.agent}`;
|
|
4481
4523
|
}
|
|
4482
4524
|
}
|
|
4525
|
+
writeStatusPayload();
|
|
4483
4526
|
try {
|
|
4484
4527
|
writeAtomicJson(resultPath, {
|
|
4485
4528
|
lifecycleArtifactVersion: SUBAGENT_LIFECYCLE_ARTIFACT_VERSION,
|
|
@@ -4612,7 +4655,7 @@ async function runSubagent(
|
|
|
4612
4655
|
shareError,
|
|
4613
4656
|
}));
|
|
4614
4657
|
if (config.runnerProcessInstanceId) {
|
|
4615
|
-
const writers: Record<string,
|
|
4658
|
+
const writers: Record<string, PiWriterProcessInstanceExitV1[]> = {};
|
|
4616
4659
|
const expectedWriters: Record<string, number> = {};
|
|
4617
4660
|
for (const [index, result] of results.entries()) {
|
|
4618
4661
|
writers[String(index)] = result.writerProcesses ?? [];
|
|
@@ -4633,7 +4676,6 @@ async function runSubagent(
|
|
|
4633
4676
|
console.error(`Failed to write process-terminal candidate for '${id}':`, error);
|
|
4634
4677
|
}
|
|
4635
4678
|
}
|
|
4636
|
-
writeStatusPayload();
|
|
4637
4679
|
}
|
|
4638
4680
|
|
|
4639
4681
|
async function waitForStartupControl(
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import type { AgentToolResult } from "@earendil-works/pi-agent-core";
|
|
4
|
+
import { writeAtomicJson } from "../../shared/atomic-json.ts";
|
|
5
|
+
import { DIRS, type Details, type SubagentState } from "../../shared/types.ts";
|
|
6
|
+
import { readStatus } from "../../shared/utils.ts";
|
|
7
|
+
import { updateActiveRunIndex } from "../background/active-run-index.ts";
|
|
8
|
+
import { reconcileAsyncRun } from "../background/stale-run-reconciler.ts";
|
|
9
|
+
|
|
10
|
+
export function dismissRecoveredWorkflow(
|
|
11
|
+
state: SubagentState,
|
|
12
|
+
location: { asyncDir: string | null; resolvedId?: string },
|
|
13
|
+
): AgentToolResult<Details> {
|
|
14
|
+
const asyncDir = location.asyncDir;
|
|
15
|
+
const runId = location.resolvedId ?? (asyncDir ? path.basename(asyncDir) : "requested");
|
|
16
|
+
if (!asyncDir) {
|
|
17
|
+
return {
|
|
18
|
+
content: [{ type: "text", text: `Recovered workflow '${runId}' has no disk status to dismiss.` }],
|
|
19
|
+
isError: true,
|
|
20
|
+
details: { mode: "management", results: [] },
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
const status = readStatus(asyncDir);
|
|
24
|
+
if (!status || status.mode !== "workflow") {
|
|
25
|
+
return {
|
|
26
|
+
content: [{ type: "text", text: `Run '${runId}' is not a recovered workflow.` }],
|
|
27
|
+
isError: true,
|
|
28
|
+
details: { mode: "management", results: [] },
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
if (!state.currentSessionId || status.sessionId !== state.currentSessionId) {
|
|
32
|
+
return {
|
|
33
|
+
content: [{ type: "text", text: `Recovered workflow '${runId}' was not found in the active session.` }],
|
|
34
|
+
isError: true,
|
|
35
|
+
details: { mode: "management", results: [] },
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
if (state.workflowControllers?.has(status.runId) || state.workflowControllers?.has(runId)) {
|
|
39
|
+
return {
|
|
40
|
+
content: [{ type: "text", text: `Workflow '${runId}' still has a live controller and cannot be dismissed.` }],
|
|
41
|
+
isError: true,
|
|
42
|
+
details: { mode: "management", results: [] },
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
if (status.state !== "running") {
|
|
46
|
+
return {
|
|
47
|
+
content: [{ type: "text", text: `Recovered workflow '${runId}' is ${status.state}, not running.` }],
|
|
48
|
+
isError: true,
|
|
49
|
+
details: { mode: "management", results: [] },
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
let latestStatus = status;
|
|
53
|
+
const resultPath = path.join(DIRS.results, `${status.runId}.json`);
|
|
54
|
+
if (fs.existsSync(resultPath)) {
|
|
55
|
+
const reconciled = reconcileAsyncRun(asyncDir).status;
|
|
56
|
+
if (reconciled && reconciled.state !== "running") {
|
|
57
|
+
return {
|
|
58
|
+
content: [{ type: "text", text: `Recovered workflow '${runId}' is ${reconciled.state}, not running.` }],
|
|
59
|
+
isError: true,
|
|
60
|
+
details: { mode: "management", results: [] },
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
if (reconciled) latestStatus = reconciled;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const dismissed = { ...latestStatus, displayDismissedAt: Date.now() };
|
|
67
|
+
writeAtomicJson(path.join(asyncDir, "status.json"), dismissed);
|
|
68
|
+
const repaired = reconcileAsyncRun(asyncDir).status;
|
|
69
|
+
if (repaired && repaired.state !== "running") {
|
|
70
|
+
return {
|
|
71
|
+
content: [{ type: "text", text: `Recovered workflow '${runId}' is ${repaired.state}, not running.` }],
|
|
72
|
+
isError: true,
|
|
73
|
+
details: { mode: "management", results: [] },
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
updateActiveRunIndex(asyncDir, "complete");
|
|
77
|
+
state.asyncJobs.delete(status.runId);
|
|
78
|
+
state.asyncJobs.delete(runId);
|
|
79
|
+
state.fleetJobs?.delete(status.runId);
|
|
80
|
+
state.fleetJobs?.delete(runId);
|
|
81
|
+
return {
|
|
82
|
+
content: [{ type: "text", text: `Dismissed recovered workflow ${status.runId} from the display. No running work was terminated.` }],
|
|
83
|
+
details: { mode: "management", results: [] },
|
|
84
|
+
};
|
|
85
|
+
}
|
|
@@ -219,15 +219,14 @@ export async function steerAsyncRun(input: {
|
|
|
219
219
|
const limits = remainingSteeringRecoveryLimits(recoveryTarget.recoveryDescriptor, paused);
|
|
220
220
|
const revived = await input.recover(limits);
|
|
221
221
|
if (revived.isError || !revived.details.asyncId) throw new Error(revived.content[0]?.type === "text" ? revived.content[0].text : "Replacement launch failed; source run remains paused.");
|
|
222
|
-
const
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
const stepSteering = sourceStatus.steps?.[targetIndex]?.steering;
|
|
222
|
+
const targetIndex = input.index ?? paused.steering?.recent.find((request) => request.id === requestId)?.targets[0]?.index ?? status.steps?.findIndex((step) => step.status === "running") ?? -1;
|
|
223
|
+
if (paused.steering && targetIndex >= 0) {
|
|
224
|
+
updateSteeringTarget(paused.steering, requestId, targetIndex, "recovered", Date.now(), { replacementRunId: revived.details.asyncId });
|
|
225
|
+
const stepSteering = paused.steps?.[targetIndex]?.steering;
|
|
227
226
|
if (stepSteering) updateSteeringTarget(stepSteering, requestId, targetIndex, "recovered", Date.now(), { replacementRunId: revived.details.asyncId });
|
|
228
|
-
writeAtomicJson(path.join(asyncDir, "status.json"),
|
|
227
|
+
writeAtomicJson(path.join(asyncDir, "status.json"), paused);
|
|
229
228
|
}
|
|
230
|
-
const recovered =
|
|
229
|
+
const recovered = paused.steering ? actionResultFromSteeringStatus(paused.steering, status.runId, requestId, revived.details.asyncId) : undefined;
|
|
231
230
|
appendSteeringNotice("recovered", `Steering recovered for run ${status.runId}; replacement ${revived.details.asyncId} launched.`);
|
|
232
231
|
return { content: [{ type: "text", text: `Steering recovered for async run ${status.runId}; replacement ${revived.details.asyncId} launched after the source paused.` }], details: { mode: "management", results: [], steering: recovered ?? { requestId, state: "recovered", sourceRunId: status.runId, replacementRunId: revived.details.asyncId, deliveryStatus: "delivered", targets: [{ index: input.index ?? 0, state: "recovered" }] } } };
|
|
233
232
|
} catch (error) {
|