pi-cohort 7.1.2 → 7.2.1
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 +12 -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 +10 -45
- 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/notify.ts +2 -13
- 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
|
@@ -8,10 +8,11 @@
|
|
|
8
8
|
* before the task arg and forwards all other args unchanged.
|
|
9
9
|
*/
|
|
10
10
|
import * as path from "node:path";
|
|
11
|
+
import { PINNED_AGENT_DIR_OVERRIDE } from "../../shared/agent-dir.ts";
|
|
11
12
|
import { fileURLToPath } from "node:url";
|
|
12
13
|
import type { Message } from "@earendil-works/pi-ai";
|
|
13
14
|
import type { Usage } from "../../shared/types.ts";
|
|
14
|
-
import { getPiSpawnCommand } from "../shared/pi-spawn.ts";
|
|
15
|
+
import { getPiSpawnCommand, type PiRuntimeIdentity } from "../shared/pi-spawn.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 { OnNewSessionMessage } from "../../execution-backend/session-watcher.ts";
|
|
@@ -36,6 +37,7 @@ interface SynthesizedChildEvent {
|
|
|
36
37
|
export type OnSynthesizedChildEvent = (event: SynthesizedChildEvent) => void;
|
|
37
38
|
|
|
38
39
|
export interface RunExternalBackgroundAttemptInput {
|
|
40
|
+
readonly runtime: PiRuntimeIdentity;
|
|
39
41
|
readonly owner: ExternalExecution;
|
|
40
42
|
readonly attemptId: string;
|
|
41
43
|
/**
|
|
@@ -148,13 +150,15 @@ export async function runExternalBackgroundAttempt(
|
|
|
148
150
|
args.push("--extension", REPORTING_EXTENSION_PATH);
|
|
149
151
|
if (taskArg !== undefined) args.push(taskArg);
|
|
150
152
|
|
|
151
|
-
const spawnSpec = getPiSpawnCommand(args);
|
|
153
|
+
const spawnSpec = getPiSpawnCommand(args, input.runtime);
|
|
152
154
|
|
|
153
155
|
const mergedEnv: Record<string, string> = {};
|
|
154
156
|
for (const [key, value] of Object.entries(environment)) {
|
|
155
157
|
if (value !== undefined) mergedEnv[key] = value;
|
|
156
158
|
}
|
|
157
159
|
|
|
160
|
+
if (PINNED_AGENT_DIR_OVERRIDE) mergedEnv.PI_CODING_AGENT_DIR = PINNED_AGENT_DIR_OVERRIDE;
|
|
161
|
+
|
|
158
162
|
const onNewSessionMessage: OnNewSessionMessage | undefined = onChildEvent
|
|
159
163
|
? (message) => synthesizeChildEvents(message, onChildEvent)
|
|
160
164
|
: undefined;
|
|
@@ -40,18 +40,7 @@ interface SubagentResult {
|
|
|
40
40
|
totalTasks?: number;
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
-
export default function registerSubagentNotify(pi: ExtensionAPI): void {
|
|
44
|
-
const unsubscribeStoreKey = "__pi_subagents_notify_unsubscribe__";
|
|
45
|
-
const globalStore = globalThis as Record<string, unknown>;
|
|
46
|
-
const previousUnsubscribe = globalStore[unsubscribeStoreKey];
|
|
47
|
-
if (typeof previousUnsubscribe === "function") {
|
|
48
|
-
try {
|
|
49
|
-
previousUnsubscribe();
|
|
50
|
-
} catch {
|
|
51
|
-
// Best effort cleanup for stale handlers from an older reload.
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
|
|
43
|
+
export default function registerSubagentNotify(pi: ExtensionAPI): () => void {
|
|
55
44
|
const seen = getGlobalSeenMap("__pi_subagents_notify_seen__");
|
|
56
45
|
const ttlMs = 10 * 60 * 1000;
|
|
57
46
|
|
|
@@ -104,5 +93,5 @@ export default function registerSubagentNotify(pi: ExtensionAPI): void {
|
|
|
104
93
|
);
|
|
105
94
|
};
|
|
106
95
|
|
|
107
|
-
|
|
96
|
+
return pi.events.on(SUBAGENT_ASYNC_COMPLETE_EVENT, handleComplete);
|
|
108
97
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
2
|
import * as path from "node:path";
|
|
3
|
-
import { ASYNC_DIR, RESULTS_DIR, type SubagentState } from "../../shared/types.ts";
|
|
4
|
-
import { findAsyncRunPrefixMatches, type AsyncRunLocation } from "./async-resume.ts";
|
|
3
|
+
import { ASYNC_DIR, LEGACY_ASYNC_DIR, RESULTS_DIR, type SubagentState } from "../../shared/types.ts";
|
|
4
|
+
import { findAsyncRunPrefixMatches, resolveAsyncRunLocation, winningAsyncRunEntries, type AsyncRunLocation } from "./async-resume.ts";
|
|
5
5
|
import { assertSafeNestedId, findNestedRunMatchesById, type NestedRoute, type NestedRunMatch, type NestedRunResolutionScope } from "../shared/nested-events.ts";
|
|
6
6
|
|
|
7
7
|
export type ResolvedSubagentRunId =
|
|
@@ -11,20 +11,17 @@ export type ResolvedSubagentRunId =
|
|
|
11
11
|
|
|
12
12
|
export interface ResolveSubagentRunIdDeps {
|
|
13
13
|
state?: SubagentState;
|
|
14
|
-
asyncDirRoot?: string;
|
|
14
|
+
asyncDirRoot?: string | readonly string[];
|
|
15
15
|
resultsDir?: string;
|
|
16
16
|
nested?: NestedRunResolutionScope;
|
|
17
|
+
readOnlyLegacy?: boolean;
|
|
17
18
|
}
|
|
18
19
|
|
|
19
|
-
function exactAsyncLocation(id: string, asyncDirRoot: string, resultsDir: string): AsyncRunLocation | undefined {
|
|
20
|
-
const
|
|
20
|
+
function exactAsyncLocation(id: string, asyncDirRoot: string | readonly string[], resultsDir: string): AsyncRunLocation | undefined {
|
|
21
|
+
const exact = winningAsyncRunEntries(asyncDirRoot, id).find((entry) => entry.id === id);
|
|
21
22
|
const resultPath = path.join(resultsDir, `${id}.json`);
|
|
22
|
-
if (!
|
|
23
|
-
return {
|
|
24
|
-
asyncDir: fs.existsSync(asyncDir) ? asyncDir : null,
|
|
25
|
-
resultPath: fs.existsSync(resultPath) ? resultPath : null,
|
|
26
|
-
resolvedId: id,
|
|
27
|
-
};
|
|
23
|
+
if (!exact && !fs.existsSync(resultPath)) return undefined;
|
|
24
|
+
return resolveAsyncRunLocation({ id }, asyncDirRoot, resultsDir);
|
|
28
25
|
}
|
|
29
26
|
|
|
30
27
|
function foregroundIds(state: SubagentState | undefined): string[] {
|
|
@@ -37,7 +34,7 @@ function nestedScopeFromState(state: SubagentState | undefined): NestedRunResolu
|
|
|
37
34
|
const seen = new Set<string>();
|
|
38
35
|
const add = (route: NestedRoute | undefined) => {
|
|
39
36
|
if (!route) return;
|
|
40
|
-
const key =
|
|
37
|
+
const key = JSON.stringify([route.rootRunId, route.capabilityToken, route.eventSink, route.controlInbox]);
|
|
41
38
|
if (seen.has(key)) return;
|
|
42
39
|
seen.add(key);
|
|
43
40
|
routes.push(route);
|
|
@@ -47,20 +44,21 @@ function nestedScopeFromState(state: SubagentState | undefined): NestedRunResolu
|
|
|
47
44
|
return { routes };
|
|
48
45
|
}
|
|
49
46
|
|
|
50
|
-
function asyncPrefixMatches(prefix: string, asyncDirRoot: string, resultsDir: string): Array<{ id: string; location: AsyncRunLocation }> {
|
|
47
|
+
function asyncPrefixMatches(prefix: string, asyncDirRoot: string | readonly string[], resultsDir: string): Array<{ id: string; location: AsyncRunLocation }> {
|
|
51
48
|
return findAsyncRunPrefixMatches(prefix, asyncDirRoot, resultsDir);
|
|
52
49
|
}
|
|
53
50
|
|
|
54
51
|
export function resolveSubagentRunId(id: string, deps: ResolveSubagentRunIdDeps = {}): ResolvedSubagentRunId | undefined {
|
|
55
52
|
assertSafeNestedId("id", id);
|
|
56
|
-
const asyncDirRoot = deps.asyncDirRoot ?? ASYNC_DIR;
|
|
53
|
+
const asyncDirRoot = deps.asyncDirRoot ?? [ASYNC_DIR, LEGACY_ASYNC_DIR];
|
|
57
54
|
const resultsDir = deps.resultsDir ?? RESULTS_DIR;
|
|
58
55
|
|
|
59
56
|
const nestedScope = deps.nested ?? nestedScopeFromState(deps.state);
|
|
60
57
|
if (deps.state?.foregroundControls.has(id)) return { kind: "foreground", id };
|
|
61
58
|
const exactAsync = exactAsyncLocation(id, asyncDirRoot, resultsDir);
|
|
62
59
|
if (exactAsync) return { kind: "async", id, location: exactAsync };
|
|
63
|
-
const
|
|
60
|
+
const nestedOptions = { scope: nestedScope, readOnlyLegacy: deps.readOnlyLegacy };
|
|
61
|
+
const exactNested = findNestedRunMatchesById(id, nestedOptions);
|
|
64
62
|
if (exactNested.length > 1) throw new Error(`Nested run id '${id}' is ambiguous across authorized registries. Provide the full id after stale registries are cleaned up.`);
|
|
65
63
|
if (exactNested[0]) return { kind: "nested", id, match: exactNested[0] };
|
|
66
64
|
|
|
@@ -71,11 +69,10 @@ export function resolveSubagentRunId(id: string, deps: ResolveSubagentRunIdDeps
|
|
|
71
69
|
for (const match of asyncPrefixMatches(id, asyncDirRoot, resultsDir)) {
|
|
72
70
|
matches.push({ kind: "async", id: match.id, location: match.location });
|
|
73
71
|
}
|
|
74
|
-
for (const match of findNestedRunMatchesById(id,
|
|
72
|
+
for (const match of findNestedRunMatchesById(id, { ...nestedOptions, prefix: true })) {
|
|
75
73
|
matches.push({ kind: "nested", id: match.run.id, match });
|
|
76
74
|
}
|
|
77
|
-
const
|
|
78
|
-
const values = [...unique.values()];
|
|
75
|
+
const values = matches;
|
|
79
76
|
if (values.length > 1) {
|
|
80
77
|
throw new Error(`Ambiguous subagent run id prefix '${id}' matched: ${values.map((match) => `${match.kind}:${match.id}`).join(", ")}. Provide a longer id.`);
|
|
81
78
|
}
|
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
2
|
import * as path from "node:path";
|
|
3
3
|
import type { AgentToolResult } from "@earendil-works/pi-agent-core";
|
|
4
|
+
import { readStatus } from "../../shared/utils.ts";
|
|
4
5
|
import { formatAsyncRunList, formatAsyncRunOutputPath, formatAsyncRunProgressLabel, listAsyncRuns } from "./async-status.ts";
|
|
5
6
|
import { formatNestedRunStatusLines } from "../shared/nested-render.ts";
|
|
6
7
|
import { formatModelThinking } from "../../shared/formatters.ts";
|
|
7
8
|
import { formatActivityLabel } from "../../shared/status-format.ts";
|
|
8
|
-
import { ASYNC_DIR, RESULTS_DIR, type AsyncStatus, type Details, type NestedRunSummary, type SubagentState } from "../../shared/types.ts";
|
|
9
|
-
import { resolveAsyncRunLocation } from "./async-resume.ts";
|
|
9
|
+
import { ASYNC_DIR, LEGACY_ASYNC_DIR, RESULTS_DIR, type AsyncStatus, type Details, type NestedRunSummary, type SubagentState } from "../../shared/types.ts";
|
|
10
|
+
import { isLegacyAsyncDir, resolveAsyncRunLocation } from "./async-resume.ts";
|
|
10
11
|
import { resolveSubagentRunId } from "./run-id-resolver.ts";
|
|
11
12
|
import { flatToLogicalStepIndex, normalizeParallelGroups } from "./parallel-groups.ts";
|
|
12
13
|
import { readRunnerLogTail, reconcileAsyncRun, reconcileNestedAsyncDescendants } from "./stale-run-reconciler.ts";
|
|
13
|
-
import { attachRootChildrenToSteps, findNestedRouteForRootId, projectNestedRegistryForRoot, type NestedRunResolutionScope } from "../shared/nested-events.ts";
|
|
14
|
+
import { attachRootChildrenToSteps, findNestedRouteForRootId, isLegacyNestedRoute, projectNestedRegistryForRoot, type NestedRunResolutionScope } from "../shared/nested-events.ts";
|
|
14
15
|
|
|
15
16
|
interface RunStatusParams {
|
|
16
17
|
action?: "status";
|
|
@@ -20,7 +21,7 @@ interface RunStatusParams {
|
|
|
20
21
|
}
|
|
21
22
|
|
|
22
23
|
interface RunStatusDeps {
|
|
23
|
-
asyncDirRoot?: string;
|
|
24
|
+
asyncDirRoot?: string | readonly string[];
|
|
24
25
|
resultsDir?: string;
|
|
25
26
|
kill?: (pid: number, signal?: NodeJS.Signals | 0) => boolean;
|
|
26
27
|
now?: () => number;
|
|
@@ -98,7 +99,7 @@ function formatNestedExactStatus(rootRunId: string, run: NestedRunSummary): stri
|
|
|
98
99
|
}
|
|
99
100
|
|
|
100
101
|
export function inspectSubagentStatus(params: RunStatusParams, deps: RunStatusDeps = {}): AgentToolResult<Details> {
|
|
101
|
-
const asyncDirRoot = deps.asyncDirRoot ?? ASYNC_DIR;
|
|
102
|
+
const asyncDirRoot = deps.asyncDirRoot ?? [ASYNC_DIR, LEGACY_ASYNC_DIR];
|
|
102
103
|
const resultsDir = deps.resultsDir ?? RESULTS_DIR;
|
|
103
104
|
if (!params.id && !params.runId && !params.dir) {
|
|
104
105
|
if (deps.nested) {
|
|
@@ -128,10 +129,10 @@ export function inspectSubagentStatus(params: RunStatusParams, deps: RunStatusDe
|
|
|
128
129
|
try {
|
|
129
130
|
const requestedId = params.id ?? params.runId;
|
|
130
131
|
if (!params.dir && requestedId) {
|
|
131
|
-
const resolved = resolveSubagentRunId(requestedId, { asyncDirRoot, resultsDir, state: deps.state, nested: deps.nested });
|
|
132
|
+
const resolved = resolveSubagentRunId(requestedId, { asyncDirRoot, resultsDir, state: deps.state, nested: deps.nested, readOnlyLegacy: true });
|
|
132
133
|
if (resolved?.kind === "nested") {
|
|
133
|
-
reconcileNestedAsyncDescendants(resolved.match.route, { resultsDir, kill: deps.kill, now: deps.now });
|
|
134
|
-
const refreshed = resolveSubagentRunId(requestedId, { asyncDirRoot, resultsDir, state: deps.state, nested: deps.nested });
|
|
134
|
+
if (!isLegacyNestedRoute(resolved.match.route)) reconcileNestedAsyncDescendants(resolved.match.route, { resultsDir, kill: deps.kill, now: deps.now });
|
|
135
|
+
const refreshed = resolveSubagentRunId(requestedId, { asyncDirRoot, resultsDir, state: deps.state, nested: deps.nested, readOnlyLegacy: true });
|
|
135
136
|
const nested = refreshed?.kind === "nested" ? refreshed : resolved;
|
|
136
137
|
return { content: [{ type: "text", text: formatNestedExactStatus(nested.match.rootRunId, nested.match.run) }], details: { mode: "single", results: [] } };
|
|
137
138
|
}
|
|
@@ -161,7 +162,17 @@ export function inspectSubagentStatus(params: RunStatusParams, deps: RunStatusDe
|
|
|
161
162
|
if (asyncDir) {
|
|
162
163
|
let reconciliation;
|
|
163
164
|
try {
|
|
164
|
-
|
|
165
|
+
const statusPath = path.join(asyncDir, "status.json");
|
|
166
|
+
let stored: AsyncStatus | null = null;
|
|
167
|
+
try {
|
|
168
|
+
stored = readStatus(asyncDir);
|
|
169
|
+
if (fs.existsSync(statusPath) && (!stored || typeof stored !== "object" || Array.isArray(stored))) throw new Error(`Invalid async status '${statusPath}': expected an object.`);
|
|
170
|
+
} catch (error) {
|
|
171
|
+
if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error;
|
|
172
|
+
}
|
|
173
|
+
reconciliation = isLegacyAsyncDir(asyncDir) || (stored && stored.state !== "running" && stored.state !== "queued")
|
|
174
|
+
? { status: stored, message: undefined, resultPath }
|
|
175
|
+
: reconcileAsyncRun(asyncDir, { resultsDir, kill: deps.kill, now: deps.now });
|
|
165
176
|
} catch (error) {
|
|
166
177
|
const message = error instanceof Error ? error.message : String(error);
|
|
167
178
|
return {
|
|
@@ -179,8 +190,8 @@ export function inspectSubagentStatus(params: RunStatusParams, deps: RunStatusDe
|
|
|
179
190
|
let nestedWarning: string | undefined;
|
|
180
191
|
try {
|
|
181
192
|
const nestedRoute = findNestedRouteForRootId(status.runId);
|
|
182
|
-
if (nestedRoute) reconcileNestedAsyncDescendants(nestedRoute, { resultsDir, kill: deps.kill, now: deps.now });
|
|
183
|
-
nestedChildren = projectNestedRegistryForRoot(status.runId)?.children ?? [];
|
|
193
|
+
if (nestedRoute && !isLegacyAsyncDir(asyncDir) && !isLegacyNestedRoute(nestedRoute)) reconcileNestedAsyncDescendants(nestedRoute, { resultsDir, kill: deps.kill, now: deps.now });
|
|
194
|
+
nestedChildren = projectNestedRegistryForRoot(status.runId, { readOnlyLegacy: true })?.children ?? [];
|
|
184
195
|
attachRootChildrenToSteps(status.runId, status.steps, nestedChildren);
|
|
185
196
|
} catch (error) {
|
|
186
197
|
nestedWarning = `Nested status unavailable: ${error instanceof Error ? error.message : String(error)}`;
|
|
@@ -240,6 +240,9 @@ function buildFailedRepair(status: AsyncStatus, asyncDir: string, now: number, r
|
|
|
240
240
|
asyncDir,
|
|
241
241
|
sessionId: status.sessionId,
|
|
242
242
|
sessionFile: status.sessionFile,
|
|
243
|
+
sessionDir: status.sessionDir,
|
|
244
|
+
runtime: status.runtime,
|
|
245
|
+
cwd: status.cwd,
|
|
243
246
|
},
|
|
244
247
|
};
|
|
245
248
|
}
|
|
@@ -326,8 +329,13 @@ export function reconcileAsyncRun(asyncDir: string, options: ReconcileAsyncRunOp
|
|
|
326
329
|
|
|
327
330
|
const runId = effectiveStatus.runId || path.basename(asyncDir);
|
|
328
331
|
const resultPath = path.join(options.resultsDir ?? RESULTS_DIR, `${runId}.json`);
|
|
332
|
+
if (effectiveStatus.state === "queued") {
|
|
333
|
+
return { status: effectiveStatus, repaired: false, resultPath, message: "Unresolved async startup: queued run has not published running status; start a new run." };
|
|
334
|
+
}
|
|
335
|
+
if (terminal(effectiveStatus.state)) return { status: effectiveStatus, repaired: false, resultPath };
|
|
336
|
+
|
|
329
337
|
if (fs.existsSync(resultPath)) {
|
|
330
|
-
const terminalStatus = effectiveStatus.state === "running"
|
|
338
|
+
const terminalStatus = effectiveStatus.state === "running"
|
|
331
339
|
? terminalStatusFromResult(effectiveStatus, resultPath, now)
|
|
332
340
|
: undefined;
|
|
333
341
|
if (terminalStatus) {
|
|
@@ -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,
|