pi-subagents 0.55.0 → 0.57.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 +81 -3
- package/agents/claude-code-writer.md +15 -0
- package/agents/claude-code.md +15 -0
- package/agents/codex-exec-writer.md +15 -0
- package/agents/codex-exec.md +15 -0
- package/agents/cursor-agent-writer.md +14 -0
- package/agents/cursor-agent.md +14 -0
- package/docs/agents.md +111 -15
- package/docs/configuration.md +37 -0
- package/docs/extension-api.md +2 -2
- package/docs/models.md +6 -0
- package/docs/observability.md +8 -7
- package/docs/tool-reference.md +11 -1
- package/docs/workflows.md +20 -0
- package/package.json +1 -1
- package/skills/pi-subagents/references/execution-controls.md +1 -1
- package/src/agents/agent-management.ts +13 -5
- package/src/agents/agent-refinements.ts +4 -4
- package/src/agents/agent-serializer.ts +2 -0
- package/src/agents/agents.ts +242 -49
- package/src/agents/builtin-names.ts +6 -0
- package/src/agents/runtime-agent-registry.ts +8 -2
- package/src/api/preflight.ts +22 -3
- package/src/extension/config.ts +70 -0
- package/src/extension/doctor.ts +3 -3
- package/src/extension/index.ts +18 -4
- package/src/extension/public-execution.ts +29 -13
- package/src/extension/rpc.ts +20 -5
- package/src/extension/schemas.ts +9 -3
- package/src/extension/tool-description.ts +11 -9
- package/src/inspectors/herdr/actions.ts +2 -1
- package/src/inspectors/herdr/inspector-runner.ts +2 -10
- package/src/inspectors/herdr/session-roots-codec.ts +42 -0
- package/src/integrations/herdr-status.ts +46 -3
- package/src/runs/background/active-async-capacity.ts +77 -10
- package/src/runs/background/async-execution.ts +148 -27
- package/src/runs/background/async-job-tracker.ts +5 -0
- package/src/runs/background/async-resume.ts +3 -0
- package/src/runs/background/async-retention.ts +20 -3
- package/src/runs/background/async-status.ts +5 -0
- package/src/runs/background/chain-root-attachment.ts +15 -1
- package/src/runs/background/fleet-view.ts +16 -10
- package/src/runs/background/inspect-rpc.ts +8 -8
- package/src/runs/background/result-files.ts +27 -14
- package/src/runs/background/result-watcher.ts +9 -5
- package/src/runs/background/run-status.ts +33 -6
- package/src/runs/background/scheduled-runs.ts +7 -1
- package/src/runs/background/subagent-runner.ts +254 -49
- package/src/runs/background/wait-completions.ts +4 -0
- package/src/runs/foreground/execution.ts +107 -16
- package/src/runs/foreground/foreground-control.ts +6 -0
- package/src/runs/foreground/foreground-history.ts +25 -3
- package/src/runs/foreground/subagent-executor.ts +324 -84
- package/src/runs/foreground/workflow-detach-reconcile.ts +31 -7
- package/src/runs/shared/acceptance.ts +10 -5
- package/src/runs/shared/agent-contract.ts +1 -1
- package/src/runs/shared/child-protocol.ts +21 -7
- package/src/runs/shared/claude-code-adapter.ts +129 -0
- package/src/runs/shared/codex-exec-adapter.ts +129 -0
- package/src/runs/shared/completion-guard.ts +3 -2
- package/src/runs/shared/cursor-agent-adapter.ts +114 -0
- package/src/runs/shared/dynamic-fanout.ts +1 -1
- package/src/runs/shared/extension-bindings.ts +78 -0
- package/src/runs/shared/external-cli-contract.ts +167 -0
- package/src/runs/shared/external-cli-preflight.ts +122 -0
- package/src/runs/shared/external-cli-runner.ts +349 -54
- package/src/runs/shared/fast-mode-extension.ts +10 -0
- package/src/runs/shared/model-exclusions.ts +59 -8
- package/src/runs/shared/model-fallback.ts +25 -8
- package/src/runs/shared/mutation-evidence.ts +150 -0
- package/src/runs/shared/nested-events.ts +3 -1
- package/src/runs/shared/nested-render.ts +2 -2
- package/src/runs/shared/parallel-utils.ts +3 -0
- package/src/runs/shared/pi-args.ts +47 -0
- package/src/runs/shared/process-signal.ts +13 -0
- package/src/runs/shared/run-history.ts +21 -1
- package/src/runs/shared/structured-output.ts +18 -4
- package/src/runs/shared/subagent-prompt-runtime.ts +9 -5
- package/src/shared/fork-context.ts +21 -0
- package/src/shared/formatters.ts +7 -1
- package/src/shared/launch-contract.ts +6 -0
- package/src/shared/pruned-fork.ts +450 -0
- package/src/shared/session-file-trust.ts +19 -0
- package/src/shared/session-tokens.ts +14 -3
- package/src/shared/settings.ts +16 -3
- package/src/shared/shortcuts.ts +17 -0
- package/src/shared/types.ts +166 -9
- package/src/shared/workflow-child-permit.ts +116 -0
- package/src/slash/delegation-adapters.ts +0 -1
- package/src/slash/slash-commands.ts +8 -6
- package/src/tui/fleet-status.ts +27 -10
- package/src/tui/fleet-transcript.ts +11 -5
- package/src/tui/fleet.ts +28 -13
- package/src/tui/render.ts +47 -21
- package/src/workflows/scripted-workflow.ts +298 -30
- package/src/workflows/workflow-child-summary.ts +117 -0
- package/src/workflows/workflow-receipt.ts +119 -4
|
@@ -10,7 +10,7 @@ import * as path from "node:path";
|
|
|
10
10
|
import { fileURLToPath } from "node:url";
|
|
11
11
|
import { createRequire } from "node:module";
|
|
12
12
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
13
|
-
import type
|
|
13
|
+
import { discoverAgents, formatUnknownAgentError, unknownAgentDiagnosticContext, type AgentConfig, type UnknownAgentDiagnosticContext } from "../../agents/agents.ts";
|
|
14
14
|
import { appendAgentRefinementOverlay } from "../../agents/agent-refinements.ts";
|
|
15
15
|
import { writePrivateAtomicJson } from "../../shared/atomic-json.ts";
|
|
16
16
|
import { currentCompletionOwnerId } from "../../shared/completion-owner.ts";
|
|
@@ -39,6 +39,7 @@ import { validateImplementationToolContract } from "../shared/completion-guard.t
|
|
|
39
39
|
import {
|
|
40
40
|
type AcceptanceInput,
|
|
41
41
|
type AgentContract,
|
|
42
|
+
type AsyncParallelGroupStatus,
|
|
42
43
|
type AsyncStatus,
|
|
43
44
|
type ArtifactConfig,
|
|
44
45
|
type Details,
|
|
@@ -70,10 +71,12 @@ import type { ImportedAsyncRoot } from "./chain-root-attachment.ts";
|
|
|
70
71
|
import type { SessionLeaseRequest } from "../shared/session-lease.ts";
|
|
71
72
|
import { finalizeProcessTerminal, readProcessTerminal } from "./process-terminal.ts";
|
|
72
73
|
import type { ActiveAsyncCapacityHandle } from "./active-async-capacity.ts";
|
|
74
|
+
import { statusStepDescription } from "./chain-append.ts";
|
|
73
75
|
import { SUBAGENT_PROCESS_TERMINAL_EVENT } from "../../shared/types.ts";
|
|
74
76
|
import { assertAgentAllowedByCapabilityCeiling, decodeSubagentCapabilityCeiling, intersectSubagentCapabilityCeilings, resolveCurrentSubagentCapabilityCeiling, SUBAGENT_CAPABILITY_CEILING_ENV, type ResolvedSubagentCapabilityCeiling } from "../shared/capability-ceiling.ts";
|
|
75
77
|
import { agentDefinitionDigest, launchBindingDigest } from "../../shared/launch-contract.ts";
|
|
76
78
|
import { resolvePermissionRules, type PermissionConfig } from "../shared/permissions.ts";
|
|
79
|
+
import { normalizeExtensionBindings, omitExtensionBindingsEnv, type ExtensionBindings } from "../shared/extension-bindings.ts";
|
|
77
80
|
|
|
78
81
|
const require = createRequire(import.meta.url);
|
|
79
82
|
const piPackageRoot = resolvePiPackageRoot();
|
|
@@ -150,6 +153,8 @@ interface AsyncChainParams {
|
|
|
150
153
|
attachRoot?: ImportedAsyncRoot & { agent: string; outputName?: string; label?: string };
|
|
151
154
|
resultMode?: SubagentRunMode;
|
|
152
155
|
agents: AgentConfig[];
|
|
156
|
+
/** Original discovery provenance, retained by normal callers for unknown-agent diagnostics. */
|
|
157
|
+
unknownAgentDiagnosticContext?: UnknownAgentDiagnosticContext;
|
|
153
158
|
ctx: AsyncExecutionContext;
|
|
154
159
|
availableModels?: AvailableModelInfo[];
|
|
155
160
|
cwd?: string;
|
|
@@ -175,6 +180,7 @@ interface AsyncChainParams {
|
|
|
175
180
|
childIntercomTarget?: (agent: string, index: number) => string | undefined;
|
|
176
181
|
nestedRoute?: NestedRouteInfo;
|
|
177
182
|
acceptance?: AcceptanceInput;
|
|
183
|
+
fast?: boolean;
|
|
178
184
|
timeoutMs?: number;
|
|
179
185
|
turnBudget?: ResolvedTurnBudget;
|
|
180
186
|
toolBudget?: ResolvedToolBudget;
|
|
@@ -224,6 +230,7 @@ interface AsyncSingleParams {
|
|
|
224
230
|
structuredOutputSchema?: JsonSchemaObject;
|
|
225
231
|
modelOverride?: string;
|
|
226
232
|
modelOverrideFromParent?: boolean;
|
|
233
|
+
fast?: boolean;
|
|
227
234
|
thinkingOverride?: AgentConfig["thinking"];
|
|
228
235
|
availableModels?: AvailableModelInfo[];
|
|
229
236
|
maxSubagentDepth: number;
|
|
@@ -265,6 +272,7 @@ interface AsyncSingleParams {
|
|
|
265
272
|
requestId: string;
|
|
266
273
|
requestDigest: string;
|
|
267
274
|
};
|
|
275
|
+
extensionBindings?: ExtensionBindings;
|
|
268
276
|
}
|
|
269
277
|
|
|
270
278
|
interface AsyncExecutionResult {
|
|
@@ -279,6 +287,8 @@ export interface AsyncRunnerStepBuildParams {
|
|
|
279
287
|
attachRoot?: ImportedAsyncRoot & { agent: string; outputName?: string; label?: string };
|
|
280
288
|
resultMode?: SubagentRunMode;
|
|
281
289
|
agents: AgentConfig[];
|
|
290
|
+
/** Exact discovery provenance for failed resolution; omission triggers defensive fallback discovery. */
|
|
291
|
+
unknownAgentDiagnosticContext?: UnknownAgentDiagnosticContext;
|
|
282
292
|
ctx: AsyncExecutionContext;
|
|
283
293
|
availableModels?: AvailableModelInfo[];
|
|
284
294
|
cwd?: string;
|
|
@@ -295,6 +305,7 @@ export interface AsyncRunnerStepBuildParams {
|
|
|
295
305
|
asyncDir: string;
|
|
296
306
|
outputBaseDir?: string;
|
|
297
307
|
validateOutputBindings?: boolean;
|
|
308
|
+
fast?: boolean;
|
|
298
309
|
toolBudget?: ResolvedToolBudget;
|
|
299
310
|
configToolBudget?: ResolvedToolBudget;
|
|
300
311
|
/** Optional per-call hard toolTimeoutMs override from the subagent invocation. */
|
|
@@ -488,7 +499,20 @@ interface SpawnRunnerResult {
|
|
|
488
499
|
startupDidNotProceed?: boolean;
|
|
489
500
|
}
|
|
490
501
|
|
|
491
|
-
function
|
|
502
|
+
function isStaleExtensionContextError(error: unknown): boolean {
|
|
503
|
+
return error instanceof Error && /extension ctx is stale|stale after session replacement or reload/i.test(error.message);
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
export function emitProcessTerminalEvent(ctx: AsyncExecutionContext, proof: unknown): void {
|
|
507
|
+
try {
|
|
508
|
+
ctx.pi.events.emit(SUBAGENT_PROCESS_TERMINAL_EVENT, proof);
|
|
509
|
+
} catch (error) {
|
|
510
|
+
if (isStaleExtensionContextError(error)) return;
|
|
511
|
+
console.error("Failed to emit subagent process-terminal event:", error);
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
function spawnRunner(cfg: object, suffix: string, cwd: string, initialStatus: Omit<AsyncStatus, "pid" | "processTerminal">, initialStatusPath: string, onProcessTerminal?: (proof: unknown) => void): SpawnRunnerResult {
|
|
492
516
|
if (!jitiCliPath) {
|
|
493
517
|
return { error: "upstream jiti for TypeScript execution could not be found; ensure package dependencies are installed" };
|
|
494
518
|
}
|
|
@@ -505,8 +529,10 @@ function spawnRunner(cfg: object, suffix: string, cwd: string, onProcessTerminal
|
|
|
505
529
|
fs.mkdirSync(TEMP_ROOT_DIR, { recursive: true });
|
|
506
530
|
const cfgPath = getAsyncConfigPath(suffix);
|
|
507
531
|
const runnerProcessInstanceId = randomUUID();
|
|
508
|
-
const
|
|
509
|
-
|
|
532
|
+
const hasRevivalLease = typeof (cfg as { revivalLease?: unknown }).revivalLease === "object";
|
|
533
|
+
const launchBarrierToken = hasRevivalLease ? undefined : runnerProcessInstanceId;
|
|
534
|
+
const launchConfig = { ...cfg, runnerProcessInstanceId, ...(launchBarrierToken ? { launchBarrierToken } : {}) };
|
|
535
|
+
writePrivateAtomicJson(cfgPath, launchConfig);
|
|
510
536
|
const runner = path.join(path.dirname(fileURLToPath(import.meta.url)), "subagent-runner.ts");
|
|
511
537
|
const nodeCommand = resolveNodeExecutable();
|
|
512
538
|
const launchForStartup = launchConfig as typeof launchConfig & { asyncDir?: unknown; id?: unknown; sessionId?: unknown; completionOwnerId?: unknown; revivalLease?: unknown };
|
|
@@ -518,7 +544,9 @@ function spawnRunner(cfg: object, suffix: string, cwd: string, onProcessTerminal
|
|
|
518
544
|
? path.join(launchAsyncDir, "runner-startup.json")
|
|
519
545
|
: undefined;
|
|
520
546
|
const startupAckPath = startupPath ? path.join(path.dirname(startupPath), "runner-startup-ack.json") : undefined;
|
|
521
|
-
const startupProceedPath =
|
|
547
|
+
const startupProceedPath = launchAsyncDir && (startupPath || launchBarrierToken)
|
|
548
|
+
? path.join(launchAsyncDir, "runner-startup-proceed.json")
|
|
549
|
+
: undefined;
|
|
522
550
|
if (startupPath) fs.rmSync(startupPath, { force: true });
|
|
523
551
|
if (startupAckPath) fs.rmSync(startupAckPath, { force: true });
|
|
524
552
|
if (startupProceedPath) fs.rmSync(startupProceedPath, { force: true });
|
|
@@ -538,7 +566,7 @@ function spawnRunner(cfg: object, suffix: string, cwd: string, onProcessTerminal
|
|
|
538
566
|
stdio: ["ignore", stdoutFd ?? "ignore", stderrFd ?? "ignore"],
|
|
539
567
|
windowsHide: true,
|
|
540
568
|
env: {
|
|
541
|
-
...process.env,
|
|
569
|
+
...omitExtensionBindingsEnv(process.env),
|
|
542
570
|
...(piPackageRoot ? { [PI_CODING_AGENT_PACKAGE_ROOT_ENV]: piPackageRoot } : {}),
|
|
543
571
|
},
|
|
544
572
|
});
|
|
@@ -600,6 +628,27 @@ function spawnRunner(cfg: object, suffix: string, cwd: string, onProcessTerminal
|
|
|
600
628
|
if (typeof proc.pid !== "number") {
|
|
601
629
|
return { error: `async runner did not produce a pid for cwd: ${cwd}` };
|
|
602
630
|
}
|
|
631
|
+
try {
|
|
632
|
+
writePrivateAtomicJson(initialStatusPath, {
|
|
633
|
+
...initialStatus,
|
|
634
|
+
pid: proc.pid,
|
|
635
|
+
processTerminal: { version: 1, state: "pending", runId: initialStatus.runId, runnerProcessInstanceId },
|
|
636
|
+
});
|
|
637
|
+
} catch (error) {
|
|
638
|
+
const message = `Failed to persist initial async status: ${error instanceof Error ? error.message : String(error)}`;
|
|
639
|
+
const terminationObserved = terminateRunnerBeforeProceed(proc.pid);
|
|
640
|
+
return { pid: proc.pid, runnerProcessInstanceId, error: message, terminationObserved, startupDidNotProceed: true };
|
|
641
|
+
}
|
|
642
|
+
if (launchBarrierToken && startupProceedPath) {
|
|
643
|
+
try {
|
|
644
|
+
writeRunnerStartupControl(startupProceedPath, { action: "proceed", token: launchBarrierToken });
|
|
645
|
+
} catch (error) {
|
|
646
|
+
const message = `Failed to authorize async runner startup: ${error instanceof Error ? error.message : String(error)}`;
|
|
647
|
+
if (launchAsyncDir) persistPreProceedStartupFailure(launchAsyncDir, launchRunId, runnerProcessInstanceId, launchSessionId, launchCompletionOwnerId, message);
|
|
648
|
+
const terminationObserved = terminateRunnerBeforeProceed(proc.pid);
|
|
649
|
+
return { pid: proc.pid, runnerProcessInstanceId, error: message, terminationObserved, startupDidNotProceed: true };
|
|
650
|
+
}
|
|
651
|
+
}
|
|
603
652
|
proc.unref();
|
|
604
653
|
if (startupPath && startupAckPath && startupProceedPath) {
|
|
605
654
|
const persistStartupFailure = (message: string) => {
|
|
@@ -704,6 +753,8 @@ export function buildAsyncRunnerSteps(id: string, params: AsyncRunnerStepBuildPa
|
|
|
704
753
|
}
|
|
705
754
|
const workflowGraph = buildWorkflowGraphSnapshot({ runId: id, mode: resultMode, steps: graphChain });
|
|
706
755
|
|
|
756
|
+
const diagnosticContext = params.unknownAgentDiagnosticContext
|
|
757
|
+
?? unknownAgentDiagnosticContext(discoverAgents(path.resolve(runnerCwd), "both"));
|
|
707
758
|
for (const s of chain) {
|
|
708
759
|
const stepAgents = isParallelStep(s)
|
|
709
760
|
? s.parallel.map((t) => t.agent)
|
|
@@ -711,9 +762,7 @@ export function buildAsyncRunnerSteps(id: string, params: AsyncRunnerStepBuildPa
|
|
|
711
762
|
? [s.parallel.agent]
|
|
712
763
|
: [(s as SequentialStep).agent];
|
|
713
764
|
for (const agentName of stepAgents) {
|
|
714
|
-
if (!agents.find((x) => x.name === agentName)) {
|
|
715
|
-
return { error: `Unknown agent: ${agentName}` };
|
|
716
|
-
}
|
|
765
|
+
if (!agents.find((x) => x.name === agentName)) return { error: formatUnknownAgentError(agentName, diagnosticContext) };
|
|
717
766
|
}
|
|
718
767
|
}
|
|
719
768
|
|
|
@@ -727,6 +776,7 @@ export function buildAsyncRunnerSteps(id: string, params: AsyncRunnerStepBuildPa
|
|
|
727
776
|
...(s.progress !== undefined ? { progress: s.progress } : {}),
|
|
728
777
|
...(stepSkillInput !== undefined ? { skills: stepSkillInput } : {}),
|
|
729
778
|
...(s.model !== undefined ? { model: s.model } : {}),
|
|
779
|
+
...(s.fast !== undefined ? { fast: s.fast } : {}),
|
|
730
780
|
};
|
|
731
781
|
};
|
|
732
782
|
const buildSeqStep = (s: SequentialStep, sessionFile?: string, behaviorCwd?: string, progressPrecreated = false, resolvedBehavior?: ResolvedStepBehavior, flatIndex?: number, parallelOutputNamespace?: { stepIndex: number; taskIndex?: number }, runFanoutPath?: string) => {
|
|
@@ -739,6 +789,7 @@ export function buildAsyncRunnerSteps(id: string, params: AsyncRunnerStepBuildPa
|
|
|
739
789
|
if (s.outputSchema !== undefined) unsupported.push("structured output");
|
|
740
790
|
if (s.acceptance !== undefined || params.agentContract !== undefined || s.agentContract !== undefined) unsupported.push("acceptance/agent contract");
|
|
741
791
|
if (s.toolBudget !== undefined || params.toolBudget !== undefined || a.toolBudget !== undefined || params.configToolBudget !== undefined) unsupported.push("tool budget");
|
|
792
|
+
if ((s.fast ?? params.fast ?? a.fast) === true) unsupported.push("fast mode");
|
|
742
793
|
if (params.contextForAgent?.(s.agent) === "fork") unsupported.push("fork context");
|
|
743
794
|
if (unsupported.length > 0) throw new AsyncStartValidationError(`Agent '${a.name}' uses runner.type='${externalRunnerType}' and does not support: ${unsupported.join(", ")}.`);
|
|
744
795
|
}
|
|
@@ -831,6 +882,21 @@ export function buildAsyncRunnerSteps(id: string, params: AsyncRunnerStepBuildPa
|
|
|
831
882
|
}
|
|
832
883
|
const agentContract = s.agentContract ?? params.agentContract;
|
|
833
884
|
const permissionRules = resolvePermissionRules(ctx.permissions, a.permissions);
|
|
885
|
+
const modelCandidates = externalRunner ? [] : buildModelCandidates(primaryModel, a.fallbackModels, availableModels, a.modelProvider ?? ctx.currentModelProvider, {
|
|
886
|
+
scope: modelScopes,
|
|
887
|
+
primaryModelFromParent,
|
|
888
|
+
}).flatMap((candidate) => {
|
|
889
|
+
const resolved = applyThinkingSuffix(candidate, effectiveThinking, thinkingOverride !== undefined);
|
|
890
|
+
return resolved ? [resolved] : [];
|
|
891
|
+
});
|
|
892
|
+
if (!externalRunner) {
|
|
893
|
+
try {
|
|
894
|
+
for (const candidate of modelCandidates) assertThinkingWithinCeiling({ model: candidate, configThinking: effectiveThinking, ceiling: thinkingCeiling, agent: a.name, runId: id });
|
|
895
|
+
} catch (error) {
|
|
896
|
+
throw new AsyncStartValidationError(error instanceof Error ? error.message : String(error));
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
const fast = s.fast ?? params.fast ?? a.fast;
|
|
834
900
|
const toolPlan = resolvePiLaunchToolPlan({
|
|
835
901
|
tools: a.tools,
|
|
836
902
|
extensions: a.extensions,
|
|
@@ -839,6 +905,9 @@ export function buildAsyncRunnerSteps(id: string, params: AsyncRunnerStepBuildPa
|
|
|
839
905
|
cwd: stepCwd,
|
|
840
906
|
requireReadTool: Boolean(resolvedSkills.length),
|
|
841
907
|
structuredOutput: Boolean(s.outputSchema),
|
|
908
|
+
fast,
|
|
909
|
+
model,
|
|
910
|
+
modelCandidates,
|
|
842
911
|
capabilityCeiling: params.capabilityCeiling,
|
|
843
912
|
inheritedCapabilityCeiling: decodeSubagentCapabilityCeiling(process.env[SUBAGENT_CAPABILITY_CEILING_ENV]),
|
|
844
913
|
agentName: a.name,
|
|
@@ -862,20 +931,6 @@ export function buildAsyncRunnerSteps(id: string, params: AsyncRunnerStepBuildPa
|
|
|
862
931
|
});
|
|
863
932
|
if (contractError) throw new AsyncStartValidationError(contractError);
|
|
864
933
|
}
|
|
865
|
-
const modelCandidates = externalRunner ? [] : buildModelCandidates(primaryModel, a.fallbackModels, availableModels, a.modelProvider ?? ctx.currentModelProvider, {
|
|
866
|
-
scope: modelScopes,
|
|
867
|
-
primaryModelFromParent,
|
|
868
|
-
}).flatMap((candidate) => {
|
|
869
|
-
const resolved = applyThinkingSuffix(candidate, effectiveThinking, thinkingOverride !== undefined);
|
|
870
|
-
return resolved ? [resolved] : [];
|
|
871
|
-
});
|
|
872
|
-
if (!externalRunner) {
|
|
873
|
-
try {
|
|
874
|
-
for (const candidate of modelCandidates) assertThinkingWithinCeiling({ model: candidate, configThinking: effectiveThinking, ceiling: thinkingCeiling, agent: a.name, runId: id });
|
|
875
|
-
} catch (error) {
|
|
876
|
-
throw new AsyncStartValidationError(error instanceof Error ? error.message : String(error));
|
|
877
|
-
}
|
|
878
|
-
}
|
|
879
934
|
return {
|
|
880
935
|
parentSessionId: ctx.parentSessionId ?? ctx.currentSessionId,
|
|
881
936
|
permissionRules,
|
|
@@ -892,6 +947,7 @@ export function buildAsyncRunnerSteps(id: string, params: AsyncRunnerStepBuildPa
|
|
|
892
947
|
structured: Boolean(s.outputSchema),
|
|
893
948
|
cwd: stepCwd,
|
|
894
949
|
model,
|
|
950
|
+
...(fast !== undefined ? { fast } : {}),
|
|
895
951
|
thinking: resolveEffectiveThinking(model, effectiveThinking),
|
|
896
952
|
...(thinkingCeiling ? { thinkingCeiling } : {}),
|
|
897
953
|
launchResolvedExtensions,
|
|
@@ -930,7 +986,7 @@ export function buildAsyncRunnerSteps(id: string, params: AsyncRunnerStepBuildPa
|
|
|
930
986
|
acceptanceRole: a.acceptanceRole,
|
|
931
987
|
...(s.gateOn ? { gateOn: s.gateOn } : {}),
|
|
932
988
|
...(s.outputSchema ? { structuredOutputSchema: s.outputSchema } : {}),
|
|
933
|
-
...(s.outputSchema ? { structuredOutput: createStructuredOutputRuntime(s.outputSchema, path.join(asyncDir, "structured-output")) } : {}),
|
|
989
|
+
...(s.outputSchema ? { structuredOutput: createStructuredOutputRuntime(s.outputSchema, path.join(asyncDir, "structured-output"), { captureAcceptanceReport: s.acceptance !== false }) } : {}),
|
|
934
990
|
...(resolvedToolBudget.budget ? { toolBudget: resolvedToolBudget.budget } : {}),
|
|
935
991
|
...(s.worktree ? { worktree: true } : {}),
|
|
936
992
|
};
|
|
@@ -1128,6 +1184,7 @@ export function executeAsyncChain(
|
|
|
1128
1184
|
attachRoot: params.attachRoot,
|
|
1129
1185
|
resultMode,
|
|
1130
1186
|
agents,
|
|
1187
|
+
unknownAgentDiagnosticContext: params.unknownAgentDiagnosticContext,
|
|
1131
1188
|
ctx,
|
|
1132
1189
|
availableModels: params.availableModels,
|
|
1133
1190
|
cwd,
|
|
@@ -1143,6 +1200,7 @@ export function executeAsyncChain(
|
|
|
1143
1200
|
waitToolEnabled: params.waitToolEnabled,
|
|
1144
1201
|
worktreeBaseDir,
|
|
1145
1202
|
asyncDir,
|
|
1203
|
+
fast: params.fast,
|
|
1146
1204
|
toolBudget: params.toolBudget,
|
|
1147
1205
|
configToolBudget: params.configToolBudget,
|
|
1148
1206
|
callToolTimeoutMs: params.callToolTimeoutMs,
|
|
@@ -1178,6 +1236,21 @@ export function executeAsyncChain(
|
|
|
1178
1236
|
}
|
|
1179
1237
|
return "agent" in step ? [childIntercomTarget(step.agent, childTargetIndex++)] : [undefined];
|
|
1180
1238
|
}) : undefined;
|
|
1239
|
+
const initialStatusSteps = eventChain.flatMap((step) => isParallelStep(step)
|
|
1240
|
+
? step.parallel.map((task) => ({ agent: task.agent, ...(statusStepDescription(task.task) ? { description: statusStepDescription(task.task) } : {}), ...(task.label ? { label: task.label } : {}), ...(task.as ? { outputName: task.as } : {}), status: "pending" as const }))
|
|
1241
|
+
: isDynamicParallelStep(step)
|
|
1242
|
+
? [{ agent: `expand:${step.parallel.agent}`, label: step.label ?? step.parallel.label ?? `Dynamic fanout (${step.collect.as})`, outputName: step.collect.as, status: "pending" as const }]
|
|
1243
|
+
: [{ agent: (step as SequentialStep).agent, ...(statusStepDescription((step as SequentialStep).task) ? { description: statusStepDescription((step as SequentialStep).task) } : {}), ...((step as SequentialStep).label ? { label: (step as SequentialStep).label } : {}), ...((step as SequentialStep).as ? { outputName: (step as SequentialStep).as } : {}), status: "pending" as const }]);
|
|
1244
|
+
const initialParallelGroups: AsyncParallelGroupStatus[] = [];
|
|
1245
|
+
let initialFlatIndex = 0;
|
|
1246
|
+
for (let stepIndex = 0; stepIndex < eventChain.length; stepIndex++) {
|
|
1247
|
+
const step = eventChain[stepIndex]!;
|
|
1248
|
+
if (isParallelStep(step)) initialParallelGroups.push({ start: initialFlatIndex, count: step.parallel.length, stepIndex });
|
|
1249
|
+
else if (isDynamicParallelStep(step)) initialParallelGroups.push({ start: initialFlatIndex, count: 1, stepIndex });
|
|
1250
|
+
initialFlatIndex += isParallelStep(step) ? step.parallel.length : 1;
|
|
1251
|
+
}
|
|
1252
|
+
const initialStatusAt = Date.now();
|
|
1253
|
+
const initialCompletionOwnerId = ctx.completionOwnerId ?? currentCompletionOwnerId();
|
|
1181
1254
|
|
|
1182
1255
|
let spawnResult: SpawnRunnerResult = {};
|
|
1183
1256
|
try {
|
|
@@ -1227,7 +1300,22 @@ export function executeAsyncChain(
|
|
|
1227
1300
|
},
|
|
1228
1301
|
id,
|
|
1229
1302
|
runnerCwd,
|
|
1230
|
-
|
|
1303
|
+
{
|
|
1304
|
+
lifecycleArtifactVersion: SUBAGENT_LIFECYCLE_ARTIFACT_VERSION,
|
|
1305
|
+
runId: id,
|
|
1306
|
+
...(ctx.currentSessionId ? { sessionId: ctx.currentSessionId } : {}),
|
|
1307
|
+
...(initialCompletionOwnerId ? { completionOwnerId: initialCompletionOwnerId } : {}),
|
|
1308
|
+
mode: resultMode,
|
|
1309
|
+
state: "running",
|
|
1310
|
+
startedAt: initialStatusAt,
|
|
1311
|
+
lastUpdate: initialStatusAt,
|
|
1312
|
+
currentStep: 0,
|
|
1313
|
+
chainStepCount: eventChain.length,
|
|
1314
|
+
...(initialParallelGroups.length ? { parallelGroups: initialParallelGroups } : {}),
|
|
1315
|
+
steps: initialStatusSteps,
|
|
1316
|
+
},
|
|
1317
|
+
path.join(asyncDir, "status.json"),
|
|
1318
|
+
(proof) => emitProcessTerminalEvent(ctx, proof),
|
|
1231
1319
|
);
|
|
1232
1320
|
} catch (error) {
|
|
1233
1321
|
params.activeAsyncCapacity?.rollback();
|
|
@@ -1391,6 +1479,12 @@ export function executeAsyncSingle(
|
|
|
1391
1479
|
nestedRoute,
|
|
1392
1480
|
} = params;
|
|
1393
1481
|
const task = params.task ?? "";
|
|
1482
|
+
let extensionBindings: ExtensionBindings | undefined;
|
|
1483
|
+
try {
|
|
1484
|
+
extensionBindings = normalizeExtensionBindings(params.extensionBindings)?.value;
|
|
1485
|
+
} catch (error) {
|
|
1486
|
+
return formatAsyncStartError("single", error instanceof Error ? error.message : String(error));
|
|
1487
|
+
}
|
|
1394
1488
|
const acceptanceErrors = validateAcceptanceInput(params.acceptance);
|
|
1395
1489
|
if (acceptanceErrors.length > 0) return formatAsyncStartError("single", acceptanceErrors.join(" "));
|
|
1396
1490
|
const externalRunner = agentConfig.runner?.type === "external-cli" || agentConfig.runner?.type === "external-job";
|
|
@@ -1399,6 +1493,7 @@ export function executeAsyncSingle(
|
|
|
1399
1493
|
if (externalRunner) {
|
|
1400
1494
|
const unsupported: string[] = [];
|
|
1401
1495
|
if (params.modelOverride !== undefined) unsupported.push("model override");
|
|
1496
|
+
if ((params.fast ?? agentConfig.fast) === true) unsupported.push("fast mode");
|
|
1402
1497
|
if (params.thinkingOverride !== undefined) unsupported.push("thinking override");
|
|
1403
1498
|
if (params.structuredOutputSchema !== undefined) unsupported.push("structured output");
|
|
1404
1499
|
if (params.acceptance !== undefined || params.agentContract !== undefined) unsupported.push("acceptance/agent contract");
|
|
@@ -1406,6 +1501,7 @@ export function executeAsyncSingle(
|
|
|
1406
1501
|
if (params.context === "fork") unsupported.push("fork context");
|
|
1407
1502
|
if ((params.skills?.length ?? 0) > 0) unsupported.push("skills");
|
|
1408
1503
|
if (permissionRules) unsupported.push("native Pi child permissions");
|
|
1504
|
+
if (extensionBindings !== undefined) unsupported.push("extension bindings");
|
|
1409
1505
|
if (unsupported.length > 0) return formatAsyncStartError("single", `Agent '${agentConfig.name}' uses runner.type='${externalRunnerType}' and does not support: ${unsupported.join(", ")}.`);
|
|
1410
1506
|
}
|
|
1411
1507
|
const capabilityCeiling = intersectSubagentCapabilityCeilings(params.capabilityCeiling ?? resolveCurrentSubagentCapabilityCeiling(ctx.currentSessionId), decodeSubagentCapabilityCeiling(process.env[SUBAGENT_CAPABILITY_CEILING_ENV]));
|
|
@@ -1518,7 +1614,7 @@ export function executeAsyncSingle(
|
|
|
1518
1614
|
const initialUsageBudget = usageBudgetState(params.usageBudget, undefined);
|
|
1519
1615
|
const resolvedSessionDir = params.sessionDir ?? (sessionRoot ? path.join(sessionRoot, `async-${id}`) : undefined);
|
|
1520
1616
|
const structuredOutput = params.structuredOutputSchema
|
|
1521
|
-
? createStructuredOutputRuntime(params.structuredOutputSchema, path.join(asyncDir, "structured-output"))
|
|
1617
|
+
? createStructuredOutputRuntime(params.structuredOutputSchema, path.join(asyncDir, "structured-output"), { captureAcceptanceReport: params.acceptance !== false })
|
|
1522
1618
|
: undefined;
|
|
1523
1619
|
const modelCandidates = externalRunner
|
|
1524
1620
|
? []
|
|
@@ -1546,6 +1642,9 @@ export function executeAsyncSingle(
|
|
|
1546
1642
|
cwd: runnerCwd,
|
|
1547
1643
|
requireReadTool: Boolean(resolvedSkills.length),
|
|
1548
1644
|
structuredOutput: Boolean(params.structuredOutputSchema),
|
|
1645
|
+
fast: params.fast ?? agentConfig.fast,
|
|
1646
|
+
model,
|
|
1647
|
+
modelCandidates,
|
|
1549
1648
|
capabilityCeiling,
|
|
1550
1649
|
inheritedCapabilityCeiling: decodeSubagentCapabilityCeiling(process.env[SUBAGENT_CAPABILITY_CEILING_ENV]),
|
|
1551
1650
|
agentName: agentConfig.name,
|
|
@@ -1571,6 +1670,7 @@ export function executeAsyncSingle(
|
|
|
1571
1670
|
task,
|
|
1572
1671
|
...(model ? { model } : {}),
|
|
1573
1672
|
modelCandidates,
|
|
1673
|
+
...((params.fast ?? agentConfig.fast) !== undefined ? { fast: params.fast ?? agentConfig.fast } : {}),
|
|
1574
1674
|
...(resolveEffectiveThinking(model, effectiveThinking) ? { thinking: resolveEffectiveThinking(model, effectiveThinking) } : {}),
|
|
1575
1675
|
...(thinkingCeiling ? { thinkingCeiling } : {}),
|
|
1576
1676
|
systemPrompt: effectiveSystemPrompt,
|
|
@@ -1584,6 +1684,7 @@ export function executeAsyncSingle(
|
|
|
1584
1684
|
...(outputPath ? { outputPath } : {}),
|
|
1585
1685
|
outputMode,
|
|
1586
1686
|
...(params.structuredOutputSchema ? { structuredOutputSchema: params.structuredOutputSchema } : {}),
|
|
1687
|
+
...(extensionBindings ? { extensionBindings } : {}),
|
|
1587
1688
|
});
|
|
1588
1689
|
const resolvedAcceptance = resolveEffectiveAcceptance({
|
|
1589
1690
|
explicit: params.acceptance,
|
|
@@ -1598,6 +1699,7 @@ export function executeAsyncSingle(
|
|
|
1598
1699
|
const recoveryDescriptor: SteeringRecoveryDescriptor = {
|
|
1599
1700
|
version: 1,
|
|
1600
1701
|
launchContractDigest,
|
|
1702
|
+
...(extensionBindings ? { extensionBindings } : {}),
|
|
1601
1703
|
runFanoutBudget,
|
|
1602
1704
|
sourceRunId: id,
|
|
1603
1705
|
...(params.agentContract ? { agentContract: params.agentContract } : {}),
|
|
@@ -1606,6 +1708,7 @@ export function executeAsyncSingle(
|
|
|
1606
1708
|
...(sessionFile ? { sessionFile } : {}),
|
|
1607
1709
|
cwd: runnerCwd,
|
|
1608
1710
|
...(model ? { model } : {}),
|
|
1711
|
+
...(params.fast ?? recoveryAgentConfig.fast ? { fast: params.fast ?? recoveryAgentConfig.fast } : {}),
|
|
1609
1712
|
...(recoveryAgentConfig.modelProvider ? { modelProvider: recoveryAgentConfig.modelProvider } : {}),
|
|
1610
1713
|
...(params.modelOverrideFromParent ? { modelOverrideFromParent: true } : {}),
|
|
1611
1714
|
...(recoveryAgentConfig.fallbackModels ? { fallbackModels: [...recoveryAgentConfig.fallbackModels] } : {}),
|
|
@@ -1650,6 +1753,8 @@ export function executeAsyncSingle(
|
|
|
1650
1753
|
}
|
|
1651
1754
|
}
|
|
1652
1755
|
let spawnResult: SpawnRunnerResult = {};
|
|
1756
|
+
const initialStatusAt = Date.now();
|
|
1757
|
+
const initialCompletionOwnerId = ctx.completionOwnerId ?? currentCompletionOwnerId();
|
|
1653
1758
|
try {
|
|
1654
1759
|
spawnResult = spawnRunner(
|
|
1655
1760
|
{
|
|
@@ -1666,6 +1771,7 @@ export function executeAsyncSingle(
|
|
|
1666
1771
|
...(params.context ? { context: params.context } : {}),
|
|
1667
1772
|
cwd: runnerCwd,
|
|
1668
1773
|
model,
|
|
1774
|
+
...(params.fast ?? agentConfig.fast ? { fast: params.fast ?? agentConfig.fast } : {}),
|
|
1669
1775
|
thinking: resolveEffectiveThinking(model, effectiveThinking),
|
|
1670
1776
|
...(thinkingCeiling ? { thinkingCeiling } : {}),
|
|
1671
1777
|
modelCandidates,
|
|
@@ -1690,6 +1796,7 @@ export function executeAsyncSingle(
|
|
|
1690
1796
|
definitionDigest: agentDefinitionDigest(agentConfig),
|
|
1691
1797
|
launchBindingTask: task,
|
|
1692
1798
|
launchContractDigest,
|
|
1799
|
+
...(extensionBindings ? { extensionBindings } : {}),
|
|
1693
1800
|
launchResolvedExtensions,
|
|
1694
1801
|
effectiveAcceptance: resolvedAcceptance,
|
|
1695
1802
|
...(structuredOutput ? { structuredOutput } : {}),
|
|
@@ -1743,7 +1850,21 @@ export function executeAsyncSingle(
|
|
|
1743
1850
|
},
|
|
1744
1851
|
id,
|
|
1745
1852
|
runnerCwd,
|
|
1746
|
-
|
|
1853
|
+
{
|
|
1854
|
+
lifecycleArtifactVersion: SUBAGENT_LIFECYCLE_ARTIFACT_VERSION,
|
|
1855
|
+
runId: id,
|
|
1856
|
+
...(ctx.currentSessionId ? { sessionId: ctx.currentSessionId } : {}),
|
|
1857
|
+
...(initialCompletionOwnerId ? { completionOwnerId: initialCompletionOwnerId } : {}),
|
|
1858
|
+
mode: "single",
|
|
1859
|
+
state: "running",
|
|
1860
|
+
startedAt: initialStatusAt,
|
|
1861
|
+
lastUpdate: initialStatusAt,
|
|
1862
|
+
currentStep: 0,
|
|
1863
|
+
chainStepCount: 1,
|
|
1864
|
+
steps: [{ agent, status: "pending" }],
|
|
1865
|
+
},
|
|
1866
|
+
path.join(asyncDir, "status.json"),
|
|
1867
|
+
(proof) => emitProcessTerminalEvent(ctx, proof),
|
|
1747
1868
|
);
|
|
1748
1869
|
} catch (error) {
|
|
1749
1870
|
params.activeAsyncCapacity?.rollback();
|
|
@@ -24,6 +24,7 @@ import { findNestedRouteForRootId, hasLiveNestedDescendants, updateAsyncJobNeste
|
|
|
24
24
|
import { listAsyncRuns, type AsyncRunSummary } from "./async-status.ts";
|
|
25
25
|
import { EXTERNAL_JOB_BRIDGE_REQUEST_DIR, serviceExternalJobBridgeRequests } from "../shared/external-job-bridge.ts";
|
|
26
26
|
import { shouldUseNativeFsWatch } from "../../shared/watch-strategy.ts";
|
|
27
|
+
import { parseWorkflowChildSummary } from "../../workflows/workflow-child-summary.ts";
|
|
27
28
|
|
|
28
29
|
interface AsyncJobTrackerOptions {
|
|
29
30
|
completionRetentionMs?: number;
|
|
@@ -171,6 +172,7 @@ export function createAsyncJobTracker(pi: Pick<ExtensionAPI, "events">, state: S
|
|
|
171
172
|
parentWorkflowRunId: run.parentWorkflowRunId,
|
|
172
173
|
workflowKey: run.workflowKey,
|
|
173
174
|
workflow: run.workflow,
|
|
175
|
+
workflowChildren: parseWorkflowChildSummary(run.workflowChildren),
|
|
174
176
|
};
|
|
175
177
|
};
|
|
176
178
|
const cancelCleanup = (asyncId: string) => {
|
|
@@ -398,6 +400,9 @@ export function createAsyncJobTracker(pi: Pick<ExtensionAPI, "events">, state: S
|
|
|
398
400
|
job.parentWorkflowRunId = status.parentWorkflowRunId ?? job.parentWorkflowRunId;
|
|
399
401
|
job.workflowKey = status.workflowKey ?? job.workflowKey;
|
|
400
402
|
job.workflow = status.workflow ?? job.workflow;
|
|
403
|
+
const workflowChildren = parseWorkflowChildSummary(status.workflowChildren);
|
|
404
|
+
if (workflowChildren && workflowChildren.workflowRunId !== status.runId) throw new Error("workflowChildren.workflowRunId does not match async status runId.");
|
|
405
|
+
job.workflowChildren = workflowChildren ?? job.workflowChildren;
|
|
401
406
|
job.currentStep = status.currentStep ?? job.currentStep;
|
|
402
407
|
job.chainStepCount = status.chainStepCount ?? job.chainStepCount;
|
|
403
408
|
job.startedAt = status.startedAt ?? job.startedAt;
|
|
@@ -2,6 +2,7 @@ import * as fs from "node:fs";
|
|
|
2
2
|
import * as path from "node:path";
|
|
3
3
|
import { DIRS, type AcceptanceInput, type AsyncStatus, type ResolvedTurnBudget, type SteeringRecoveryDescriptor, type SubagentRunMode } from "../../shared/types.ts";
|
|
4
4
|
import type { AgentConfig } from "../../agents/agents.ts";
|
|
5
|
+
import { normalizeExtensionBindings } from "../shared/extension-bindings.ts";
|
|
5
6
|
import { validateAcceptanceInput } from "../shared/acceptance.ts";
|
|
6
7
|
import { validateToolBudgetConfig } from "../shared/tool-budget.ts";
|
|
7
8
|
import { intersectSubagentCapabilityCeilings, parseSubagentCapabilityCeiling, type ResolvedSubagentCapabilityCeiling } from "../shared/capability-ceiling.ts";
|
|
@@ -328,6 +329,7 @@ export function readAsyncRecoveryDescriptor(asyncDir: string | undefined): Steer
|
|
|
328
329
|
"skillPath", "agentFilePath", "completionGuard", "memory", "outputPath", "outputMode", "structuredOutputSchema", "acceptance", "sessionDir", "artifactConfig",
|
|
329
330
|
"artifactsDir", "maxOutput", "controlConfig", "context", "intercomBridge", "absoluteDeadlineAt", "initialTurnBudget", "initialToolBudget", "maxSubagentDepth", "share", "capabilityCeiling",
|
|
330
331
|
"launchResolvedExtensions", "runFanoutBudget",
|
|
332
|
+
"extensionBindings",
|
|
331
333
|
]);
|
|
332
334
|
for (const field of Object.keys(parsed)) {
|
|
333
335
|
if (!allowedFields.has(field)) throw new Error(`Invalid async recovery descriptor '${descriptorPath}': unknown field '${field}'.`);
|
|
@@ -344,6 +346,7 @@ export function readAsyncRecoveryDescriptor(asyncDir: string | undefined): Steer
|
|
|
344
346
|
}
|
|
345
347
|
if (parsed.capabilityCeiling !== undefined) parsed.capabilityCeiling = parseSubagentCapabilityCeiling(parsed.capabilityCeiling, `async recovery descriptor '${descriptorPath}' capabilityCeiling`);
|
|
346
348
|
if (parsed.thinkingCeiling !== undefined) parsed.thinkingCeiling = parseThinkingLevel(parsed.thinkingCeiling, `async recovery descriptor '${descriptorPath}' thinkingCeiling`);
|
|
349
|
+
if (parsed.extensionBindings !== undefined) parsed.extensionBindings = normalizeExtensionBindings(parsed.extensionBindings)!.value;
|
|
347
350
|
if (parsed.agentContract !== undefined) {
|
|
348
351
|
if (!parsed.agentContract || typeof parsed.agentContract !== "object" || Array.isArray(parsed.agentContract)) throw new Error(`Invalid async recovery descriptor '${descriptorPath}': agentContract must be an object.`);
|
|
349
352
|
const contract = parsed.agentContract as Record<string, unknown>;
|
|
@@ -9,6 +9,7 @@ import type { AsyncStatus } from "../../shared/types.ts";
|
|
|
9
9
|
import { MISSION_BINDING_FILE } from "../../missions/lifecycle.ts";
|
|
10
10
|
import { ACTIVE_RUN_INDEX_DIR } from "./active-run-index.ts";
|
|
11
11
|
import { encodeIndexSegment } from "./index-segment.ts";
|
|
12
|
+
import { reconcileAsyncRun } from "./stale-run-reconciler.ts";
|
|
12
13
|
|
|
13
14
|
export const ASYNC_RETENTION_DAYS = 30;
|
|
14
15
|
export const ASYNC_RETENTION_BATCH_SIZE = 100;
|
|
@@ -96,11 +97,13 @@ export interface AsyncRetentionOptions {
|
|
|
96
97
|
lstatSync?: typeof fs.lstatSync;
|
|
97
98
|
signal?: AbortSignal;
|
|
98
99
|
discoveryWorkerUrl?: URL;
|
|
100
|
+
reconcileKill?: (pid: number, signal?: NodeJS.Signals | 0) => boolean;
|
|
99
101
|
}
|
|
100
102
|
|
|
101
103
|
export interface AsyncRetentionResult {
|
|
102
104
|
acquired: boolean;
|
|
103
105
|
scanned: number;
|
|
106
|
+
repairedRuns: number;
|
|
104
107
|
deletedRuns: number;
|
|
105
108
|
deletedResults: number;
|
|
106
109
|
reapedTombstones: number;
|
|
@@ -135,7 +138,7 @@ function listDir(dir: string): fs.Dirent[] {
|
|
|
135
138
|
|
|
136
139
|
function readJson(filePath: string): Record<string, unknown> | undefined {
|
|
137
140
|
try {
|
|
138
|
-
const parsed = JSON.parse(fs.readFileSync(filePath
|
|
141
|
+
const parsed = JSON.parse(fs.readFileSync(filePath).toString("utf8")) as unknown;
|
|
139
142
|
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed as Record<string, unknown> : undefined;
|
|
140
143
|
} catch {
|
|
141
144
|
return undefined;
|
|
@@ -320,7 +323,7 @@ function readCursor(root: string): RetentionCursor {
|
|
|
320
323
|
function processStartIdentity(pid: number): string | undefined {
|
|
321
324
|
if (process.platform === "linux") {
|
|
322
325
|
try {
|
|
323
|
-
const stat = fs.readFileSync(`/proc/${pid}/stat
|
|
326
|
+
const stat = fs.readFileSync(`/proc/${pid}/stat`).toString("utf8");
|
|
324
327
|
const commandEnd = stat.lastIndexOf(")");
|
|
325
328
|
if (commandEnd === -1) return undefined;
|
|
326
329
|
const fields = stat.slice(commandEnd + 1).trim().split(/\s+/);
|
|
@@ -504,6 +507,7 @@ function appendMaintenanceLog(root: string, result: AsyncRetentionResult, now: n
|
|
|
504
507
|
at: new Date(now).toISOString(),
|
|
505
508
|
acquired: result.acquired,
|
|
506
509
|
scanned: result.scanned,
|
|
510
|
+
repairedRuns: result.repairedRuns,
|
|
507
511
|
deletedRuns: result.deletedRuns,
|
|
508
512
|
deletedResults: result.deletedResults,
|
|
509
513
|
reapedTombstones: result.reapedTombstones,
|
|
@@ -654,6 +658,7 @@ export async function cleanupAsyncRetention(options: AsyncRetentionOptions): Pro
|
|
|
654
658
|
const result: AsyncRetentionResult = {
|
|
655
659
|
acquired: false,
|
|
656
660
|
scanned: 0,
|
|
661
|
+
repairedRuns: 0,
|
|
657
662
|
deletedRuns: 0,
|
|
658
663
|
deletedResults: 0,
|
|
659
664
|
reapedTombstones: 0,
|
|
@@ -750,7 +755,19 @@ export async function cleanupAsyncRetention(options: AsyncRetentionOptions): Pro
|
|
|
750
755
|
increment(result.skipped, "unsafe-run-path");
|
|
751
756
|
continue;
|
|
752
757
|
}
|
|
753
|
-
|
|
758
|
+
let status = readStatus(runDir);
|
|
759
|
+
if (status?.state === "running"
|
|
760
|
+
&& validRunId(status.runId)
|
|
761
|
+
&& path.basename(runDir) === status.runId
|
|
762
|
+
&& !protectedRunIds.has(status.runId)) {
|
|
763
|
+
const reconciliation = reconcileAsyncRun(runDir, {
|
|
764
|
+
resultsDir: options.resultsDir,
|
|
765
|
+
kill: options.reconcileKill,
|
|
766
|
+
now,
|
|
767
|
+
});
|
|
768
|
+
status = reconciliation.status ?? undefined;
|
|
769
|
+
if (reconciliation.repaired) result.repairedRuns += 1;
|
|
770
|
+
}
|
|
754
771
|
const initialReason = runSkipReason({ runDir, status, asyncDirRoot: options.asyncDirRoot, resultsDir: options.resultsDir, cutoff, protectedRunIds, waitRunIds: waitReferences.runIds });
|
|
755
772
|
if (initialReason) {
|
|
756
773
|
increment(result.skipped, initialReason);
|
|
@@ -16,6 +16,7 @@ import { ACTIVE_RUN_INDEX_DIR, DEFAULT_STALE_TERMINAL_ACTIVE_MARKER_MS, activeRu
|
|
|
16
16
|
import { readRecentTerminalRunIndex, TERMINAL_RUN_INDEX_DIR } from "./terminal-run-index.ts";
|
|
17
17
|
import { canScanAsyncRunPrefix } from "./run-id-query.ts";
|
|
18
18
|
import { asyncStatusChildIdentity } from "../shared/child-identity.ts";
|
|
19
|
+
import { parseWorkflowChildSummary } from "../../workflows/workflow-child-summary.ts";
|
|
19
20
|
|
|
20
21
|
interface AsyncRunStepSummary {
|
|
21
22
|
index: number;
|
|
@@ -121,6 +122,7 @@ export interface AsyncRunSummary {
|
|
|
121
122
|
parentWorkflowRunId?: string;
|
|
122
123
|
workflowKey?: string;
|
|
123
124
|
workflow?: Details["workflow"];
|
|
125
|
+
workflowChildren?: Details["workflowChildren"];
|
|
124
126
|
}
|
|
125
127
|
|
|
126
128
|
interface AsyncRunListOptions {
|
|
@@ -229,6 +231,8 @@ function deriveAsyncActivityState(asyncDir: string, status: AsyncStatus): { acti
|
|
|
229
231
|
}
|
|
230
232
|
|
|
231
233
|
function statusToSummary(asyncDir: string, status: AsyncStatus & { cwd?: string }, nestedWarnings: string[] = [], nestedRoute?: NestedRoute): AsyncRunSummary {
|
|
234
|
+
const workflowChildren = parseWorkflowChildSummary(status.workflowChildren);
|
|
235
|
+
if (workflowChildren && workflowChildren.workflowRunId !== status.runId) throw new Error(`Invalid async status '${path.join(asyncDir, "status.json")}': workflowChildren.workflowRunId does not match.`);
|
|
232
236
|
if (status.sessionId !== undefined && typeof status.sessionId !== "string") {
|
|
233
237
|
throw new Error(`Invalid async status '${path.join(asyncDir, "status.json")}': sessionId must be a string.`);
|
|
234
238
|
}
|
|
@@ -357,6 +361,7 @@ function statusToSummary(asyncDir: string, status: AsyncStatus & { cwd?: string
|
|
|
357
361
|
...(status.parentWorkflowRunId ? { parentWorkflowRunId: status.parentWorkflowRunId } : {}),
|
|
358
362
|
...(status.workflowKey ? { workflowKey: status.workflowKey } : {}),
|
|
359
363
|
...(status.workflow ? { workflow: status.workflow } : {}),
|
|
364
|
+
...(workflowChildren ? { workflowChildren } : {}),
|
|
360
365
|
...(status.sessionDir ? { sessionDir: status.sessionDir } : {}),
|
|
361
366
|
...(status.outputFile ? { outputFile: status.outputFile } : {}),
|
|
362
367
|
...(status.totalTokens ? { totalTokens: status.totalTokens } : {}),
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
2
|
import { resultFilePath } from "./result-files.ts";
|
|
3
|
-
import type { AcceptanceLedger, AsyncStatus, CostSummary, ModelAttempt } from "../../shared/types.ts";
|
|
3
|
+
import type { AcceptanceLedger, ArtifactPaths, AsyncStatus, CostSummary, ModelAttempt } from "../../shared/types.ts";
|
|
4
4
|
import { readStatus } from "../../shared/utils.ts";
|
|
5
5
|
|
|
6
6
|
export interface ImportedAsyncRoot {
|
|
@@ -27,6 +27,10 @@ export interface ImportedAsyncRootResult {
|
|
|
27
27
|
structuredOutputPath?: string;
|
|
28
28
|
structuredOutputSchemaPath?: string;
|
|
29
29
|
acceptance?: AcceptanceLedger;
|
|
30
|
+
artifactPaths?: ArtifactPaths;
|
|
31
|
+
outputSaveError?: string;
|
|
32
|
+
transcriptPath?: string;
|
|
33
|
+
transcriptError?: string;
|
|
30
34
|
timedOut?: boolean;
|
|
31
35
|
stopped?: boolean;
|
|
32
36
|
}
|
|
@@ -56,6 +60,10 @@ interface AsyncResultFile {
|
|
|
56
60
|
structuredOutputPath?: string;
|
|
57
61
|
structuredOutputSchemaPath?: string;
|
|
58
62
|
acceptance?: AcceptanceLedger;
|
|
63
|
+
artifactPaths?: ArtifactPaths;
|
|
64
|
+
outputSaveError?: string;
|
|
65
|
+
transcriptPath?: string;
|
|
66
|
+
transcriptError?: string;
|
|
59
67
|
}>;
|
|
60
68
|
}
|
|
61
69
|
|
|
@@ -118,6 +126,7 @@ function outputFromTerminalStatus(root: ImportedAsyncRoot, status: AsyncStatus,
|
|
|
118
126
|
...(step?.structuredOutputPath ? { structuredOutputPath: step.structuredOutputPath } : {}),
|
|
119
127
|
...(step?.structuredOutputSchemaPath ? { structuredOutputSchemaPath: step.structuredOutputSchemaPath } : {}),
|
|
120
128
|
...(step?.acceptance ? { acceptance: step.acceptance } : {}),
|
|
129
|
+
...(step?.transcriptPath ? { transcriptPath: step.transcriptPath } : {}),
|
|
121
130
|
};
|
|
122
131
|
}
|
|
123
132
|
|
|
@@ -136,6 +145,7 @@ function outputFromTimeout(root: ImportedAsyncRoot, status: AsyncStatus | null,
|
|
|
136
145
|
...(step?.modelAttempts ? { modelAttempts: step.modelAttempts } : {}),
|
|
137
146
|
...(step?.contextOverflow ? { contextOverflow: true } : {}),
|
|
138
147
|
...(step?.totalCost ? { totalCost: step.totalCost } : {}),
|
|
148
|
+
...(step?.transcriptPath ? { transcriptPath: step.transcriptPath } : {}),
|
|
139
149
|
};
|
|
140
150
|
}
|
|
141
151
|
|
|
@@ -168,6 +178,10 @@ function buildImportedResult(root: ImportedAsyncRoot, status: AsyncStatus | null
|
|
|
168
178
|
...(child?.structuredOutputPath ?? step?.structuredOutputPath ? { structuredOutputPath: child?.structuredOutputPath ?? step?.structuredOutputPath } : {}),
|
|
169
179
|
...(child?.structuredOutputSchemaPath ?? step?.structuredOutputSchemaPath ? { structuredOutputSchemaPath: child?.structuredOutputSchemaPath ?? step?.structuredOutputSchemaPath } : {}),
|
|
170
180
|
...(child?.acceptance ?? step?.acceptance ? { acceptance: child?.acceptance ?? step?.acceptance } : {}),
|
|
181
|
+
...(child?.artifactPaths ? { artifactPaths: child.artifactPaths } : {}),
|
|
182
|
+
...(child?.outputSaveError ? { outputSaveError: child.outputSaveError } : {}),
|
|
183
|
+
...(child?.transcriptPath ?? step?.transcriptPath ? { transcriptPath: child?.transcriptPath ?? step?.transcriptPath } : {}),
|
|
184
|
+
...(child?.transcriptError ? { transcriptError: child.transcriptError } : {}),
|
|
171
185
|
};
|
|
172
186
|
}
|
|
173
187
|
|