pi-cohort 7.1.2 → 7.2.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 +6 -0
- package/README.md +6 -0
- package/package.json +3 -3
- package/src/execution-backend/child-host-controller.ts +10 -8
- package/src/extension/doctor.ts +6 -2
- package/src/extension/fanout-child.ts +2 -2
- package/src/extension/index.ts +8 -6
- package/src/runs/background/async-execution.ts +154 -87
- package/src/runs/background/async-job-tracker.ts +4 -1
- package/src/runs/background/async-resume.ts +93 -17
- package/src/runs/background/async-status.ts +12 -9
- package/src/runs/background/external-attempt.ts +6 -2
- package/src/runs/background/run-id-resolver.ts +15 -18
- package/src/runs/background/run-status.ts +22 -11
- package/src/runs/background/stale-run-reconciler.ts +9 -1
- package/src/runs/background/subagent-runner.ts +54 -79
- package/src/runs/foreground/chain-execution.ts +7 -0
- package/src/runs/foreground/execution.ts +30 -13
- package/src/runs/foreground/external-single-attempt.ts +3 -2
- package/src/runs/foreground/subagent-executor.ts +142 -63
- package/src/runs/shared/external-execution.ts +2 -0
- package/src/runs/shared/jiti-cli.ts +17 -48
- package/src/runs/shared/nested-events.ts +103 -30
- package/src/runs/shared/pi-spawn.ts +75 -67
- package/src/shared/agent-dir.ts +12 -0
- package/src/shared/atomic-json.ts +12 -1
- package/src/shared/fork-context.ts +15 -3
- package/src/shared/types.ts +11 -1
- package/src/shared/utils.ts +1 -6
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { spawn, spawnSync } from "node:child_process";
|
|
2
|
+
import { PINNED_AGENT_DIR_OVERRIDE } from "../../shared/agent-dir.ts";
|
|
2
3
|
import * as fs from "node:fs";
|
|
3
4
|
import * as path from "node:path";
|
|
4
5
|
import { pathToFileURL } from "node:url";
|
|
5
6
|
import type { Message } from "@earendil-works/pi-ai";
|
|
6
7
|
import { writeAtomicJson } from "../../shared/atomic-json.ts";
|
|
7
8
|
import { appendJsonl, getArtifactPaths } from "../../shared/artifacts.ts";
|
|
8
|
-
import {
|
|
9
|
+
import { getPiSpawnCommand, parsePiRuntime, PiRuntimeLaunchError, type PiRuntimeIdentity } from "../shared/pi-spawn.ts";
|
|
9
10
|
import { captureSingleOutputSnapshot, finalizeSingleOutput, formatSavedOutputReference, resolveSingleOutput, type SingleOutputSnapshot } from "../shared/single-output.ts";
|
|
10
11
|
import {
|
|
11
12
|
type ActivityState,
|
|
@@ -110,8 +111,7 @@ interface SubagentRunConfig {
|
|
|
110
111
|
sessionDir?: string;
|
|
111
112
|
asyncDir: string;
|
|
112
113
|
sessionId?: string | null;
|
|
113
|
-
|
|
114
|
-
piArgv1?: string;
|
|
114
|
+
runtime: PiRuntimeIdentity;
|
|
115
115
|
forwardedFlags?: string[];
|
|
116
116
|
worktreeSetupHook?: string;
|
|
117
117
|
worktreeSetupHookTimeoutMs?: number;
|
|
@@ -244,21 +244,18 @@ function runPiStreaming(
|
|
|
244
244
|
args: string[],
|
|
245
245
|
cwd: string,
|
|
246
246
|
outputFile: string,
|
|
247
|
-
env
|
|
248
|
-
|
|
249
|
-
piArgv1?: string,
|
|
247
|
+
env: Record<string, string | undefined> | undefined,
|
|
248
|
+
runtime: PiRuntimeIdentity,
|
|
250
249
|
maxSubagentDepth?: number,
|
|
251
250
|
childEventContext?: ChildEventContext,
|
|
252
251
|
registerInterrupt?: (interrupt: (() => void) | undefined) => void,
|
|
253
252
|
onChildEvent?: (event: ChildEvent) => void,
|
|
254
253
|
): Promise<RunPiStreamingResult> {
|
|
255
|
-
return new Promise((resolve) => {
|
|
254
|
+
return new Promise((resolve, reject) => {
|
|
255
|
+
const spawnSpec = getPiSpawnCommand(args, runtime);
|
|
256
256
|
const outputStream = fs.createWriteStream(outputFile, { flags: "w" });
|
|
257
257
|
const spawnEnv = { ...process.env, ...(env ?? {}), ...getSubagentDepthEnv(maxSubagentDepth) };
|
|
258
|
-
|
|
259
|
-
...(piPackageRoot ? { piPackageRoot } : {}),
|
|
260
|
-
...(piArgv1 ? { argv1: piArgv1 } : {}),
|
|
261
|
-
});
|
|
258
|
+
if (PINNED_AGENT_DIR_OVERRIDE) spawnEnv.PI_CODING_AGENT_DIR = PINNED_AGENT_DIR_OVERRIDE;
|
|
262
259
|
const child = spawn(spawnSpec.command, spawnSpec.args, {
|
|
263
260
|
cwd,
|
|
264
261
|
stdio: ["ignore", "pipe", "pipe"],
|
|
@@ -431,6 +428,7 @@ function runPiStreaming(
|
|
|
431
428
|
clearDrainTimers();
|
|
432
429
|
});
|
|
433
430
|
child.on("close", (exitCode, signal) => {
|
|
431
|
+
if (settled) return;
|
|
434
432
|
settled = true;
|
|
435
433
|
registerInterrupt?.(undefined);
|
|
436
434
|
clearDrainTimers();
|
|
@@ -460,22 +458,13 @@ function runPiStreaming(
|
|
|
460
458
|
clearDrainTimers();
|
|
461
459
|
clearStdioGuard();
|
|
462
460
|
outputStream.end();
|
|
463
|
-
|
|
464
|
-
const spawnErrorMessage = spawnError instanceof Error ? spawnError.message : String(spawnError);
|
|
465
|
-
resolve({ stderr, exitCode: 1, messages, usage, model, error: error ?? assistantError ?? spawnErrorMessage, finalOutput, observedMutationAttempt });
|
|
461
|
+
reject(new PiRuntimeLaunchError(`Failed to spawn selected Pi runtime ${runtime.nodeExecPath} ${runtime.piEntryPath}: ${String(spawnError)}`));
|
|
466
462
|
});
|
|
467
463
|
});
|
|
468
464
|
}
|
|
469
465
|
|
|
470
|
-
function
|
|
471
|
-
const
|
|
472
|
-
if (root) return root;
|
|
473
|
-
throw new Error(`Could not resolve ${PI_CODING_AGENT_PACKAGE} package root`);
|
|
474
|
-
}
|
|
475
|
-
|
|
476
|
-
async function exportSessionHtml(sessionFile: string, outputDir: string, piPackageRoot?: string): Promise<string> {
|
|
477
|
-
const pkgRoot = piPackageRoot ?? resolvePiPackageRootFallback();
|
|
478
|
-
const exportModulePath = path.join(pkgRoot, "dist", "core", "export-html", "index.js");
|
|
466
|
+
async function exportSessionHtml(sessionFile: string, outputDir: string, piPackageRoot: string): Promise<string> {
|
|
467
|
+
const exportModulePath = path.join(piPackageRoot, "dist", "core", "export-html", "index.js");
|
|
479
468
|
const moduleUrl = pathToFileURL(exportModulePath).href;
|
|
480
469
|
const mod = await import(moduleUrl);
|
|
481
470
|
const exportFromFile = (mod as { exportFromFile?: (inputPath: string, options?: { outputPath?: string }) => string })
|
|
@@ -587,8 +576,7 @@ export interface SingleStepContext {
|
|
|
587
576
|
flatIndex: number;
|
|
588
577
|
flatStepCount: number;
|
|
589
578
|
outputFile: string;
|
|
590
|
-
|
|
591
|
-
piArgv1?: string;
|
|
579
|
+
runtime: PiRuntimeIdentity;
|
|
592
580
|
forwardedFlags?: string[];
|
|
593
581
|
registerInterrupt?: (interrupt: (() => void) | undefined) => void;
|
|
594
582
|
nestedRoute?: NestedRouteInfo;
|
|
@@ -636,6 +624,7 @@ export async function runSingleStep(
|
|
|
636
624
|
output: message,
|
|
637
625
|
exitCode: 1,
|
|
638
626
|
error: message,
|
|
627
|
+
sessionFile: step.sessionFile,
|
|
639
628
|
};
|
|
640
629
|
}
|
|
641
630
|
|
|
@@ -673,11 +662,11 @@ export async function runSingleStep(
|
|
|
673
662
|
const attemptNotes: string[] = [];
|
|
674
663
|
const eventsPath = path.join(path.dirname(ctx.outputFile), "events.jsonl");
|
|
675
664
|
let finalResult: RunPiStreamingResult | undefined;
|
|
665
|
+
let finalSessionFile = step.sessionFile;
|
|
676
666
|
let finalOutputSnapshot: SingleOutputSnapshot | undefined;
|
|
677
667
|
let completionGuardTriggeredFinal = false;
|
|
678
668
|
let executionSurface: ExecutionSurfaceResult | undefined;
|
|
679
669
|
|
|
680
|
-
const asyncDir = path.dirname(ctx.outputFile);
|
|
681
670
|
let externalOwner: ExternalExecution | undefined;
|
|
682
671
|
|
|
683
672
|
if (backendSelection.selection.kind === "external") {
|
|
@@ -687,6 +676,7 @@ export async function runSingleStep(
|
|
|
687
676
|
const ownerController = new AbortController();
|
|
688
677
|
try {
|
|
689
678
|
externalOwner = await createExternalExecution({
|
|
679
|
+
runtime: ctx.runtime,
|
|
690
680
|
runId: ctx.id,
|
|
691
681
|
childId: `${step.agent}-${ctx.flatIndex}`,
|
|
692
682
|
backend: selection.backend,
|
|
@@ -695,8 +685,8 @@ export async function runSingleStep(
|
|
|
695
685
|
signal: ownerController.signal,
|
|
696
686
|
});
|
|
697
687
|
} catch (startErr) {
|
|
698
|
-
const message = safeExternalFailure("startup", startErr);
|
|
699
|
-
return { agent: step.agent, output: message, exitCode: 1, error: message };
|
|
688
|
+
const message = startErr instanceof PiRuntimeLaunchError ? startErr.message : safeExternalFailure("startup", startErr);
|
|
689
|
+
return { agent: step.agent, output: message, exitCode: 1, error: message, sessionFile: step.sessionFile };
|
|
700
690
|
}
|
|
701
691
|
|
|
702
692
|
// Bridge registerInterrupt to an AbortController for the current external attempt.
|
|
@@ -718,7 +708,9 @@ export async function runSingleStep(
|
|
|
718
708
|
// External background: baseArgs=[] (no --mode json or -p); task is the last arg.
|
|
719
709
|
// Core allocates a trusted session file for every external attempt regardless of sessionEnabled.
|
|
720
710
|
// Caller owns tempDir cleanup (F1): cleanupTempDir is in finally, not in the helper.
|
|
721
|
-
|
|
711
|
+
if (!step.sessionFile) fs.mkdirSync(ctx.sessionDir!, { recursive: true });
|
|
712
|
+
const attemptSessionFile = step.sessionFile ?? path.join(ctx.sessionDir!, `session-${ctx.flatIndex}-attempt-${index}.jsonl`);
|
|
713
|
+
finalSessionFile = attemptSessionFile;
|
|
722
714
|
const { args, env, tempDir } = buildPiArgs({
|
|
723
715
|
baseArgs: [],
|
|
724
716
|
task,
|
|
@@ -754,6 +746,7 @@ export async function runSingleStep(
|
|
|
754
746
|
try {
|
|
755
747
|
try {
|
|
756
748
|
run = await runExternalBackgroundAttempt({
|
|
749
|
+
runtime: ctx.runtime,
|
|
757
750
|
owner: externalOwner,
|
|
758
751
|
attemptId: `${ctx.id}-${ctx.flatIndex}-attempt-${index}`,
|
|
759
752
|
args,
|
|
@@ -764,6 +757,7 @@ export async function runSingleStep(
|
|
|
764
757
|
onChildEvent: ctx.onChildEvent,
|
|
765
758
|
});
|
|
766
759
|
} catch (error) {
|
|
760
|
+
if (error instanceof PiRuntimeLaunchError) throw error;
|
|
767
761
|
run = {
|
|
768
762
|
stderr: "",
|
|
769
763
|
exitCode: 1,
|
|
@@ -840,7 +834,7 @@ export async function runSingleStep(
|
|
|
840
834
|
attemptNotes.push(formatModelAttemptNote(attempt, candidates[index + 1]));
|
|
841
835
|
}
|
|
842
836
|
} catch (error) {
|
|
843
|
-
const message = safeExternalFailure("attempt", error);
|
|
837
|
+
const message = error instanceof PiRuntimeLaunchError ? error.message : safeExternalFailure("attempt", error);
|
|
844
838
|
finalResult = {
|
|
845
839
|
stderr: "",
|
|
846
840
|
exitCode: 1,
|
|
@@ -854,7 +848,6 @@ export async function runSingleStep(
|
|
|
854
848
|
ctx.registerInterrupt?.(undefined);
|
|
855
849
|
}
|
|
856
850
|
} else {
|
|
857
|
-
// Native path: unchanged, uses runPiStreaming.
|
|
858
851
|
for (let index = 0; index < candidates.length; index++) {
|
|
859
852
|
const candidate = candidates[index];
|
|
860
853
|
ctx.onAttemptStart?.({ model: candidate, thinking: resolveEffectiveThinking(candidate, step.thinking) });
|
|
@@ -898,13 +891,16 @@ export async function runSingleStep(
|
|
|
898
891
|
step.cwd ?? ctx.cwd,
|
|
899
892
|
ctx.outputFile,
|
|
900
893
|
env,
|
|
901
|
-
ctx.
|
|
902
|
-
ctx.piArgv1,
|
|
894
|
+
ctx.runtime,
|
|
903
895
|
step.maxSubagentDepth,
|
|
904
896
|
{ eventsPath, runId: ctx.id, stepIndex: ctx.flatIndex, agent: step.agent },
|
|
905
897
|
ctx.registerInterrupt,
|
|
906
898
|
ctx.onChildEvent,
|
|
907
899
|
);
|
|
900
|
+
} catch (error) {
|
|
901
|
+
if (!(error instanceof PiRuntimeLaunchError)) throw error;
|
|
902
|
+
finalResult = { stderr: "", exitCode: 1, messages: [], usage: emptyUsage(), error: error.message, finalOutput: "" };
|
|
903
|
+
break;
|
|
908
904
|
} finally {
|
|
909
905
|
cleanupTempDir(tempDir); // F1: ensure no prompt-dir leak on throw
|
|
910
906
|
}
|
|
@@ -1086,7 +1082,7 @@ export async function runSingleStep(
|
|
|
1086
1082
|
output: outputForSummary,
|
|
1087
1083
|
exitCode: effectiveFinalExitCode,
|
|
1088
1084
|
error: effectiveFinalError,
|
|
1089
|
-
sessionFile:
|
|
1085
|
+
sessionFile: finalSessionFile ?? (sessionEnabled && sessionDir ? findLatestSessionFile(sessionDir) ?? undefined : undefined),
|
|
1090
1086
|
model: finalResult?.model,
|
|
1091
1087
|
attemptedModels: attemptedModels.length > 0 ? attemptedModels : undefined,
|
|
1092
1088
|
modelAttempts,
|
|
@@ -1229,6 +1225,7 @@ function ensureParallelProgressFile(cwd: string, group: Extract<RunnerStep, { pa
|
|
|
1229
1225
|
}
|
|
1230
1226
|
|
|
1231
1227
|
async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
1228
|
+
config.runtime = parsePiRuntime(config.runtime, `Async run ${config.id}`);
|
|
1232
1229
|
const { id, steps, resultPath, cwd, placeholder, taskIndex, totalTasks, maxOutput, artifactsDir, artifactConfig } =
|
|
1233
1230
|
config;
|
|
1234
1231
|
let previousOutput = "";
|
|
@@ -1253,7 +1250,6 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
1253
1250
|
let activityTimer: NodeJS.Timeout | undefined;
|
|
1254
1251
|
let previousCumulativeTokens: TokenUsage = { input: 0, output: 0, total: 0 };
|
|
1255
1252
|
let previousCumulativeCost = 0;
|
|
1256
|
-
let latestSessionFile: string | undefined;
|
|
1257
1253
|
|
|
1258
1254
|
const parallelGroups: Array<{ start: number; count: number; stepIndex: number }> = [];
|
|
1259
1255
|
const initialStatusSteps: RunnerStatusStep[] = [];
|
|
@@ -1317,6 +1313,7 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
1317
1313
|
|| shareEnabled
|
|
1318
1314
|
|| flatSteps.some((step) => Boolean(step.sessionFile));
|
|
1319
1315
|
const statusPayload: RunnerStatusPayload = {
|
|
1316
|
+
runtime: config.runtime,
|
|
1320
1317
|
runId: id,
|
|
1321
1318
|
...(config.sessionId ? { sessionId: config.sessionId } : {}),
|
|
1322
1319
|
mode: config.resultMode ?? (flatSteps.length > 1 ? "chain" : "single"),
|
|
@@ -1874,8 +1871,7 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
1874
1871
|
artifactsDir, artifactConfig, id,
|
|
1875
1872
|
flatIndex: fi, flatStepCount: Math.max(statusPayload.steps.length, 1),
|
|
1876
1873
|
outputFile: path.join(asyncDir, `output-${fi}.log`),
|
|
1877
|
-
|
|
1878
|
-
piArgv1: config.piArgv1,
|
|
1874
|
+
runtime: config.runtime,
|
|
1879
1875
|
forwardedFlags: config.forwardedFlags,
|
|
1880
1876
|
executionBackendCoordinator,
|
|
1881
1877
|
executionBackendConfig,
|
|
@@ -1891,6 +1887,7 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
1891
1887
|
statusPayload.steps[fi].endedAt = taskEndTime;
|
|
1892
1888
|
statusPayload.steps[fi].durationMs = taskEndTime - taskStartTime;
|
|
1893
1889
|
statusPayload.steps[fi].exitCode = singleResult.exitCode;
|
|
1890
|
+
statusPayload.steps[fi].sessionFile = singleResult.sessionFile;
|
|
1894
1891
|
statusPayload.steps[fi].model = singleResult.model;
|
|
1895
1892
|
statusPayload.steps[fi].thinking = resolveEffectiveThinking(singleResult.model, statusPayload.steps[fi].thinking);
|
|
1896
1893
|
statusPayload.steps[fi].attemptedModels = singleResult.attemptedModels;
|
|
@@ -2111,7 +2108,7 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
2111
2108
|
}));
|
|
2112
2109
|
|
|
2113
2110
|
const taskSessionDir = config.sessionDir
|
|
2114
|
-
? path.join(config.sessionDir, `parallel-${
|
|
2111
|
+
? path.join(config.sessionDir, `parallel-${fi}`)
|
|
2115
2112
|
: undefined;
|
|
2116
2113
|
const { taskForRun, taskCwd } = prepareParallelTaskRun(task, cwd, worktreeSetup, taskIdx);
|
|
2117
2114
|
|
|
@@ -2122,8 +2119,7 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
2122
2119
|
artifactsDir, artifactConfig, id,
|
|
2123
2120
|
flatIndex: fi, flatStepCount: flatSteps.length,
|
|
2124
2121
|
outputFile: path.join(asyncDir, `output-${fi}.log`),
|
|
2125
|
-
|
|
2126
|
-
piArgv1: config.piArgv1,
|
|
2122
|
+
runtime: config.runtime,
|
|
2127
2123
|
forwardedFlags: config.forwardedFlags,
|
|
2128
2124
|
executionBackendCoordinator,
|
|
2129
2125
|
executionBackendConfig,
|
|
@@ -2134,9 +2130,6 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
2134
2130
|
onAttemptStart: (attempt) => updateStepModel(fi, attempt.model, attempt.thinking),
|
|
2135
2131
|
onChildEvent: (event) => updateStepFromChildEvent(fi, event),
|
|
2136
2132
|
});
|
|
2137
|
-
if (task.sessionFile) {
|
|
2138
|
-
latestSessionFile = task.sessionFile;
|
|
2139
|
-
}
|
|
2140
2133
|
|
|
2141
2134
|
const taskEndTime = Date.now();
|
|
2142
2135
|
const taskDuration = taskEndTime - taskStartTime;
|
|
@@ -2145,6 +2138,7 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
2145
2138
|
statusPayload.steps[fi].endedAt = taskEndTime;
|
|
2146
2139
|
statusPayload.steps[fi].durationMs = taskDuration;
|
|
2147
2140
|
statusPayload.steps[fi].exitCode = singleResult.exitCode;
|
|
2141
|
+
statusPayload.steps[fi].sessionFile = singleResult.sessionFile;
|
|
2148
2142
|
statusPayload.steps[fi].model = singleResult.model;
|
|
2149
2143
|
statusPayload.steps[fi].thinking = resolveEffectiveThinking(singleResult.model, statusPayload.steps[fi].thinking);
|
|
2150
2144
|
statusPayload.steps[fi].attemptedModels = singleResult.attemptedModels;
|
|
@@ -2186,7 +2180,7 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
2186
2180
|
for (let t = 0; t < group.parallel.length; t++) {
|
|
2187
2181
|
const fi = groupStartFlatIndex + t;
|
|
2188
2182
|
const sessionTokens = config.sessionDir
|
|
2189
|
-
? parseSessionTokens(path.join(config.sessionDir, `parallel-${
|
|
2183
|
+
? parseSessionTokens(path.join(config.sessionDir, `parallel-${fi}`))
|
|
2190
2184
|
: null;
|
|
2191
2185
|
const taskTokens = sessionTokens ?? tokenUsageFromAttempts(parallelResults[t]?.modelAttempts);
|
|
2192
2186
|
if (!taskTokens) continue;
|
|
@@ -2286,12 +2280,11 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
2286
2280
|
const singleResult = await runSingleStep(seqStep, {
|
|
2287
2281
|
previousOutput, placeholder, cwd, sessionEnabled,
|
|
2288
2282
|
outputs,
|
|
2289
|
-
sessionDir: config.sessionDir,
|
|
2283
|
+
sessionDir: config.sessionDir ? path.join(config.sessionDir, `parallel-${flatIndex}`) : undefined,
|
|
2290
2284
|
artifactsDir, artifactConfig, id,
|
|
2291
2285
|
flatIndex, flatStepCount: flatSteps.length,
|
|
2292
2286
|
outputFile: path.join(asyncDir, `output-${flatIndex}.log`),
|
|
2293
|
-
|
|
2294
|
-
piArgv1: config.piArgv1,
|
|
2287
|
+
runtime: config.runtime,
|
|
2295
2288
|
forwardedFlags: config.forwardedFlags,
|
|
2296
2289
|
executionBackendCoordinator,
|
|
2297
2290
|
executionBackendConfig,
|
|
@@ -2302,9 +2295,6 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
2302
2295
|
onAttemptStart: (attempt) => updateStepModel(flatIndex, attempt.model, attempt.thinking),
|
|
2303
2296
|
onChildEvent: (event) => updateStepFromChildEvent(flatIndex, event),
|
|
2304
2297
|
});
|
|
2305
|
-
if (seqStep.sessionFile) {
|
|
2306
|
-
latestSessionFile = seqStep.sessionFile;
|
|
2307
|
-
}
|
|
2308
2298
|
|
|
2309
2299
|
previousOutput = singleResult.output;
|
|
2310
2300
|
results.push({
|
|
@@ -2333,26 +2323,15 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
2333
2323
|
}
|
|
2334
2324
|
statusPayload.outputs = outputs;
|
|
2335
2325
|
|
|
2336
|
-
const
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
previousCumulativeTokens = cumulativeTokens;
|
|
2346
|
-
previousCumulativeCost = cumulativeTokens.cost;
|
|
2347
|
-
} else {
|
|
2348
|
-
stepTokens = tokenUsageFromAttempts(singleResult.modelAttempts);
|
|
2349
|
-
if (stepTokens) {
|
|
2350
|
-
previousCumulativeTokens = {
|
|
2351
|
-
input: previousCumulativeTokens.input + stepTokens.input,
|
|
2352
|
-
output: previousCumulativeTokens.output + stepTokens.output,
|
|
2353
|
-
total: previousCumulativeTokens.total + stepTokens.total,
|
|
2354
|
-
};
|
|
2355
|
-
}
|
|
2326
|
+
const sessionTokens = config.sessionDir ? parseSessionTokens(path.join(config.sessionDir, `parallel-${flatIndex}`)) : null;
|
|
2327
|
+
const stepTokens = sessionTokens ?? tokenUsageFromAttempts(singleResult.modelAttempts);
|
|
2328
|
+
if (stepTokens) {
|
|
2329
|
+
previousCumulativeTokens = {
|
|
2330
|
+
input: previousCumulativeTokens.input + stepTokens.input,
|
|
2331
|
+
output: previousCumulativeTokens.output + stepTokens.output,
|
|
2332
|
+
total: previousCumulativeTokens.total + stepTokens.total,
|
|
2333
|
+
};
|
|
2334
|
+
previousCumulativeCost += sessionTokens?.cost ?? 0;
|
|
2356
2335
|
}
|
|
2357
2336
|
|
|
2358
2337
|
const stepEndTime = Date.now();
|
|
@@ -2360,6 +2339,7 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
2360
2339
|
statusPayload.steps[flatIndex].endedAt = stepEndTime;
|
|
2361
2340
|
statusPayload.steps[flatIndex].durationMs = stepEndTime - stepStartTime;
|
|
2362
2341
|
statusPayload.steps[flatIndex].exitCode = singleResult.exitCode;
|
|
2342
|
+
statusPayload.steps[flatIndex].sessionFile = singleResult.sessionFile;
|
|
2363
2343
|
statusPayload.steps[flatIndex].model = singleResult.model;
|
|
2364
2344
|
statusPayload.steps[flatIndex].thinking = resolveEffectiveThinking(singleResult.model, statusPayload.steps[flatIndex].thinking);
|
|
2365
2345
|
statusPayload.steps[flatIndex].attemptedModels = singleResult.attemptedModels;
|
|
@@ -2427,22 +2407,16 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
2427
2407
|
: resultMode === "parallel"
|
|
2428
2408
|
? `parallel:${flatSteps.map((s) => s.agent).join("+")}`
|
|
2429
2409
|
: `chain:${flatSteps.map((s) => s.agent).join("->")}`;
|
|
2430
|
-
|
|
2410
|
+
const sessionFile = results.findLast((result) => result.sessionFile)?.sessionFile;
|
|
2431
2411
|
let shareUrl: string | undefined;
|
|
2432
2412
|
let gistUrl: string | undefined;
|
|
2433
2413
|
let shareError: string | undefined;
|
|
2434
2414
|
|
|
2435
2415
|
if (shareEnabled) {
|
|
2436
|
-
sessionFile = config.sessionDir
|
|
2437
|
-
? (findLatestSessionFile(config.sessionDir) ?? undefined)
|
|
2438
|
-
: undefined;
|
|
2439
|
-
if (!sessionFile && latestSessionFile) {
|
|
2440
|
-
sessionFile = latestSessionFile;
|
|
2441
|
-
}
|
|
2442
2416
|
if (sessionFile) {
|
|
2443
2417
|
try {
|
|
2444
2418
|
const exportDir = config.sessionDir ?? path.dirname(sessionFile);
|
|
2445
|
-
const htmlPath = await exportSessionHtml(sessionFile, exportDir, config.piPackageRoot);
|
|
2419
|
+
const htmlPath = await exportSessionHtml(sessionFile, exportDir, config.runtime.piPackageRoot);
|
|
2446
2420
|
const share = createShareLink(htmlPath);
|
|
2447
2421
|
if ("error" in share) shareError = share.error;
|
|
2448
2422
|
else {
|
|
@@ -2461,7 +2435,7 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
2461
2435
|
clearInterval(activityTimer);
|
|
2462
2436
|
activityTimer = undefined;
|
|
2463
2437
|
}
|
|
2464
|
-
const effectiveSessionFile = sessionFile
|
|
2438
|
+
const effectiveSessionFile = sessionFile;
|
|
2465
2439
|
const runEndedAt = Date.now();
|
|
2466
2440
|
statusPayload.state = interrupted ? "paused" : results.every((r) => r.success) ? "complete" : "failed";
|
|
2467
2441
|
statusPayload.activityState = undefined;
|
|
@@ -2509,6 +2483,7 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
2509
2483
|
|
|
2510
2484
|
try {
|
|
2511
2485
|
writeAtomicJson(resultPath, {
|
|
2486
|
+
runtime: config.runtime,
|
|
2512
2487
|
id,
|
|
2513
2488
|
agent: agentName,
|
|
2514
2489
|
mode: resultMode,
|
|
@@ -32,6 +32,7 @@ import {
|
|
|
32
32
|
} from "../../shared/settings.ts";
|
|
33
33
|
import { discoverAvailableSkills, normalizeSkillInput } from "../../agents/skills.ts";
|
|
34
34
|
import { runSync } from "./execution.ts";
|
|
35
|
+
import type { PiRuntimeIdentity } from "../shared/pi-spawn.ts";
|
|
35
36
|
import { buildChainSummary } from "../../shared/formatters.ts";
|
|
36
37
|
import { compactForegroundDetails, getSingleResultOutput, mapConcurrent, resolveChildCwd } from "../../shared/utils.ts";
|
|
37
38
|
import { recordRun } from "../shared/run-history.ts";
|
|
@@ -85,6 +86,7 @@ interface ChainExecutionDetailsInput {
|
|
|
85
86
|
}
|
|
86
87
|
|
|
87
88
|
interface ParallelChainRunInput {
|
|
89
|
+
runtime: PiRuntimeIdentity;
|
|
88
90
|
step: ParallelStep;
|
|
89
91
|
parallelTemplates: string[];
|
|
90
92
|
parallelBehaviors: ResolvedStepBehavior[];
|
|
@@ -260,6 +262,7 @@ async function runParallelChainTasks(input: ParallelChainRunInput): Promise<Sing
|
|
|
260
262
|
? createStructuredOutputRuntime(task.outputSchema, path.join(input.chainDir, "structured-output"))
|
|
261
263
|
: undefined;
|
|
262
264
|
const result = await runSync(input.ctx.cwd, input.agents, task.agent, taskStr, {
|
|
265
|
+
runtime: input.runtime,
|
|
263
266
|
cwd: taskCwd,
|
|
264
267
|
signal: input.signal,
|
|
265
268
|
interruptSignal: interruptController.signal,
|
|
@@ -347,6 +350,7 @@ async function runParallelChainTasks(input: ParallelChainRunInput): Promise<Sing
|
|
|
347
350
|
}
|
|
348
351
|
|
|
349
352
|
interface ChainExecutionParams {
|
|
353
|
+
runtime: PiRuntimeIdentity;
|
|
350
354
|
chain: ChainStep[];
|
|
351
355
|
task?: string;
|
|
352
356
|
agents: AgentConfig[];
|
|
@@ -629,6 +633,7 @@ export async function executeChain(params: ChainExecutionParams): Promise<ChainE
|
|
|
629
633
|
createParallelDirs(chainDir, stepIndex, step.parallel.length, agentNames);
|
|
630
634
|
|
|
631
635
|
const parallelResults = await runParallelChainTasks({
|
|
636
|
+
runtime: params.runtime,
|
|
632
637
|
step,
|
|
633
638
|
parallelTemplates,
|
|
634
639
|
parallelBehaviors,
|
|
@@ -822,6 +827,7 @@ export async function executeChain(params: ChainExecutionParams): Promise<ChainE
|
|
|
822
827
|
progressCreated = ensureParallelProgressFile(chainDir, progressCreated, parallelBehaviors);
|
|
823
828
|
createParallelDirs(chainDir, stepIndex, dynamicParallelStep.parallel.length, dynamicParallelStep.parallel.map((task) => task.agent));
|
|
824
829
|
const parallelResults = await runParallelChainTasks({
|
|
830
|
+
runtime: params.runtime,
|
|
825
831
|
step: dynamicParallelStep,
|
|
826
832
|
parallelTemplates,
|
|
827
833
|
parallelBehaviors,
|
|
@@ -1021,6 +1027,7 @@ export async function executeChain(params: ChainExecutionParams): Promise<ChainE
|
|
|
1021
1027
|
? createStructuredOutputRuntime(seqStep.outputSchema, path.join(chainDir, "structured-output"))
|
|
1022
1028
|
: undefined;
|
|
1023
1029
|
const r = await runSync(ctx.cwd, agents, seqStep.agent, stepTask, {
|
|
1030
|
+
runtime: params.runtime,
|
|
1024
1031
|
cwd: resolveChildCwd(cwd ?? ctx.cwd, seqStep.cwd),
|
|
1025
1032
|
signal,
|
|
1026
1033
|
interruptSignal: interruptController.signal,
|
|
@@ -41,7 +41,8 @@ import {
|
|
|
41
41
|
} from "../../shared/utils.ts";
|
|
42
42
|
import { buildSkillInjection, resolveSkillsWithFallback } from "../../agents/skills.ts";
|
|
43
43
|
import { blockedLine } from "../shared/completion-guard.ts";
|
|
44
|
-
import { getPiSpawnCommand } from "../shared/pi-spawn.ts";
|
|
44
|
+
import { getPiSpawnCommand, PiRuntimeLaunchError } from "../shared/pi-spawn.ts";
|
|
45
|
+
import { PINNED_AGENT_DIR_OVERRIDE } from "../../shared/agent-dir.ts";
|
|
45
46
|
import { createJsonlWriter } from "../../shared/jsonl-writer.ts";
|
|
46
47
|
import { attachPostExitStdioGuard, trySignalChild } from "../../shared/post-exit-stdio-guard.ts";
|
|
47
48
|
import { buildPiArgs, cleanupTempDir } from "../shared/pi-args.ts";
|
|
@@ -170,11 +171,11 @@ async function runSingleAttempt(
|
|
|
170
171
|
let interruptedByControl = false;
|
|
171
172
|
let pendingControlEvents: ControlEvent[] = init.pendingControlEvents;
|
|
172
173
|
const controlConfig = init.controlConfig;
|
|
173
|
-
const spawnEnv = { ...process.env, ...sharedEnv, ...getSubagentDepthEnv(options.maxSubagentDepth) };
|
|
174
|
+
const spawnEnv = { ...process.env, ...sharedEnv, ...getSubagentDepthEnv(options.maxSubagentDepth), ...(PINNED_AGENT_DIR_OVERRIDE ? { PI_CODING_AGENT_DIR: PINNED_AGENT_DIR_OVERRIDE } : {}) };
|
|
174
175
|
let observedMutationAttempt = false;
|
|
175
176
|
|
|
176
|
-
const exitCode = await new Promise<number>((resolve) => {
|
|
177
|
-
const spawnSpec = getPiSpawnCommand(args);
|
|
177
|
+
const exitCode = await new Promise<number>((resolve, reject) => {
|
|
178
|
+
const spawnSpec = getPiSpawnCommand(args, options.runtime);
|
|
178
179
|
const proc = spawn(spawnSpec.command, spawnSpec.args, {
|
|
179
180
|
cwd: options.cwd ?? runtimeCwd,
|
|
180
181
|
env: spawnEnv,
|
|
@@ -553,6 +554,7 @@ async function runSingleAttempt(
|
|
|
553
554
|
if (!result.error) {
|
|
554
555
|
result.error = error instanceof Error ? error.message : String(error);
|
|
555
556
|
}
|
|
557
|
+
reject(new PiRuntimeLaunchError(`${options.runtime.nodeExecPath} ${options.runtime.piEntryPath}: ${error.message}`));
|
|
556
558
|
finish(1);
|
|
557
559
|
});
|
|
558
560
|
|
|
@@ -591,7 +593,7 @@ async function runSingleAttempt(
|
|
|
591
593
|
removeInterruptListener = () => options.interruptSignal?.removeEventListener("abort", interrupt);
|
|
592
594
|
}
|
|
593
595
|
}
|
|
594
|
-
});
|
|
596
|
+
}).finally(() => cleanupTempDir(tempDir));
|
|
595
597
|
result.exitCode = exitCode;
|
|
596
598
|
return finalizeSingleAttempt(result, progress, {
|
|
597
599
|
agent,
|
|
@@ -640,6 +642,12 @@ export async function runSync(
|
|
|
640
642
|
};
|
|
641
643
|
}
|
|
642
644
|
|
|
645
|
+
try {
|
|
646
|
+
getPiSpawnCommand([], options.runtime);
|
|
647
|
+
} catch (error) {
|
|
648
|
+
return failedResult(agentName, task, error instanceof Error ? error.message : String(error));
|
|
649
|
+
}
|
|
650
|
+
|
|
643
651
|
const childCwd = options.cwd ?? runtimeCwd;
|
|
644
652
|
let backendSelection: ExecutionBackendSelectionResult;
|
|
645
653
|
try {
|
|
@@ -717,6 +725,7 @@ export async function runSync(
|
|
|
717
725
|
try {
|
|
718
726
|
externalOwner = await (dependencies.createExternalOwner ?? createExternalExecution)({
|
|
719
727
|
backend: backendSelection.selection.backend,
|
|
728
|
+
runtime: options.runtime,
|
|
720
729
|
runId: options.runId,
|
|
721
730
|
childId,
|
|
722
731
|
cwd: childCwd,
|
|
@@ -724,8 +733,8 @@ export async function runSync(
|
|
|
724
733
|
signal: options.signal ?? NEVER_ABORTED_SIGNAL,
|
|
725
734
|
});
|
|
726
735
|
await externalOwner.ready;
|
|
727
|
-
} catch {
|
|
728
|
-
lastResult = failedResult(agentName, task, safeExternalFailure("startup"));
|
|
736
|
+
} catch (error) {
|
|
737
|
+
lastResult = failedResult(agentName, task, error instanceof PiRuntimeLaunchError ? error.message : safeExternalFailure("startup"));
|
|
729
738
|
}
|
|
730
739
|
}
|
|
731
740
|
|
|
@@ -746,6 +755,7 @@ export async function runSync(
|
|
|
746
755
|
originalTask: task,
|
|
747
756
|
};
|
|
748
757
|
let result: SingleResult;
|
|
758
|
+
let runtimeFailure = false;
|
|
749
759
|
if (externalOwner) {
|
|
750
760
|
try {
|
|
751
761
|
result = await (dependencies.runExternalAttempt ?? runExternalSingleAttempt)({
|
|
@@ -758,13 +768,20 @@ export async function runSync(
|
|
|
758
768
|
options,
|
|
759
769
|
shared,
|
|
760
770
|
});
|
|
761
|
-
} catch {
|
|
762
|
-
|
|
771
|
+
} catch (error) {
|
|
772
|
+
runtimeFailure = error instanceof PiRuntimeLaunchError;
|
|
773
|
+
result = failedResult(agentName, task, runtimeFailure ? (error as Error).message : safeExternalFailure("attempt"));
|
|
763
774
|
}
|
|
764
775
|
} else {
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
776
|
+
try {
|
|
777
|
+
result = await (dependencies.runNativeAttempt ?? runSingleAttempt)(
|
|
778
|
+
runtimeCwd, effectiveAgent, taskWithAcceptance, candidate, options, shared,
|
|
779
|
+
);
|
|
780
|
+
} catch (error) {
|
|
781
|
+
if (!(error instanceof PiRuntimeLaunchError)) throw error;
|
|
782
|
+
runtimeFailure = true;
|
|
783
|
+
result = failedResult(agentName, task, error.message);
|
|
784
|
+
}
|
|
768
785
|
}
|
|
769
786
|
lastResult = result;
|
|
770
787
|
sumUsage(aggregateUsage, result.usage);
|
|
@@ -782,7 +799,7 @@ export async function runSync(
|
|
|
782
799
|
if (attemptSucceeded) {
|
|
783
800
|
break;
|
|
784
801
|
}
|
|
785
|
-
if (blockedLine(result.error ?? "") || !isRetryableModelFailure(result.error) || i === modelsToTry.length - 1) {
|
|
802
|
+
if (runtimeFailure || blockedLine(result.error ?? "") || !isRetryableModelFailure(result.error) || i === modelsToTry.length - 1) {
|
|
786
803
|
break;
|
|
787
804
|
}
|
|
788
805
|
attemptNotes.push(formatModelAttemptNote(attempt, modelsToTry[i + 1]));
|
|
@@ -12,6 +12,7 @@ import type { RunSyncOptions, SingleResult, Usage } from "../../shared/types.ts"
|
|
|
12
12
|
import { getSubagentDepthEnv } from "../../shared/types.ts";
|
|
13
13
|
import { buildPiArgs, cleanupTempDir } from "../shared/pi-args.ts";
|
|
14
14
|
import { getPiSpawnCommand } from "../shared/pi-spawn.ts";
|
|
15
|
+
import { PINNED_AGENT_DIR_OVERRIDE } from "../../shared/agent-dir.ts";
|
|
15
16
|
import { isMutatingTool } from "../shared/long-running-guard.ts";
|
|
16
17
|
import type { ExternalAttemptResult, ExternalExecution } from "../shared/external-execution.ts";
|
|
17
18
|
import { type AttemptSharedInit, finalizeSingleAttempt, initializeAttempt } from "./attempt-finalization.ts";
|
|
@@ -104,8 +105,8 @@ export async function runExternalSingleAttempt(input: RunExternalSingleAttemptIn
|
|
|
104
105
|
args.push("--extension", REPORTING_EXTENSION_PATH);
|
|
105
106
|
if (taskArg !== undefined) args.push(taskArg);
|
|
106
107
|
|
|
107
|
-
const spawnSpec = getPiSpawnCommand(args);
|
|
108
|
-
const mergedEnv: Record<string, string | undefined> = { ...process.env, ...sharedEnv, ...getSubagentDepthEnv(options.maxSubagentDepth) };
|
|
108
|
+
const spawnSpec = getPiSpawnCommand(args, options.runtime);
|
|
109
|
+
const mergedEnv: Record<string, string | undefined> = { ...process.env, ...sharedEnv, ...getSubagentDepthEnv(options.maxSubagentDepth), ...(PINNED_AGENT_DIR_OVERRIDE ? { PI_CODING_AGENT_DIR: PINNED_AGENT_DIR_OVERRIDE } : {}) };
|
|
109
110
|
const environment: Record<string, string> = {};
|
|
110
111
|
for (const [key, value] of Object.entries(mergedEnv)) {
|
|
111
112
|
if (value !== undefined) environment[key] = value;
|