pi-subagents 0.23.0 → 0.24.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 +30 -0
- package/README.md +17 -79
- package/agents/reviewer.md +2 -2
- package/package.json +1 -1
- package/prompts/parallel-cleanup.md +11 -1
- package/prompts/parallel-review.md +11 -1
- package/skills/pi-subagents/SKILL.md +29 -13
- package/src/agents/agent-serializer.ts +0 -42
- package/src/agents/agents.ts +1 -1
- package/src/extension/index.ts +14 -8
- package/src/extension/schemas.ts +1 -1
- package/src/intercom/intercom-bridge.ts +4 -1
- package/src/intercom/result-intercom.ts +8 -3
- package/src/runs/background/async-execution.ts +10 -5
- package/src/runs/background/async-resume.ts +57 -31
- package/src/runs/background/async-status.ts +16 -50
- package/src/runs/background/result-watcher.ts +3 -1
- package/src/runs/background/run-status.ts +28 -26
- package/src/runs/background/stale-run-reconciler.ts +3 -0
- package/src/runs/background/subagent-runner.ts +21 -7
- package/src/runs/foreground/chain-clarify.ts +183 -218
- package/src/runs/foreground/chain-execution.ts +55 -21
- package/src/runs/foreground/execution.ts +6 -3
- package/src/runs/foreground/subagent-executor.ts +152 -20
- package/src/runs/shared/single-output.ts +21 -6
- package/src/shared/settings.ts +19 -0
- package/src/shared/status-format.ts +49 -0
- package/src/shared/types.ts +18 -5
- package/src/slash/slash-commands.ts +1 -74
- package/src/tui/render.ts +37 -61
- package/src/agents/agent-templates.ts +0 -60
- package/src/manager-ui/agent-manager-chain-detail.ts +0 -164
- package/src/manager-ui/agent-manager-detail.ts +0 -235
- package/src/manager-ui/agent-manager-edit.ts +0 -456
- package/src/manager-ui/agent-manager-list.ts +0 -283
- package/src/manager-ui/agent-manager-parallel.ts +0 -302
- package/src/manager-ui/agent-manager.ts +0 -732
- package/src/tui/subagents-status.ts +0 -621
- package/src/tui/text-editor.ts +0 -286
|
@@ -12,7 +12,7 @@ import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
|
|
12
12
|
import type { AgentConfig } from "../../agents/agents.ts";
|
|
13
13
|
import { applyThinkingSuffix } from "../shared/pi-args.ts";
|
|
14
14
|
import { injectSingleOutputInstruction, resolveSingleOutputPath, validateFileOnlyOutputMode } from "../shared/single-output.ts";
|
|
15
|
-
import { buildChainInstructions, isParallelStep, resolveStepBehavior, writeInitialProgressFile, type ChainStep, type ResolvedStepBehavior, type SequentialStep, type StepOverrides } from "../../shared/settings.ts";
|
|
15
|
+
import { buildChainInstructions, isParallelStep, resolveStepBehavior, suppressProgressForReadOnlyTask, writeInitialProgressFile, type ChainStep, type ResolvedStepBehavior, type SequentialStep, type StepOverrides } from "../../shared/settings.ts";
|
|
16
16
|
import type { RunnerStep } from "../shared/parallel-utils.ts";
|
|
17
17
|
import { resolvePiPackageRoot } from "../shared/pi-spawn.ts";
|
|
18
18
|
import { buildSkillInjection, normalizeSkillInput, resolveSkillsWithFallback } from "../../agents/skills.ts";
|
|
@@ -65,6 +65,7 @@ interface AsyncExecutionContext {
|
|
|
65
65
|
|
|
66
66
|
interface AsyncChainParams {
|
|
67
67
|
chain: ChainStep[];
|
|
68
|
+
task?: string;
|
|
68
69
|
resultMode?: Exclude<SubagentRunMode, "single">;
|
|
69
70
|
agents: AgentConfig[];
|
|
70
71
|
ctx: AsyncExecutionContext;
|
|
@@ -213,6 +214,10 @@ export function executeAsyncChain(
|
|
|
213
214
|
const chainSkills = params.chainSkills ?? [];
|
|
214
215
|
const availableModels = params.availableModels;
|
|
215
216
|
const runnerCwd = resolveChildCwd(ctx.cwd, cwd);
|
|
217
|
+
const firstStep = chain[0];
|
|
218
|
+
const originalTask = params.task ?? (firstStep
|
|
219
|
+
? (isParallelStep(firstStep) ? firstStep.parallel[0]?.task : (firstStep as SequentialStep).task)
|
|
220
|
+
: undefined);
|
|
216
221
|
|
|
217
222
|
for (const s of chain) {
|
|
218
223
|
const stepAgents = isParallelStep(s)
|
|
@@ -257,7 +262,7 @@ export function executeAsyncChain(
|
|
|
257
262
|
const a = agents.find((x) => x.name === s.agent)!;
|
|
258
263
|
const stepCwd = resolveChildCwd(runnerCwd, s.cwd);
|
|
259
264
|
const instructionCwd = behaviorCwd ?? stepCwd;
|
|
260
|
-
const behavior = resolvedBehavior ?? resolveStepBehavior(a, buildStepOverrides(s), chainSkills);
|
|
265
|
+
const behavior = suppressProgressForReadOnlyTask(resolvedBehavior ?? resolveStepBehavior(a, buildStepOverrides(s), chainSkills), s.task, originalTask);
|
|
261
266
|
const skillNames = behavior.skills === false ? [] : behavior.skills;
|
|
262
267
|
const { resolved: resolvedSkills, missing: missingSkills } = resolveSkillsWithFallback(skillNames, stepCwd, ctx.cwd);
|
|
263
268
|
if (missingSkills.includes("pi-subagents")) throw new UnavailableSubagentSkillError(UNAVAILABLE_SUBAGENT_SKILL_ERROR);
|
|
@@ -314,7 +319,7 @@ export function executeAsyncChain(
|
|
|
314
319
|
if (isParallelStep(s)) {
|
|
315
320
|
const parallelBehaviors = s.parallel.map((task) => {
|
|
316
321
|
const agent = agents.find((candidate) => candidate.name === task.agent)!;
|
|
317
|
-
return resolveStepBehavior(agent, buildStepOverrides(task), chainSkills);
|
|
322
|
+
return suppressProgressForReadOnlyTask(resolveStepBehavior(agent, buildStepOverrides(task), chainSkills), task.task, originalTask);
|
|
318
323
|
});
|
|
319
324
|
const progressPrecreated = parallelBehaviors.some((behavior) => behavior.progress);
|
|
320
325
|
if (progressPrecreated) {
|
|
@@ -436,7 +441,7 @@ export function executeAsyncChain(
|
|
|
436
441
|
|
|
437
442
|
return {
|
|
438
443
|
content: [{ type: "text", text: formatAsyncStartedMessage(`Async ${resultMode}: ${chainDesc} [${id}]`) }],
|
|
439
|
-
details: { mode: resultMode, results: [], asyncId: id, asyncDir },
|
|
444
|
+
details: { mode: resultMode, runId: id, results: [], asyncId: id, asyncDir },
|
|
440
445
|
};
|
|
441
446
|
}
|
|
442
447
|
|
|
@@ -568,6 +573,6 @@ export function executeAsyncSingle(
|
|
|
568
573
|
|
|
569
574
|
return {
|
|
570
575
|
content: [{ type: "text", text: formatAsyncStartedMessage(`Async: ${agent} [${id}]`) }],
|
|
571
|
-
details: { mode: "single", results: [], asyncId: id, asyncDir },
|
|
576
|
+
details: { mode: "single", runId: id, results: [], asyncId: id, asyncDir },
|
|
572
577
|
};
|
|
573
578
|
}
|
|
@@ -39,7 +39,7 @@ interface AsyncResultFile {
|
|
|
39
39
|
success?: boolean;
|
|
40
40
|
cwd?: string;
|
|
41
41
|
sessionFile?: string;
|
|
42
|
-
results?: Array<{ agent?: string; success?: boolean; intercomTarget?: string }>;
|
|
42
|
+
results?: Array<{ agent?: string; success?: boolean; sessionFile?: string; intercomTarget?: string }>;
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
export interface AsyncRunLocation {
|
|
@@ -59,10 +59,10 @@ function ensureObject(value: unknown, source: string): Record<string, unknown> {
|
|
|
59
59
|
return value as Record<string, unknown>;
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
-
function validateOptionalString(value: Record<string, unknown>, field: string, source: string): string | undefined {
|
|
62
|
+
function validateOptionalString(value: Record<string, unknown>, field: string, source: string, displayField = field): string | undefined {
|
|
63
63
|
const fieldValue = value[field];
|
|
64
64
|
if (fieldValue === undefined) return undefined;
|
|
65
|
-
if (typeof fieldValue !== "string") throw new Error(`Invalid async result file '${source}': ${
|
|
65
|
+
if (typeof fieldValue !== "string") throw new Error(`Invalid async result file '${source}': ${displayField} must be a string.`);
|
|
66
66
|
return fieldValue;
|
|
67
67
|
}
|
|
68
68
|
|
|
@@ -74,11 +74,12 @@ function validateResultFile(value: unknown, resultPath: string): AsyncResultFile
|
|
|
74
74
|
if (!Array.isArray(resultsValue)) throw new Error(`Invalid async result file '${resultPath}': results must be an array.`);
|
|
75
75
|
results = resultsValue.map((entry, index) => {
|
|
76
76
|
const child = ensureObject(entry, `${resultPath} results[${index}]`);
|
|
77
|
-
const agent = validateOptionalString(child, "agent",
|
|
78
|
-
const
|
|
77
|
+
const agent = validateOptionalString(child, "agent", resultPath, `results[${index}].agent`);
|
|
78
|
+
const sessionFile = validateOptionalString(child, "sessionFile", resultPath, `results[${index}].sessionFile`);
|
|
79
|
+
const intercomTarget = validateOptionalString(child, "intercomTarget", resultPath, `results[${index}].intercomTarget`);
|
|
79
80
|
const success = child.success;
|
|
80
81
|
if (success !== undefined && typeof success !== "boolean") throw new Error(`Invalid async result file '${resultPath}': results[${index}].success must be a boolean.`);
|
|
81
|
-
return { agent, intercomTarget, ...(typeof success === "boolean" ? { success } : {}) };
|
|
82
|
+
return { agent, sessionFile, intercomTarget, ...(typeof success === "boolean" ? { success } : {}) };
|
|
82
83
|
});
|
|
83
84
|
}
|
|
84
85
|
const success = data.success;
|
|
@@ -210,6 +211,7 @@ function validateStatusForResume(status: AsyncStatus | null, source: string): vo
|
|
|
210
211
|
status.steps.forEach((step, index) => {
|
|
211
212
|
if (!step || typeof step !== "object" || Array.isArray(step)) throw new Error(`Invalid async status '${source}': steps[${index}] must be an object.`);
|
|
212
213
|
if (typeof step.agent !== "string") throw new Error(`Invalid async status '${source}': steps[${index}].agent must be a string.`);
|
|
214
|
+
if (step.sessionFile !== undefined && typeof step.sessionFile !== "string") throw new Error(`Invalid async status '${source}': steps[${index}].sessionFile must be a string.`);
|
|
213
215
|
});
|
|
214
216
|
}
|
|
215
217
|
}
|
|
@@ -243,38 +245,62 @@ export function resolveAsyncResumeTarget(params: AsyncResumeParams, deps: AsyncR
|
|
|
243
245
|
const resultSteps = result?.results ?? [];
|
|
244
246
|
const stepCount = statusSteps.length || resultSteps.length || (result?.agent ? 1 : 0);
|
|
245
247
|
const requestedIndex = params.index;
|
|
248
|
+
if (requestedIndex !== undefined && !Number.isInteger(requestedIndex)) throw new Error(`Async run '${runId}' index must be an integer.`);
|
|
249
|
+
const terminalStepStatuses = new Set(["complete", "completed", "failed", "paused"]);
|
|
246
250
|
|
|
247
251
|
if (state === "running") {
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
252
|
+
if (requestedIndex !== undefined) {
|
|
253
|
+
if (requestedIndex < 0 || requestedIndex >= stepCount) throw new Error(`Async run '${runId}' has ${stepCount} children. Index ${requestedIndex} is out of range.`);
|
|
254
|
+
const selectedStep = statusSteps[requestedIndex];
|
|
255
|
+
if (selectedStep?.status === "running") {
|
|
256
|
+
return {
|
|
257
|
+
kind: "live",
|
|
258
|
+
runId,
|
|
259
|
+
asyncDir: location.asyncDir ?? undefined,
|
|
260
|
+
state,
|
|
261
|
+
agent: selectedStep.agent,
|
|
262
|
+
index: requestedIndex,
|
|
263
|
+
intercomTarget: resolveSubagentIntercomTarget(runId, selectedStep.agent, requestedIndex),
|
|
264
|
+
cwd: status?.cwd ?? result?.cwd,
|
|
265
|
+
sessionFile: selectedStep.sessionFile ?? status?.sessionFile ?? result?.sessionFile,
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
if (selectedStep?.status === "pending") throw new Error(`Async run '${runId}' child ${requestedIndex} is pending and has not started yet. Wait for it to run or complete before resuming.`);
|
|
269
|
+
if (selectedStep && !terminalStepStatuses.has(selectedStep.status)) throw new Error(`Async run '${runId}' child ${requestedIndex} is ${selectedStep.status} and cannot be revived yet.`);
|
|
270
|
+
} else {
|
|
271
|
+
const running = statusSteps
|
|
272
|
+
.map((step, index) => ({ step, index }))
|
|
273
|
+
.filter(({ step }) => step.status === "running");
|
|
274
|
+
const selected = running.length === 1 ? running[0] : undefined;
|
|
275
|
+
if (!selected) {
|
|
276
|
+
throw new Error(`Async run '${runId}' has ${running.length} running children. Provide index to choose one.`);
|
|
277
|
+
}
|
|
278
|
+
return {
|
|
279
|
+
kind: "live",
|
|
280
|
+
runId,
|
|
281
|
+
asyncDir: location.asyncDir ?? undefined,
|
|
282
|
+
state,
|
|
283
|
+
agent: selected.step.agent,
|
|
284
|
+
index: selected.index,
|
|
285
|
+
intercomTarget: resolveSubagentIntercomTarget(runId, selected.step.agent, selected.index),
|
|
286
|
+
cwd: status?.cwd ?? result?.cwd,
|
|
287
|
+
sessionFile: selected.step.sessionFile ?? status?.sessionFile ?? result?.sessionFile,
|
|
288
|
+
};
|
|
256
289
|
}
|
|
257
|
-
return {
|
|
258
|
-
kind: "live",
|
|
259
|
-
runId,
|
|
260
|
-
asyncDir: location.asyncDir ?? undefined,
|
|
261
|
-
state,
|
|
262
|
-
agent: selected.step.agent,
|
|
263
|
-
index: selected.index,
|
|
264
|
-
intercomTarget: resolveSubagentIntercomTarget(runId, selected.step.agent, selected.index),
|
|
265
|
-
cwd: status?.cwd ?? result?.cwd,
|
|
266
|
-
sessionFile: status?.sessionFile ?? result?.sessionFile,
|
|
267
|
-
};
|
|
268
290
|
}
|
|
269
291
|
|
|
270
|
-
if (stepCount
|
|
271
|
-
throw new Error(`Async run '${runId}' has ${stepCount} children.
|
|
292
|
+
if (stepCount > 1 && requestedIndex === undefined) {
|
|
293
|
+
throw new Error(`Async run '${runId}' has ${stepCount} children. Provide index to choose one.`);
|
|
272
294
|
}
|
|
273
295
|
const index = requestedIndex ?? 0;
|
|
296
|
+
if (!Number.isInteger(index)) throw new Error(`Async run '${runId}' index must be an integer.`);
|
|
297
|
+
if (index < 0 || index >= stepCount) throw new Error(`Async run '${runId}' has ${stepCount} children. Index ${index} is out of range.`);
|
|
274
298
|
const agent = statusSteps[index]?.agent ?? resultSteps[index]?.agent ?? result?.agent;
|
|
275
299
|
if (!agent) throw new Error(`Could not determine child agent for async run '${runId}'.`);
|
|
276
|
-
const sessionFile =
|
|
277
|
-
|
|
300
|
+
const sessionFile = statusSteps[index]?.sessionFile
|
|
301
|
+
?? resultSteps[index]?.sessionFile
|
|
302
|
+
?? (stepCount === 1 ? status?.sessionFile ?? result?.sessionFile : undefined);
|
|
303
|
+
if (!sessionFile) throw new Error(`Async run '${runId}' child ${index} does not have a persisted session file to resume from.`);
|
|
278
304
|
const resolvedSessionFile = validateResumeSessionFile(runId, sessionFile);
|
|
279
305
|
|
|
280
306
|
return {
|
|
@@ -292,9 +318,9 @@ export function resolveAsyncResumeTarget(params: AsyncResumeParams, deps: AsyncR
|
|
|
292
318
|
|
|
293
319
|
export function buildRevivedAsyncTask(target: AsyncResumeTarget, message: string): string {
|
|
294
320
|
return [
|
|
295
|
-
"You are reviving a
|
|
321
|
+
"You are reviving a previous subagent conversation.",
|
|
296
322
|
"",
|
|
297
|
-
`Original
|
|
323
|
+
`Original run: ${target.runId}`,
|
|
298
324
|
`Original agent: ${target.agent}`,
|
|
299
325
|
target.sessionFile ? `Original session file: ${target.sessionFile}` : undefined,
|
|
300
326
|
"",
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
2
|
import * as path from "node:path";
|
|
3
3
|
import { formatDuration, formatTokens, shortenPath } from "../../shared/formatters.ts";
|
|
4
|
-
import {
|
|
4
|
+
import { formatActivityLabel, formatParallelOutcome } from "../../shared/status-format.ts";
|
|
5
|
+
import { type ActivityState, type AsyncJobStep, type AsyncParallelGroupStatus, type AsyncStatus, type SubagentRunMode, type TokenUsage } from "../../shared/types.ts";
|
|
5
6
|
import { readStatus } from "../../shared/utils.ts";
|
|
6
7
|
import { flatToLogicalStepIndex, normalizeParallelGroups } from "./parallel-groups.ts";
|
|
7
8
|
import { reconcileAsyncRun } from "./stale-run-reconciler.ts";
|
|
@@ -9,7 +10,7 @@ import { reconcileAsyncRun } from "./stale-run-reconciler.ts";
|
|
|
9
10
|
interface AsyncRunStepSummary {
|
|
10
11
|
index: number;
|
|
11
12
|
agent: string;
|
|
12
|
-
status:
|
|
13
|
+
status: AsyncJobStep["status"];
|
|
13
14
|
activityState?: ActivityState;
|
|
14
15
|
lastActivityAt?: number;
|
|
15
16
|
currentTool?: string;
|
|
@@ -65,16 +66,6 @@ interface AsyncRunListOptions {
|
|
|
65
66
|
reconcile?: boolean;
|
|
66
67
|
}
|
|
67
68
|
|
|
68
|
-
export interface AsyncRunOverlayData {
|
|
69
|
-
active: AsyncRunSummary[];
|
|
70
|
-
recent: AsyncRunSummary[];
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
export interface AsyncRunOverlayOptions {
|
|
74
|
-
recentLimit?: number;
|
|
75
|
-
sessionId?: string;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
69
|
function getErrorMessage(error: unknown): string {
|
|
79
70
|
return error instanceof Error ? error.message : String(error);
|
|
80
71
|
}
|
|
@@ -185,9 +176,9 @@ function sortRuns(runs: AsyncRunSummary[]): AsyncRunSummary[] {
|
|
|
185
176
|
switch (state) {
|
|
186
177
|
case "running": return 0;
|
|
187
178
|
case "queued": return 1;
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
179
|
+
case "failed": return 2;
|
|
180
|
+
case "paused": return 2;
|
|
181
|
+
case "complete": return 3;
|
|
191
182
|
}
|
|
192
183
|
};
|
|
193
184
|
return [...runs].sort((a, b) => {
|
|
@@ -229,35 +220,15 @@ export function listAsyncRuns(asyncDirRoot: string, options: AsyncRunListOptions
|
|
|
229
220
|
return options.limit !== undefined ? sorted.slice(0, options.limit) : sorted;
|
|
230
221
|
}
|
|
231
222
|
|
|
232
|
-
export function listAsyncRunsForOverlay(asyncDirRoot: string, options: AsyncRunOverlayOptions = {}): AsyncRunOverlayData {
|
|
233
|
-
const recentLimit = options.recentLimit ?? 5;
|
|
234
|
-
const all = listAsyncRuns(asyncDirRoot, { sessionId: options.sessionId });
|
|
235
|
-
const recent = all
|
|
236
|
-
.filter((run) => run.state === "complete" || run.state === "failed" || run.state === "paused")
|
|
237
|
-
.sort((a, b) => (b.lastUpdate ?? b.endedAt ?? b.startedAt) - (a.lastUpdate ?? a.endedAt ?? a.startedAt))
|
|
238
|
-
.slice(0, recentLimit);
|
|
239
|
-
return {
|
|
240
|
-
active: all.filter((run) => run.state === "queued" || run.state === "running"),
|
|
241
|
-
recent,
|
|
242
|
-
};
|
|
243
|
-
}
|
|
244
|
-
|
|
245
223
|
function formatActivityFacts(input: { activityState?: ActivityState; lastActivityAt?: number; currentTool?: string; currentToolStartedAt?: number; currentPath?: string; turnCount?: number; toolCount?: number }): string | undefined {
|
|
246
224
|
const facts: string[] = [];
|
|
247
|
-
if (input.currentTool && input.currentToolStartedAt) facts.push(`tool ${input.currentTool} ${formatDuration(Math.max(0, Date.now() - input.currentToolStartedAt))}`);
|
|
225
|
+
if (input.currentTool && input.currentToolStartedAt !== undefined) facts.push(`tool ${input.currentTool} ${formatDuration(Math.max(0, Date.now() - input.currentToolStartedAt))}`);
|
|
248
226
|
else if (input.currentTool) facts.push(`tool ${input.currentTool}`);
|
|
249
227
|
if (input.currentPath) facts.push(shortenPath(input.currentPath));
|
|
250
228
|
if (input.turnCount !== undefined) facts.push(`${input.turnCount} turns`);
|
|
251
229
|
if (input.toolCount !== undefined) facts.push(`${input.toolCount} tools`);
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
if (input.activityState === "active_long_running") return ["active but long-running", ...facts].join(" | ");
|
|
255
|
-
return facts.length ? facts.join(" | ") : undefined;
|
|
256
|
-
}
|
|
257
|
-
const elapsed = formatDuration(Math.max(0, Date.now() - input.lastActivityAt));
|
|
258
|
-
if (input.activityState === "needs_attention") return [`no activity for ${elapsed}`, ...facts].join(" | ");
|
|
259
|
-
if (input.activityState === "active_long_running") return [`active but long-running; last activity ${elapsed} ago`, ...facts].join(" | ");
|
|
260
|
-
return [`active ${elapsed} ago`, ...facts].join(" | ");
|
|
230
|
+
const activity = formatActivityLabel(input.lastActivityAt, input.activityState);
|
|
231
|
+
return activity || facts.length ? [activity, ...facts].filter(Boolean).join(" | ") : undefined;
|
|
261
232
|
}
|
|
262
233
|
|
|
263
234
|
function formatStepLine(step: AsyncRunStepSummary): string {
|
|
@@ -270,16 +241,9 @@ function formatStepLine(step: AsyncRunStepSummary): string {
|
|
|
270
241
|
return parts.join(" | ");
|
|
271
242
|
}
|
|
272
243
|
|
|
273
|
-
function
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
const failed = steps.filter((step) => step.status === "failed").length;
|
|
277
|
-
const paused = steps.filter((step) => step.status === "paused").length;
|
|
278
|
-
const parts = [`${done}/${total} done`];
|
|
279
|
-
if (showRunning && running > 0) parts.unshift(running === 1 ? "1 agent running" : `${running} agents running`);
|
|
280
|
-
if (failed > 0) parts.push(`${failed} failed`);
|
|
281
|
-
if (paused > 0) parts.push(`${paused} paused`);
|
|
282
|
-
return parts.join(" · ");
|
|
244
|
+
export function formatAsyncRunOutputPath(run: Pick<AsyncRunSummary, "asyncDir" | "outputFile">): string | undefined {
|
|
245
|
+
if (!run.outputFile) return undefined;
|
|
246
|
+
return path.isAbsolute(run.outputFile) ? run.outputFile : path.join(run.asyncDir, run.outputFile);
|
|
283
247
|
}
|
|
284
248
|
|
|
285
249
|
export function formatAsyncRunProgressLabel(run: Pick<AsyncRunSummary, "mode" | "state" | "currentStep" | "chainStepCount" | "parallelGroups" | "steps">): string {
|
|
@@ -291,11 +255,11 @@ export function formatAsyncRunProgressLabel(run: Pick<AsyncRunSummary, "mode" |
|
|
|
291
255
|
: undefined;
|
|
292
256
|
if (activeGroup) {
|
|
293
257
|
const groupSteps = run.steps.slice(activeGroup.start, activeGroup.start + activeGroup.count);
|
|
294
|
-
const groupLabel =
|
|
258
|
+
const groupLabel = formatParallelOutcome(groupSteps, activeGroup.count, { showRunning: run.state === "running" });
|
|
295
259
|
if (run.mode === "parallel") return groupLabel;
|
|
296
260
|
return `step ${activeGroup.stepIndex + 1}/${chainStepCount} · parallel group: ${groupLabel}`;
|
|
297
261
|
}
|
|
298
|
-
if (run.mode === "parallel") return
|
|
262
|
+
if (run.mode === "parallel") return formatParallelOutcome(run.steps, stepCount, { showRunning: run.state === "running" });
|
|
299
263
|
if (run.mode === "chain" && run.currentStep !== undefined && groups.length > 0) {
|
|
300
264
|
const logicalStep = flatToLogicalStepIndex(run.currentStep, chainStepCount, groups);
|
|
301
265
|
return `step ${logicalStep + 1}/${chainStepCount}`;
|
|
@@ -319,6 +283,8 @@ export function formatAsyncRunList(runs: AsyncRunSummary[], heading = "Active as
|
|
|
319
283
|
for (const step of run.steps) {
|
|
320
284
|
lines.push(` ${formatStepLine(step)}`);
|
|
321
285
|
}
|
|
286
|
+
const outputPath = formatAsyncRunOutputPath(run);
|
|
287
|
+
if (outputPath) lines.push(` output: ${shortenPath(outputPath)}`);
|
|
322
288
|
if (run.sessionFile) lines.push(` session: ${shortenPath(run.sessionFile)}`);
|
|
323
289
|
lines.push("");
|
|
324
290
|
}
|
|
@@ -76,6 +76,7 @@ export function createResultWatcher(
|
|
|
76
76
|
output?: string;
|
|
77
77
|
error?: string;
|
|
78
78
|
success?: boolean;
|
|
79
|
+
sessionFile?: string;
|
|
79
80
|
artifactPaths?: { outputPath?: string };
|
|
80
81
|
intercomTarget?: string;
|
|
81
82
|
}>;
|
|
@@ -120,6 +121,7 @@ export function createResultWatcher(
|
|
|
120
121
|
const summary = result.success === false && result.error
|
|
121
122
|
? `${result.error}${hasRealOutput ? `\n\nOutput:\n${baseOutput}` : ""}`
|
|
122
123
|
: output;
|
|
124
|
+
const sessionPath = result.sessionFile ?? (childResults.length === 1 ? data.sessionFile : undefined);
|
|
123
125
|
return {
|
|
124
126
|
agent: result.agent ?? data.agent ?? `step-${index + 1}`,
|
|
125
127
|
status: resolveSubagentResultStatus({
|
|
@@ -129,7 +131,7 @@ export function createResultWatcher(
|
|
|
129
131
|
summary,
|
|
130
132
|
index,
|
|
131
133
|
artifactPath: result.artifactPaths?.outputPath,
|
|
132
|
-
sessionPath:
|
|
134
|
+
...(typeof sessionPath === "string" && fsApi.existsSync(sessionPath) ? { sessionPath } : {}),
|
|
133
135
|
intercomTarget: result.intercomTarget,
|
|
134
136
|
};
|
|
135
137
|
}),
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
2
|
import * as path from "node:path";
|
|
3
3
|
import type { AgentToolResult } from "@mariozechner/pi-agent-core";
|
|
4
|
-
import { formatAsyncRunList, formatAsyncRunProgressLabel, listAsyncRuns } from "./async-status.ts";
|
|
4
|
+
import { formatAsyncRunList, formatAsyncRunOutputPath, formatAsyncRunProgressLabel, listAsyncRuns } from "./async-status.ts";
|
|
5
|
+
import { formatActivityLabel } from "../../shared/status-format.ts";
|
|
5
6
|
import { ASYNC_DIR, RESULTS_DIR, type AsyncStatus, type Details } from "../../shared/types.ts";
|
|
6
7
|
import { resolveSubagentIntercomTarget } from "../../intercom/intercom-bridge.ts";
|
|
7
8
|
import { resolveAsyncRunLocation } from "./async-resume.ts";
|
|
@@ -22,14 +23,24 @@ interface RunStatusDeps {
|
|
|
22
23
|
now?: () => number;
|
|
23
24
|
}
|
|
24
25
|
|
|
25
|
-
function
|
|
26
|
-
|
|
27
|
-
const seconds = Math.floor(Math.max(0, Date.now() - lastActivityAt) / 1000);
|
|
28
|
-
return activityState === "needs_attention" ? `no activity for ${seconds}s` : `active ${seconds}s ago`;
|
|
26
|
+
function hasExistingSessionFile(value: unknown): value is string {
|
|
27
|
+
return typeof value === "string" && fs.existsSync(value);
|
|
29
28
|
}
|
|
30
29
|
|
|
31
|
-
function
|
|
32
|
-
|
|
30
|
+
function formatResumeGuidance(runId: string | undefined, children: Array<{ agent?: unknown; sessionFile?: unknown }>, fallbackSessionFile?: unknown): string {
|
|
31
|
+
const knownChildren = children
|
|
32
|
+
.map((child, index) => ({ child, index }))
|
|
33
|
+
.filter(({ child }) => typeof child.agent === "string");
|
|
34
|
+
if (!runId || knownChildren.length === 0) return "Resume: unavailable; no child session file was persisted.";
|
|
35
|
+
const singleSessionFile = knownChildren[0]?.child.sessionFile ?? fallbackSessionFile;
|
|
36
|
+
if (children.length === 1 && knownChildren.length === 1 && hasExistingSessionFile(singleSessionFile)) {
|
|
37
|
+
return `Revive: subagent({ action: "resume", id: "${runId}", message: "..." })`;
|
|
38
|
+
}
|
|
39
|
+
const childWithSession = knownChildren.find(({ child }) => hasExistingSessionFile(child.sessionFile));
|
|
40
|
+
if (childWithSession) {
|
|
41
|
+
return `Revive child: subagent({ action: "resume", id: "${runId}", index: ${childWithSession.index}, message: "..." })`;
|
|
42
|
+
}
|
|
43
|
+
return "Resume: unavailable; no child session file was persisted.";
|
|
33
44
|
}
|
|
34
45
|
|
|
35
46
|
function stepLineLabel(status: AsyncStatus, index: number): string {
|
|
@@ -103,6 +114,7 @@ export function inspectSubagentStatus(params: RunStatusParams, deps: RunStatusDe
|
|
|
103
114
|
const logPath = path.join(asyncDir, `subagent-log-${effectiveRunId}.md`);
|
|
104
115
|
const eventsPath = path.join(asyncDir, "events.jsonl");
|
|
105
116
|
if (status) {
|
|
117
|
+
const outputPath = formatAsyncRunOutputPath({ asyncDir, outputFile: status.outputFile });
|
|
106
118
|
const progressLabel = formatAsyncRunProgressLabel({
|
|
107
119
|
mode: status.mode,
|
|
108
120
|
state: status.state,
|
|
@@ -113,7 +125,7 @@ export function inspectSubagentStatus(params: RunStatusParams, deps: RunStatusDe
|
|
|
113
125
|
});
|
|
114
126
|
const started = new Date(status.startedAt).toISOString();
|
|
115
127
|
const updated = status.lastUpdate ? new Date(status.lastUpdate).toISOString() : "n/a";
|
|
116
|
-
const statusActivityText = status.state === "running" ?
|
|
128
|
+
const statusActivityText = status.state === "running" ? formatActivityLabel(status.lastActivityAt, status.activityState) : undefined;
|
|
117
129
|
|
|
118
130
|
const lines = [
|
|
119
131
|
`Run: ${status.runId}`,
|
|
@@ -124,27 +136,23 @@ export function inspectSubagentStatus(params: RunStatusParams, deps: RunStatusDe
|
|
|
124
136
|
`Started: ${started}`,
|
|
125
137
|
`Updated: ${updated}`,
|
|
126
138
|
`Dir: ${asyncDir}`,
|
|
139
|
+
outputPath ? `Output: ${outputPath}` : undefined,
|
|
127
140
|
reconciliation.message ? `Diagnosis: ${reconciliation.message}` : undefined,
|
|
128
141
|
reconciliation.resultPath && fs.existsSync(reconciliation.resultPath) ? `Result: ${reconciliation.resultPath}` : undefined,
|
|
129
142
|
].filter((line): line is string => Boolean(line));
|
|
130
143
|
for (const [index, step] of (status.steps ?? []).entries()) {
|
|
131
|
-
const stepActivityText = step.status === "running" ?
|
|
144
|
+
const stepActivityText = step.status === "running" ? formatActivityLabel(step.lastActivityAt, step.activityState) : undefined;
|
|
132
145
|
const errorText = step.error ? `, error: ${step.error}` : "";
|
|
133
146
|
lines.push(`${stepLineLabel(status, index)}: ${step.agent} ${step.status}${stepActivityText ? `, ${stepActivityText}` : ""}${errorText}`);
|
|
147
|
+
const stepOutputPath = path.join(asyncDir, `output-${index}.log`);
|
|
148
|
+
if (stepOutputPath !== outputPath && fs.existsSync(stepOutputPath)) lines.push(` Output: ${stepOutputPath}`);
|
|
134
149
|
if (step.status === "running") {
|
|
135
150
|
lines.push(` Intercom target: ${resolveSubagentIntercomTarget(status.runId, step.agent, index)} (if registered)`);
|
|
136
151
|
}
|
|
137
152
|
}
|
|
138
153
|
if (status.sessionFile) lines.push(`Session: ${status.sessionFile}`);
|
|
139
154
|
if (status.state !== "running") {
|
|
140
|
-
|
|
141
|
-
if (canShowRevive(stepCount, status.sessionFile)) {
|
|
142
|
-
lines.push(`Revive: subagent({ action: "resume", id: "${status.runId}", message: "..." })`);
|
|
143
|
-
} else if (stepCount > 1) {
|
|
144
|
-
lines.push("Resume: unsupported for multi-child async runs until per-child session files are persisted.");
|
|
145
|
-
} else {
|
|
146
|
-
lines.push("Resume: unavailable; no single child session file was persisted.");
|
|
147
|
-
}
|
|
155
|
+
lines.push(formatResumeGuidance(status.runId, status.steps ?? [], status.sessionFile));
|
|
148
156
|
}
|
|
149
157
|
if (fs.existsSync(logPath)) lines.push(`Log: ${logPath}`);
|
|
150
158
|
if (fs.existsSync(eventsPath)) lines.push(`Events: ${eventsPath}`);
|
|
@@ -156,18 +164,12 @@ export function inspectSubagentStatus(params: RunStatusParams, deps: RunStatusDe
|
|
|
156
164
|
if (resultPath) {
|
|
157
165
|
try {
|
|
158
166
|
const raw = fs.readFileSync(resultPath, "utf-8");
|
|
159
|
-
const data = JSON.parse(raw) as { id?: string; runId?: string; agent?: string; success?: boolean; summary?: string; exitCode?: number; state?: string; sessionFile?: string; results?: Array<{ agent?: string }> };
|
|
167
|
+
const data = JSON.parse(raw) as { id?: string; runId?: string; agent?: string; success?: boolean; summary?: string; exitCode?: number; state?: string; sessionFile?: string; results?: Array<{ agent?: string; sessionFile?: string }> };
|
|
160
168
|
const status = data.success ? "complete" : data.state === "paused" || data.exitCode === 0 ? "paused" : "failed";
|
|
161
169
|
const runId = data.runId ?? data.id ?? resolvedId;
|
|
162
170
|
const lines = [`Run: ${runId}`, `State: ${status}`, `Result: ${resultPath}`];
|
|
163
|
-
const
|
|
164
|
-
|
|
165
|
-
lines.push(`Revive: subagent({ action: "resume", id: "${runId}", message: "..." })`);
|
|
166
|
-
} else if (stepCount > 1) {
|
|
167
|
-
lines.push("Resume: unsupported for multi-child async runs until per-child session files are persisted.");
|
|
168
|
-
} else {
|
|
169
|
-
lines.push("Resume: unavailable; no single child session file was persisted.");
|
|
170
|
-
}
|
|
171
|
+
const children = Array.isArray(data.results) ? data.results : data.agent ? [{ agent: data.agent, sessionFile: data.sessionFile }] : [];
|
|
172
|
+
lines.push(formatResumeGuidance(runId, children, data.sessionFile));
|
|
171
173
|
if (data.summary) lines.push("", data.summary);
|
|
172
174
|
return { content: [{ type: "text", text: lines.join("\n") }], details: { mode: "single", results: [] } };
|
|
173
175
|
} catch (error) {
|
|
@@ -76,6 +76,7 @@ interface ResultChildOutcome {
|
|
|
76
76
|
agent?: string;
|
|
77
77
|
success?: boolean;
|
|
78
78
|
error?: string;
|
|
79
|
+
sessionFile?: string;
|
|
79
80
|
model?: string;
|
|
80
81
|
attemptedModels?: string[];
|
|
81
82
|
modelAttempts?: NonNullable<AsyncStatus["steps"]>[number]["modelAttempts"];
|
|
@@ -119,6 +120,7 @@ function terminalStatusFromResult(status: AsyncStatus, resultPath: string, now:
|
|
|
119
120
|
durationMs: step.startedAt !== undefined && step.durationMs === undefined ? Math.max(0, now - step.startedAt) : step.durationMs,
|
|
120
121
|
exitCode: step.exitCode ?? (state === "complete" || state === "paused" ? 0 : 1),
|
|
121
122
|
error: state === "failed" ? step.error ?? child?.error : step.error,
|
|
123
|
+
sessionFile: step.sessionFile ?? child?.sessionFile,
|
|
122
124
|
model: step.model ?? child?.model,
|
|
123
125
|
attemptedModels: step.attemptedModels ?? child?.attemptedModels,
|
|
124
126
|
modelAttempts: step.modelAttempts ?? child?.modelAttempts,
|
|
@@ -204,6 +206,7 @@ function buildFailedRepair(status: AsyncStatus, asyncDir: string, now: number, r
|
|
|
204
206
|
model: step.model,
|
|
205
207
|
attemptedModels: step.attemptedModels,
|
|
206
208
|
modelAttempts: step.modelAttempts,
|
|
209
|
+
sessionFile: step.sessionFile,
|
|
207
210
|
})),
|
|
208
211
|
exitCode: 1,
|
|
209
212
|
timestamp: now,
|
|
@@ -7,7 +7,7 @@ import type { Message } from "@mariozechner/pi-ai";
|
|
|
7
7
|
import { writeAtomicJson } from "../../shared/atomic-json.ts";
|
|
8
8
|
import { appendJsonl, getArtifactPaths } from "../../shared/artifacts.ts";
|
|
9
9
|
import { getPiSpawnCommand } from "../shared/pi-spawn.ts";
|
|
10
|
-
import { captureSingleOutputSnapshot, finalizeSingleOutput, formatSavedOutputReference, resolveSingleOutput } from "../shared/single-output.ts";
|
|
10
|
+
import { captureSingleOutputSnapshot, finalizeSingleOutput, formatSavedOutputReference, resolveSingleOutput, type SingleOutputSnapshot } from "../shared/single-output.ts";
|
|
11
11
|
import {
|
|
12
12
|
type ActivityState,
|
|
13
13
|
type ArtifactConfig,
|
|
@@ -100,6 +100,7 @@ interface StepResult {
|
|
|
100
100
|
error?: string;
|
|
101
101
|
success: boolean;
|
|
102
102
|
skipped?: boolean;
|
|
103
|
+
sessionFile?: string;
|
|
103
104
|
intercomTarget?: string;
|
|
104
105
|
model?: string;
|
|
105
106
|
attemptedModels?: string[];
|
|
@@ -575,6 +576,7 @@ async function runSingleStep(
|
|
|
575
576
|
modelAttempts?: ModelAttempt[];
|
|
576
577
|
artifactPaths?: ArtifactPaths;
|
|
577
578
|
interrupted?: boolean;
|
|
579
|
+
sessionFile?: string;
|
|
578
580
|
intercomTarget?: string;
|
|
579
581
|
completionGuardTriggered?: boolean;
|
|
580
582
|
}> {
|
|
@@ -582,7 +584,6 @@ async function runSingleStep(
|
|
|
582
584
|
const task = step.task.replace(placeholderRegex, () => ctx.previousOutput);
|
|
583
585
|
const sessionEnabled = Boolean(step.sessionFile) || ctx.sessionEnabled;
|
|
584
586
|
const sessionDir = step.sessionFile ? undefined : ctx.sessionDir;
|
|
585
|
-
const outputSnapshot = captureSingleOutputSnapshot(step.outputPath);
|
|
586
587
|
|
|
587
588
|
let artifactPaths: ArtifactPaths | undefined;
|
|
588
589
|
if (ctx.artifactsDir && ctx.artifactConfig?.enabled !== false) {
|
|
@@ -604,10 +605,12 @@ async function runSingleStep(
|
|
|
604
605
|
const attemptNotes: string[] = [];
|
|
605
606
|
const eventsPath = path.join(path.dirname(ctx.outputFile), "events.jsonl");
|
|
606
607
|
let finalResult: RunPiStreamingResult | undefined;
|
|
608
|
+
let finalOutputSnapshot: SingleOutputSnapshot | undefined;
|
|
607
609
|
let completionGuardTriggeredFinal = false;
|
|
608
610
|
|
|
609
611
|
for (let index = 0; index < candidates.length; index++) {
|
|
610
612
|
const candidate = candidates[index];
|
|
613
|
+
const outputSnapshot = captureSingleOutputSnapshot(step.outputPath);
|
|
611
614
|
const { args, env, tempDir } = buildPiArgs({
|
|
612
615
|
baseArgs: ["--mode", "json", "-p"],
|
|
613
616
|
task,
|
|
@@ -659,7 +662,9 @@ async function runSingleStep(
|
|
|
659
662
|
? 1
|
|
660
663
|
: hiddenError?.hasError
|
|
661
664
|
? (hiddenError.exitCode ?? 1)
|
|
662
|
-
: run.exitCode
|
|
665
|
+
: run.error && run.exitCode === 0
|
|
666
|
+
? 1
|
|
667
|
+
: run.exitCode;
|
|
663
668
|
const error = completionGuardError
|
|
664
669
|
?? (hiddenError?.hasError
|
|
665
670
|
? hiddenError.details
|
|
@@ -676,6 +681,7 @@ async function runSingleStep(
|
|
|
676
681
|
modelAttempts.push(attempt);
|
|
677
682
|
if (candidate) attemptedModels.push(candidate);
|
|
678
683
|
completionGuardTriggeredFinal = completionGuardTriggered;
|
|
684
|
+
finalOutputSnapshot = outputSnapshot;
|
|
679
685
|
finalResult = { ...run, exitCode: effectiveExitCode, model: candidate ?? run.model, error };
|
|
680
686
|
if (attempt.success || completionGuardTriggered) break;
|
|
681
687
|
if (!isRetryableModelFailure(error) || index === candidates.length - 1) break;
|
|
@@ -684,7 +690,7 @@ async function runSingleStep(
|
|
|
684
690
|
|
|
685
691
|
const rawOutput = finalResult?.finalOutput ?? "";
|
|
686
692
|
const resolvedOutput = step.outputPath && finalResult?.exitCode === 0
|
|
687
|
-
? resolveSingleOutput(step.outputPath, rawOutput,
|
|
693
|
+
? resolveSingleOutput(step.outputPath, rawOutput, finalOutputSnapshot)
|
|
688
694
|
: { fullOutput: rawOutput };
|
|
689
695
|
const output = resolvedOutput.fullOutput;
|
|
690
696
|
const outputReference = resolvedOutput.savedPath ? formatSavedOutputReference(resolvedOutput.savedPath, output) : undefined;
|
|
@@ -731,6 +737,7 @@ async function runSingleStep(
|
|
|
731
737
|
output: outputForSummary,
|
|
732
738
|
exitCode: finalResult?.exitCode ?? 1,
|
|
733
739
|
error: finalResult?.error,
|
|
740
|
+
sessionFile: step.sessionFile,
|
|
734
741
|
intercomTarget: ctx.childIntercomTarget,
|
|
735
742
|
model: finalResult?.model,
|
|
736
743
|
attemptedModels: attemptedModels.length > 0 ? attemptedModels : undefined,
|
|
@@ -780,7 +787,7 @@ function markParallelGroupSetupFailure(input: {
|
|
|
780
787
|
input.statusPayload.steps[flatTaskIndex].endedAt = input.failedAt;
|
|
781
788
|
input.statusPayload.steps[flatTaskIndex].durationMs = 0;
|
|
782
789
|
input.statusPayload.steps[flatTaskIndex].exitCode = 1;
|
|
783
|
-
input.results.push({ agent: input.group.parallel[taskIndex].agent, output: input.setupError, success: false });
|
|
790
|
+
input.results.push({ agent: input.group.parallel[taskIndex].agent, output: input.setupError, success: false, sessionFile: input.group.parallel[taskIndex].sessionFile });
|
|
784
791
|
}
|
|
785
792
|
input.statusPayload.currentStep = input.groupStartFlatIndex;
|
|
786
793
|
input.statusPayload.lastUpdate = input.failedAt;
|
|
@@ -916,6 +923,7 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
916
923
|
steps: flatSteps.map((step) => ({
|
|
917
924
|
agent: step.agent,
|
|
918
925
|
status: "pending",
|
|
926
|
+
...(step.sessionFile ? { sessionFile: step.sessionFile } : {}),
|
|
919
927
|
skills: step.skills,
|
|
920
928
|
model: step.model,
|
|
921
929
|
attemptedModels: step.modelCandidates && step.modelCandidates.length > 0 ? step.modelCandidates : step.model ? [step.model] : undefined,
|
|
@@ -1409,6 +1417,7 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
1409
1417
|
error: pr.error,
|
|
1410
1418
|
success: pr.exitCode === 0,
|
|
1411
1419
|
skipped: pr.skipped,
|
|
1420
|
+
sessionFile: pr.sessionFile,
|
|
1412
1421
|
intercomTarget: pr.intercomTarget,
|
|
1413
1422
|
model: pr.model,
|
|
1414
1423
|
attemptedModels: pr.attemptedModels,
|
|
@@ -1492,6 +1501,7 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
1492
1501
|
output: singleResult.output,
|
|
1493
1502
|
error: singleResult.error,
|
|
1494
1503
|
success: singleResult.exitCode === 0,
|
|
1504
|
+
sessionFile: singleResult.sessionFile,
|
|
1495
1505
|
intercomTarget: singleResult.intercomTarget,
|
|
1496
1506
|
model: singleResult.model,
|
|
1497
1507
|
attemptedModels: singleResult.attemptedModels,
|
|
@@ -1580,9 +1590,12 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
1580
1590
|
}
|
|
1581
1591
|
}
|
|
1582
1592
|
|
|
1593
|
+
const resultMode = config.resultMode ?? statusPayload.mode;
|
|
1583
1594
|
const agentName = flatSteps.length === 1
|
|
1584
1595
|
? flatSteps[0].agent
|
|
1585
|
-
:
|
|
1596
|
+
: resultMode === "parallel"
|
|
1597
|
+
? `parallel:${flatSteps.map((s) => s.agent).join("+")}`
|
|
1598
|
+
: `chain:${flatSteps.map((s) => s.agent).join("->")}`;
|
|
1586
1599
|
let sessionFile: string | undefined;
|
|
1587
1600
|
let shareUrl: string | undefined;
|
|
1588
1601
|
let gistUrl: string | undefined;
|
|
@@ -1667,7 +1680,7 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
1667
1680
|
writeAtomicJson(resultPath, {
|
|
1668
1681
|
id,
|
|
1669
1682
|
agent: agentName,
|
|
1670
|
-
mode:
|
|
1683
|
+
mode: resultMode,
|
|
1671
1684
|
success: !interrupted && results.every((r) => r.success),
|
|
1672
1685
|
state: interrupted ? "paused" : results.every((r) => r.success) ? "complete" : "failed",
|
|
1673
1686
|
summary: interrupted ? "Paused after interrupt. Waiting for explicit next action." : summary,
|
|
@@ -1677,6 +1690,7 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
1677
1690
|
error: r.error,
|
|
1678
1691
|
success: r.success,
|
|
1679
1692
|
skipped: r.skipped || undefined,
|
|
1693
|
+
sessionFile: r.sessionFile,
|
|
1680
1694
|
intercomTarget: r.intercomTarget,
|
|
1681
1695
|
model: r.model,
|
|
1682
1696
|
attemptedModels: r.attemptedModels,
|