pi-subagents 0.22.0 → 0.23.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 +29 -0
- package/README.md +13 -10
- package/agents/reviewer.md +2 -2
- package/package.json +1 -1
- package/skills/pi-subagents/SKILL.md +24 -6
- package/src/agents/agent-management.ts +3 -1
- package/src/agents/agents.ts +22 -4
- package/src/extension/doctor.ts +1 -0
- package/src/extension/index.ts +12 -6
- package/src/extension/schemas.ts +1 -1
- package/src/intercom/intercom-bridge.ts +140 -11
- package/src/intercom/result-intercom.ts +8 -3
- package/src/manager-ui/agent-manager.ts +6 -5
- package/src/runs/background/async-execution.ts +22 -7
- package/src/runs/background/async-job-tracker.ts +2 -2
- package/src/runs/background/async-resume.ts +57 -31
- package/src/runs/background/async-status.ts +7 -1
- package/src/runs/background/result-watcher.ts +3 -1
- package/src/runs/background/run-status.ts +22 -19
- package/src/runs/background/stale-run-reconciler.ts +3 -0
- package/src/runs/background/subagent-runner.ts +52 -7
- package/src/runs/foreground/chain-clarify.ts +2 -3
- package/src/runs/foreground/chain-execution.ts +55 -21
- package/src/runs/foreground/execution.ts +9 -5
- package/src/runs/foreground/subagent-executor.ts +157 -23
- package/src/runs/shared/single-output.ts +21 -6
- package/src/shared/settings.ts +19 -0
- package/src/shared/types.ts +26 -1
- package/src/slash/slash-commands.ts +1 -0
- package/src/tui/render.ts +202 -16
- package/src/tui/subagents-status.ts +18 -3
|
@@ -38,7 +38,7 @@ export type ManagerResult =
|
|
|
38
38
|
| { action: "launch-chain"; chain: ChainConfig; task: string; skipClarify?: boolean; fork?: boolean; background?: boolean; worktree?: boolean }
|
|
39
39
|
| undefined;
|
|
40
40
|
|
|
41
|
-
export interface AgentData { builtin: AgentConfig[]; user: AgentConfig[]; project: AgentConfig[]; chains: ChainConfig[]; userDir: string; projectDir: string | null; userSettingsPath: string; projectSettingsPath: string | null; cwd: string; }
|
|
41
|
+
export interface AgentData { builtin: AgentConfig[]; user: AgentConfig[]; project: AgentConfig[]; chains: ChainConfig[]; userDir: string; projectDir: string | null; userChainDir: string; projectChainDir: string | null; userSettingsPath: string; projectSettingsPath: string | null; cwd: string; }
|
|
42
42
|
type ManagerScreen = "list" | "detail" | "chain-detail" | "edit" | "edit-field" | "edit-prompt" | "task-input" | "confirm-delete" | "name-input" | "chain-edit" | "template-select" | "parallel-builder" | "override-scope";
|
|
43
43
|
interface AgentEntry { id: string; kind: "agent"; config: AgentConfig; isNew: boolean; }
|
|
44
44
|
interface ChainEntry { id: string; kind: "chain"; config: ChainConfig; }
|
|
@@ -253,7 +253,8 @@ export class AgentManagerComponent implements Component {
|
|
|
253
253
|
}
|
|
254
254
|
|
|
255
255
|
private enterNameInput(mode: NameInputState["mode"], sourceId?: string, template?: AgentTemplate): void {
|
|
256
|
-
const
|
|
256
|
+
const isChain = mode === "new-chain" || mode === "clone-chain";
|
|
257
|
+
const allowProject = Boolean(isChain ? this.agentData.projectChainDir : this.agentData.projectDir); let initial = ""; let scope: "user" | "project" = "user";
|
|
257
258
|
if (mode === "clone-agent" && sourceId) { const entry = this.getAgentEntry(sourceId); if (entry) { initial = `${frontmatterNameForConfig(entry.config)}-copy`; scope = entry.config.source === "project" ? "project" : "user"; } }
|
|
258
259
|
if (mode === "clone-chain" && sourceId) { const entry = this.getChainEntry(sourceId); if (entry) { initial = `${frontmatterNameForConfig(entry.config)}-copy`; scope = entry.config.source === "project" ? "project" : "user"; } }
|
|
259
260
|
if (mode === "new-agent" && template && template.name !== "Blank") initial = slugTemplateName(template.name);
|
|
@@ -425,14 +426,14 @@ export class AgentManagerComponent implements Component {
|
|
|
425
426
|
|
|
426
427
|
if (state.mode === "clone-chain" && state.sourceId) {
|
|
427
428
|
const sourceEntry = this.getChainEntry(state.sourceId); if (!sourceEntry) { this.screen = "list"; this.tui.requestRender(); return; }
|
|
428
|
-
const dir = state.scope === "project" ? this.agentData.
|
|
429
|
-
if (!dir) { state.error = "Project
|
|
429
|
+
const dir = state.scope === "project" ? this.agentData.projectChainDir : this.agentData.userChainDir;
|
|
430
|
+
if (!dir) { state.error = "Project chains directory not found."; this.tui.requestRender(); return; }
|
|
430
431
|
const filePath = path.join(dir, `${name}.chain.md`); if (fs.existsSync(filePath) || this.runtimeNameExistsInScope("chain", state.scope, name)) { state.error = "A chain with that name already exists."; this.tui.requestRender(); return; }
|
|
431
432
|
try { const cloned = cloneChainConfig({ ...sourceEntry.config, name, localName: name, packageName: undefined, source: state.scope, filePath }); fs.mkdirSync(dir, { recursive: true }); fs.writeFileSync(filePath, serializeChain(cloned), "utf-8"); const added: ChainEntry = { id: `c${this.nextId++}`, kind: "chain", config: cloned }; this.chains.push(added); this.nameInputState = null; this.enterChainDetail(added); this.tui.requestRender(); return; }
|
|
432
433
|
catch (err) { state.error = err instanceof Error ? err.message : "Failed to clone chain."; this.tui.requestRender(); return; }
|
|
433
434
|
}
|
|
434
435
|
if (state.mode === "new-chain") {
|
|
435
|
-
const dir = state.scope === "project" ? this.agentData.
|
|
436
|
+
const dir = state.scope === "project" ? this.agentData.projectChainDir : this.agentData.userChainDir;
|
|
436
437
|
if (!dir) { state.error = "Directory not found."; this.tui.requestRender(); return; }
|
|
437
438
|
const filePath = path.join(dir, `${name}.chain.md`); if (fs.existsSync(filePath) || this.runtimeNameExistsInScope("chain", state.scope, name)) { state.error = "A chain with that name already exists."; this.tui.requestRender(); return; }
|
|
438
439
|
const config: ChainConfig = { name, localName: name, description: "Describe this chain", source: state.scope, filePath, steps: [{ agent: "agent-name", task: "{task}" }] };
|
|
@@ -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;
|
|
@@ -116,6 +117,16 @@ interface AsyncExecutionResult {
|
|
|
116
117
|
isError?: boolean;
|
|
117
118
|
}
|
|
118
119
|
|
|
120
|
+
export function formatAsyncStartedMessage(headline: string): string {
|
|
121
|
+
return [
|
|
122
|
+
headline,
|
|
123
|
+
"",
|
|
124
|
+
"The async run is detached. Do not run sleep timers or polling loops just to wait for it.",
|
|
125
|
+
"If you have independent work, continue that work. If you have nothing else to do until the async result arrives, end your turn now; Pi will deliver the completion when the run finishes.",
|
|
126
|
+
"Use subagent({ action: \"status\", id: \"...\" }) when you need the current status/result, or to inspect a blocked/stale run. Do not poll just to wait.",
|
|
127
|
+
].join("\n");
|
|
128
|
+
}
|
|
129
|
+
|
|
119
130
|
/**
|
|
120
131
|
* Check if jiti is available for async execution
|
|
121
132
|
*/
|
|
@@ -203,6 +214,10 @@ export function executeAsyncChain(
|
|
|
203
214
|
const chainSkills = params.chainSkills ?? [];
|
|
204
215
|
const availableModels = params.availableModels;
|
|
205
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);
|
|
206
221
|
|
|
207
222
|
for (const s of chain) {
|
|
208
223
|
const stepAgents = isParallelStep(s)
|
|
@@ -247,7 +262,7 @@ export function executeAsyncChain(
|
|
|
247
262
|
const a = agents.find((x) => x.name === s.agent)!;
|
|
248
263
|
const stepCwd = resolveChildCwd(runnerCwd, s.cwd);
|
|
249
264
|
const instructionCwd = behaviorCwd ?? stepCwd;
|
|
250
|
-
const behavior = resolvedBehavior ?? resolveStepBehavior(a, buildStepOverrides(s), chainSkills);
|
|
265
|
+
const behavior = suppressProgressForReadOnlyTask(resolvedBehavior ?? resolveStepBehavior(a, buildStepOverrides(s), chainSkills), s.task, originalTask);
|
|
251
266
|
const skillNames = behavior.skills === false ? [] : behavior.skills;
|
|
252
267
|
const { resolved: resolvedSkills, missing: missingSkills } = resolveSkillsWithFallback(skillNames, stepCwd, ctx.cwd);
|
|
253
268
|
if (missingSkills.includes("pi-subagents")) throw new UnavailableSubagentSkillError(UNAVAILABLE_SUBAGENT_SKILL_ERROR);
|
|
@@ -304,7 +319,7 @@ export function executeAsyncChain(
|
|
|
304
319
|
if (isParallelStep(s)) {
|
|
305
320
|
const parallelBehaviors = s.parallel.map((task) => {
|
|
306
321
|
const agent = agents.find((candidate) => candidate.name === task.agent)!;
|
|
307
|
-
return resolveStepBehavior(agent, buildStepOverrides(task), chainSkills);
|
|
322
|
+
return suppressProgressForReadOnlyTask(resolveStepBehavior(agent, buildStepOverrides(task), chainSkills), task.task, originalTask);
|
|
308
323
|
});
|
|
309
324
|
const progressPrecreated = parallelBehaviors.some((behavior) => behavior.progress);
|
|
310
325
|
if (progressPrecreated) {
|
|
@@ -425,8 +440,8 @@ export function executeAsyncChain(
|
|
|
425
440
|
.join(" -> ");
|
|
426
441
|
|
|
427
442
|
return {
|
|
428
|
-
content: [{ type: "text", text: `Async ${resultMode}: ${chainDesc} [${id}]` }],
|
|
429
|
-
details: { mode: resultMode, results: [], asyncId: id, asyncDir },
|
|
443
|
+
content: [{ type: "text", text: formatAsyncStartedMessage(`Async ${resultMode}: ${chainDesc} [${id}]`) }],
|
|
444
|
+
details: { mode: resultMode, runId: id, results: [], asyncId: id, asyncDir },
|
|
430
445
|
};
|
|
431
446
|
}
|
|
432
447
|
|
|
@@ -557,7 +572,7 @@ export function executeAsyncSingle(
|
|
|
557
572
|
}
|
|
558
573
|
|
|
559
574
|
return {
|
|
560
|
-
content: [{ type: "text", text: `Async: ${agent} [${id}]` }],
|
|
561
|
-
details: { mode: "single", results: [], asyncId: id, asyncDir },
|
|
575
|
+
content: [{ type: "text", text: formatAsyncStartedMessage(`Async: ${agent} [${id}]`) }],
|
|
576
|
+
details: { mode: "single", runId: id, results: [], asyncId: id, asyncDir },
|
|
562
577
|
};
|
|
563
578
|
}
|
|
@@ -162,8 +162,8 @@ export function createAsyncJobTracker(pi: Pick<ExtensionAPI, "events">, state: S
|
|
|
162
162
|
? groups.find((group) => status.currentStep! >= group.start && status.currentStep! < group.start + group.count)
|
|
163
163
|
: undefined;
|
|
164
164
|
const visibleSteps = activeGroup
|
|
165
|
-
? status.steps.slice(activeGroup.start, activeGroup.start + activeGroup.count)
|
|
166
|
-
: status.steps;
|
|
165
|
+
? status.steps.slice(activeGroup.start, activeGroup.start + activeGroup.count).map((step, index) => ({ ...step, index: activeGroup.start + index }))
|
|
166
|
+
: status.steps.map((step, index) => ({ ...step, index }));
|
|
167
167
|
job.activeParallelGroup = Boolean(activeGroup);
|
|
168
168
|
job.agents = visibleSteps.map((step) => step.agent);
|
|
169
169
|
job.steps = visibleSteps;
|
|
@@ -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
|
"",
|
|
@@ -13,8 +13,11 @@ interface AsyncRunStepSummary {
|
|
|
13
13
|
activityState?: ActivityState;
|
|
14
14
|
lastActivityAt?: number;
|
|
15
15
|
currentTool?: string;
|
|
16
|
+
currentToolArgs?: string;
|
|
16
17
|
currentToolStartedAt?: number;
|
|
17
18
|
currentPath?: string;
|
|
19
|
+
recentTools?: Array<{ tool: string; args: string; endMs: number }>;
|
|
20
|
+
recentOutput?: string[];
|
|
18
21
|
turnCount?: number;
|
|
19
22
|
toolCount?: number;
|
|
20
23
|
durationMs?: number;
|
|
@@ -155,8 +158,11 @@ function statusToSummary(asyncDir: string, status: AsyncStatus & { cwd?: string
|
|
|
155
158
|
...(stepActivityState ? { activityState: stepActivityState } : {}),
|
|
156
159
|
...(stepLastActivityAt ? { lastActivityAt: stepLastActivityAt } : {}),
|
|
157
160
|
...(step.currentTool ? { currentTool: step.currentTool } : {}),
|
|
161
|
+
...(step.currentToolArgs ? { currentToolArgs: step.currentToolArgs } : {}),
|
|
158
162
|
...(step.currentToolStartedAt ? { currentToolStartedAt: step.currentToolStartedAt } : {}),
|
|
159
163
|
...(step.currentPath ? { currentPath: step.currentPath } : {}),
|
|
164
|
+
...(step.recentTools ? { recentTools: step.recentTools.map((tool) => ({ ...tool })) } : {}),
|
|
165
|
+
...(step.recentOutput ? { recentOutput: [...step.recentOutput] } : {}),
|
|
160
166
|
...(step.turnCount !== undefined ? { turnCount: step.turnCount } : {}),
|
|
161
167
|
...(step.toolCount !== undefined ? { toolCount: step.toolCount } : {}),
|
|
162
168
|
...(step.durationMs !== undefined ? { durationMs: step.durationMs } : {}),
|
|
@@ -270,7 +276,7 @@ function formatParallelProgress(steps: Pick<AsyncRunStepSummary, "status">[], to
|
|
|
270
276
|
const failed = steps.filter((step) => step.status === "failed").length;
|
|
271
277
|
const paused = steps.filter((step) => step.status === "paused").length;
|
|
272
278
|
const parts = [`${done}/${total} done`];
|
|
273
|
-
if (showRunning) parts.unshift(running === 1 ? "1 agent running" : `${running} agents running`);
|
|
279
|
+
if (showRunning && running > 0) parts.unshift(running === 1 ? "1 agent running" : `${running} agents running`);
|
|
274
280
|
if (failed > 0) parts.push(`${failed} failed`);
|
|
275
281
|
if (paused > 0) parts.push(`${paused} paused`);
|
|
276
282
|
return parts.join(" · ");
|
|
@@ -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
|
}),
|
|
@@ -28,8 +28,24 @@ function activityText(activityState: unknown, lastActivityAt: unknown): string |
|
|
|
28
28
|
return activityState === "needs_attention" ? `no activity for ${seconds}s` : `active ${seconds}s ago`;
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
function
|
|
32
|
-
return
|
|
31
|
+
function hasExistingSessionFile(value: unknown): value is string {
|
|
32
|
+
return typeof value === "string" && fs.existsSync(value);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function formatResumeGuidance(runId: string | undefined, children: Array<{ agent?: unknown; sessionFile?: unknown }>, fallbackSessionFile?: unknown): string {
|
|
36
|
+
const knownChildren = children
|
|
37
|
+
.map((child, index) => ({ child, index }))
|
|
38
|
+
.filter(({ child }) => typeof child.agent === "string");
|
|
39
|
+
if (!runId || knownChildren.length === 0) return "Resume: unavailable; no child session file was persisted.";
|
|
40
|
+
const singleSessionFile = knownChildren[0]?.child.sessionFile ?? fallbackSessionFile;
|
|
41
|
+
if (children.length === 1 && knownChildren.length === 1 && hasExistingSessionFile(singleSessionFile)) {
|
|
42
|
+
return `Revive: subagent({ action: "resume", id: "${runId}", message: "..." })`;
|
|
43
|
+
}
|
|
44
|
+
const childWithSession = knownChildren.find(({ child }) => hasExistingSessionFile(child.sessionFile));
|
|
45
|
+
if (childWithSession) {
|
|
46
|
+
return `Revive child: subagent({ action: "resume", id: "${runId}", index: ${childWithSession.index}, message: "..." })`;
|
|
47
|
+
}
|
|
48
|
+
return "Resume: unavailable; no child session file was persisted.";
|
|
33
49
|
}
|
|
34
50
|
|
|
35
51
|
function stepLineLabel(status: AsyncStatus, index: number): string {
|
|
@@ -137,14 +153,7 @@ export function inspectSubagentStatus(params: RunStatusParams, deps: RunStatusDe
|
|
|
137
153
|
}
|
|
138
154
|
if (status.sessionFile) lines.push(`Session: ${status.sessionFile}`);
|
|
139
155
|
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
|
-
}
|
|
156
|
+
lines.push(formatResumeGuidance(status.runId, status.steps ?? [], status.sessionFile));
|
|
148
157
|
}
|
|
149
158
|
if (fs.existsSync(logPath)) lines.push(`Log: ${logPath}`);
|
|
150
159
|
if (fs.existsSync(eventsPath)) lines.push(`Events: ${eventsPath}`);
|
|
@@ -156,18 +165,12 @@ export function inspectSubagentStatus(params: RunStatusParams, deps: RunStatusDe
|
|
|
156
165
|
if (resultPath) {
|
|
157
166
|
try {
|
|
158
167
|
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 }> };
|
|
168
|
+
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
169
|
const status = data.success ? "complete" : data.state === "paused" || data.exitCode === 0 ? "paused" : "failed";
|
|
161
170
|
const runId = data.runId ?? data.id ?? resolvedId;
|
|
162
171
|
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
|
-
}
|
|
172
|
+
const children = Array.isArray(data.results) ? data.results : data.agent ? [{ agent: data.agent, sessionFile: data.sessionFile }] : [];
|
|
173
|
+
lines.push(formatResumeGuidance(runId, children, data.sessionFile));
|
|
171
174
|
if (data.summary) lines.push("", data.summary);
|
|
172
175
|
return { content: [{ type: "text", text: lines.join("\n") }], details: { mode: "single", results: [] } };
|
|
173
176
|
} 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[];
|
|
@@ -142,6 +143,25 @@ function tokenUsageFromAttempts(attempts: ModelAttempt[] | undefined): TokenUsag
|
|
|
142
143
|
return total > 0 ? { input, output, total } : null;
|
|
143
144
|
}
|
|
144
145
|
|
|
146
|
+
function appendRecentStepOutput(step: RunnerStatusStep, lines: string[]): void {
|
|
147
|
+
const nonEmpty = lines.filter((line) => line.trim());
|
|
148
|
+
if (nonEmpty.length === 0) return;
|
|
149
|
+
step.recentOutput ??= [];
|
|
150
|
+
step.recentOutput.push(...nonEmpty);
|
|
151
|
+
if (step.recentOutput.length > 50) {
|
|
152
|
+
step.recentOutput.splice(0, step.recentOutput.length - 50);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function resetStepLiveDetail(step: RunnerStatusStep): void {
|
|
157
|
+
step.currentTool = undefined;
|
|
158
|
+
step.currentToolArgs = undefined;
|
|
159
|
+
step.currentToolStartedAt = undefined;
|
|
160
|
+
step.currentPath = undefined;
|
|
161
|
+
step.recentTools = [];
|
|
162
|
+
step.recentOutput = [];
|
|
163
|
+
}
|
|
164
|
+
|
|
145
165
|
interface ChildEventContext {
|
|
146
166
|
eventsPath: string;
|
|
147
167
|
runId: string;
|
|
@@ -556,6 +576,7 @@ async function runSingleStep(
|
|
|
556
576
|
modelAttempts?: ModelAttempt[];
|
|
557
577
|
artifactPaths?: ArtifactPaths;
|
|
558
578
|
interrupted?: boolean;
|
|
579
|
+
sessionFile?: string;
|
|
559
580
|
intercomTarget?: string;
|
|
560
581
|
completionGuardTriggered?: boolean;
|
|
561
582
|
}> {
|
|
@@ -563,7 +584,6 @@ async function runSingleStep(
|
|
|
563
584
|
const task = step.task.replace(placeholderRegex, () => ctx.previousOutput);
|
|
564
585
|
const sessionEnabled = Boolean(step.sessionFile) || ctx.sessionEnabled;
|
|
565
586
|
const sessionDir = step.sessionFile ? undefined : ctx.sessionDir;
|
|
566
|
-
const outputSnapshot = captureSingleOutputSnapshot(step.outputPath);
|
|
567
587
|
|
|
568
588
|
let artifactPaths: ArtifactPaths | undefined;
|
|
569
589
|
if (ctx.artifactsDir && ctx.artifactConfig?.enabled !== false) {
|
|
@@ -585,10 +605,12 @@ async function runSingleStep(
|
|
|
585
605
|
const attemptNotes: string[] = [];
|
|
586
606
|
const eventsPath = path.join(path.dirname(ctx.outputFile), "events.jsonl");
|
|
587
607
|
let finalResult: RunPiStreamingResult | undefined;
|
|
608
|
+
let finalOutputSnapshot: SingleOutputSnapshot | undefined;
|
|
588
609
|
let completionGuardTriggeredFinal = false;
|
|
589
610
|
|
|
590
611
|
for (let index = 0; index < candidates.length; index++) {
|
|
591
612
|
const candidate = candidates[index];
|
|
613
|
+
const outputSnapshot = captureSingleOutputSnapshot(step.outputPath);
|
|
592
614
|
const { args, env, tempDir } = buildPiArgs({
|
|
593
615
|
baseArgs: ["--mode", "json", "-p"],
|
|
594
616
|
task,
|
|
@@ -640,7 +662,9 @@ async function runSingleStep(
|
|
|
640
662
|
? 1
|
|
641
663
|
: hiddenError?.hasError
|
|
642
664
|
? (hiddenError.exitCode ?? 1)
|
|
643
|
-
: run.exitCode
|
|
665
|
+
: run.error && run.exitCode === 0
|
|
666
|
+
? 1
|
|
667
|
+
: run.exitCode;
|
|
644
668
|
const error = completionGuardError
|
|
645
669
|
?? (hiddenError?.hasError
|
|
646
670
|
? hiddenError.details
|
|
@@ -657,6 +681,7 @@ async function runSingleStep(
|
|
|
657
681
|
modelAttempts.push(attempt);
|
|
658
682
|
if (candidate) attemptedModels.push(candidate);
|
|
659
683
|
completionGuardTriggeredFinal = completionGuardTriggered;
|
|
684
|
+
finalOutputSnapshot = outputSnapshot;
|
|
660
685
|
finalResult = { ...run, exitCode: effectiveExitCode, model: candidate ?? run.model, error };
|
|
661
686
|
if (attempt.success || completionGuardTriggered) break;
|
|
662
687
|
if (!isRetryableModelFailure(error) || index === candidates.length - 1) break;
|
|
@@ -665,7 +690,7 @@ async function runSingleStep(
|
|
|
665
690
|
|
|
666
691
|
const rawOutput = finalResult?.finalOutput ?? "";
|
|
667
692
|
const resolvedOutput = step.outputPath && finalResult?.exitCode === 0
|
|
668
|
-
? resolveSingleOutput(step.outputPath, rawOutput,
|
|
693
|
+
? resolveSingleOutput(step.outputPath, rawOutput, finalOutputSnapshot)
|
|
669
694
|
: { fullOutput: rawOutput };
|
|
670
695
|
const output = resolvedOutput.fullOutput;
|
|
671
696
|
const outputReference = resolvedOutput.savedPath ? formatSavedOutputReference(resolvedOutput.savedPath, output) : undefined;
|
|
@@ -712,6 +737,7 @@ async function runSingleStep(
|
|
|
712
737
|
output: outputForSummary,
|
|
713
738
|
exitCode: finalResult?.exitCode ?? 1,
|
|
714
739
|
error: finalResult?.error,
|
|
740
|
+
sessionFile: step.sessionFile,
|
|
715
741
|
intercomTarget: ctx.childIntercomTarget,
|
|
716
742
|
model: finalResult?.model,
|
|
717
743
|
attemptedModels: attemptedModels.length > 0 ? attemptedModels : undefined,
|
|
@@ -761,7 +787,7 @@ function markParallelGroupSetupFailure(input: {
|
|
|
761
787
|
input.statusPayload.steps[flatTaskIndex].endedAt = input.failedAt;
|
|
762
788
|
input.statusPayload.steps[flatTaskIndex].durationMs = 0;
|
|
763
789
|
input.statusPayload.steps[flatTaskIndex].exitCode = 1;
|
|
764
|
-
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 });
|
|
765
791
|
}
|
|
766
792
|
input.statusPayload.currentStep = input.groupStartFlatIndex;
|
|
767
793
|
input.statusPayload.lastUpdate = input.failedAt;
|
|
@@ -897,9 +923,12 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
897
923
|
steps: flatSteps.map((step) => ({
|
|
898
924
|
agent: step.agent,
|
|
899
925
|
status: "pending",
|
|
926
|
+
...(step.sessionFile ? { sessionFile: step.sessionFile } : {}),
|
|
900
927
|
skills: step.skills,
|
|
901
928
|
model: step.model,
|
|
902
929
|
attemptedModels: step.modelCandidates && step.modelCandidates.length > 0 ? step.modelCandidates : step.model ? [step.model] : undefined,
|
|
930
|
+
recentTools: [],
|
|
931
|
+
recentOutput: [],
|
|
903
932
|
})),
|
|
904
933
|
artifactsDir,
|
|
905
934
|
sessionDir: config.sessionDir,
|
|
@@ -1002,13 +1031,19 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
1002
1031
|
const currentPath = resolveCurrentPath(event.toolName, event.args);
|
|
1003
1032
|
step.toolCount = (step.toolCount ?? 0) + 1;
|
|
1004
1033
|
step.currentTool = event.toolName;
|
|
1034
|
+
step.currentToolArgs = extractToolArgsPreview(event.args ?? {});
|
|
1005
1035
|
step.currentToolStartedAt = now;
|
|
1006
1036
|
step.currentPath = currentPath;
|
|
1007
1037
|
pendingToolResults[flatIndex] = { tool: event.toolName, path: currentPath, mutates, startedAt: now };
|
|
1008
1038
|
statusPayload.toolCount = (statusPayload.toolCount ?? 0) + 1;
|
|
1009
1039
|
syncTopLevelCurrentTool();
|
|
1010
1040
|
} else if (event.type === "tool_execution_end") {
|
|
1041
|
+
if (step.currentTool) {
|
|
1042
|
+
step.recentTools ??= [];
|
|
1043
|
+
step.recentTools.push({ tool: step.currentTool, args: step.currentToolArgs || "", endMs: now });
|
|
1044
|
+
}
|
|
1011
1045
|
step.currentTool = undefined;
|
|
1046
|
+
step.currentToolArgs = undefined;
|
|
1012
1047
|
step.currentToolStartedAt = undefined;
|
|
1013
1048
|
step.currentPath = undefined;
|
|
1014
1049
|
syncTopLevelCurrentTool();
|
|
@@ -1016,6 +1051,7 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
1016
1051
|
const toolSnapshot = pendingToolResults[flatIndex];
|
|
1017
1052
|
pendingToolResults[flatIndex] = undefined;
|
|
1018
1053
|
const resultText = extractTextFromContent(event.message.content);
|
|
1054
|
+
appendRecentStepOutput(step, resultText.split("\n").slice(-10));
|
|
1019
1055
|
if (toolSnapshot?.mutates && didMutatingToolFail(resultText)) {
|
|
1020
1056
|
const state = mutatingFailureStates[flatIndex]!;
|
|
1021
1057
|
recordMutatingFailure(state, {
|
|
@@ -1051,6 +1087,7 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
1051
1087
|
resetMutatingFailureState(mutatingFailureStates[flatIndex]!);
|
|
1052
1088
|
}
|
|
1053
1089
|
} else if (event.type === "message_end" && event.message?.role === "assistant") {
|
|
1090
|
+
appendRecentStepOutput(step, extractTextFromContent(event.message.content).split("\n").slice(-10));
|
|
1054
1091
|
step.turnCount = (step.turnCount ?? 0) + 1;
|
|
1055
1092
|
const usage = event.message.usage;
|
|
1056
1093
|
if (usage) {
|
|
@@ -1277,6 +1314,7 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
1277
1314
|
statusPayload.steps[fi].status = "running";
|
|
1278
1315
|
statusPayload.steps[fi].error = undefined;
|
|
1279
1316
|
statusPayload.steps[fi].activityState = undefined;
|
|
1317
|
+
resetStepLiveDetail(statusPayload.steps[fi]);
|
|
1280
1318
|
statusPayload.steps[fi].startedAt = taskStartTime;
|
|
1281
1319
|
statusPayload.steps[fi].endedAt = undefined;
|
|
1282
1320
|
statusPayload.steps[fi].durationMs = undefined;
|
|
@@ -1379,6 +1417,7 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
1379
1417
|
error: pr.error,
|
|
1380
1418
|
success: pr.exitCode === 0,
|
|
1381
1419
|
skipped: pr.skipped,
|
|
1420
|
+
sessionFile: pr.sessionFile,
|
|
1382
1421
|
intercomTarget: pr.intercomTarget,
|
|
1383
1422
|
model: pr.model,
|
|
1384
1423
|
attemptedModels: pr.attemptedModels,
|
|
@@ -1420,6 +1459,7 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
1420
1459
|
statusPayload.steps[flatIndex].status = "running";
|
|
1421
1460
|
statusPayload.steps[flatIndex].activityState = undefined;
|
|
1422
1461
|
statusPayload.activityState = undefined;
|
|
1462
|
+
resetStepLiveDetail(statusPayload.steps[flatIndex]);
|
|
1423
1463
|
statusPayload.steps[flatIndex].skills = seqStep.skills;
|
|
1424
1464
|
statusPayload.steps[flatIndex].startedAt = stepStartTime;
|
|
1425
1465
|
statusPayload.steps[flatIndex].lastActivityAt = stepStartTime;
|
|
@@ -1461,6 +1501,7 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
1461
1501
|
output: singleResult.output,
|
|
1462
1502
|
error: singleResult.error,
|
|
1463
1503
|
success: singleResult.exitCode === 0,
|
|
1504
|
+
sessionFile: singleResult.sessionFile,
|
|
1464
1505
|
intercomTarget: singleResult.intercomTarget,
|
|
1465
1506
|
model: singleResult.model,
|
|
1466
1507
|
attemptedModels: singleResult.attemptedModels,
|
|
@@ -1549,9 +1590,12 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
1549
1590
|
}
|
|
1550
1591
|
}
|
|
1551
1592
|
|
|
1593
|
+
const resultMode = config.resultMode ?? statusPayload.mode;
|
|
1552
1594
|
const agentName = flatSteps.length === 1
|
|
1553
1595
|
? flatSteps[0].agent
|
|
1554
|
-
:
|
|
1596
|
+
: resultMode === "parallel"
|
|
1597
|
+
? `parallel:${flatSteps.map((s) => s.agent).join("+")}`
|
|
1598
|
+
: `chain:${flatSteps.map((s) => s.agent).join("->")}`;
|
|
1555
1599
|
let sessionFile: string | undefined;
|
|
1556
1600
|
let shareUrl: string | undefined;
|
|
1557
1601
|
let gistUrl: string | undefined;
|
|
@@ -1636,7 +1680,7 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
1636
1680
|
writeAtomicJson(resultPath, {
|
|
1637
1681
|
id,
|
|
1638
1682
|
agent: agentName,
|
|
1639
|
-
mode:
|
|
1683
|
+
mode: resultMode,
|
|
1640
1684
|
success: !interrupted && results.every((r) => r.success),
|
|
1641
1685
|
state: interrupted ? "paused" : results.every((r) => r.success) ? "complete" : "failed",
|
|
1642
1686
|
summary: interrupted ? "Paused after interrupt. Waiting for explicit next action." : summary,
|
|
@@ -1646,6 +1690,7 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
1646
1690
|
error: r.error,
|
|
1647
1691
|
success: r.success,
|
|
1648
1692
|
skipped: r.skipped || undefined,
|
|
1693
|
+
sessionFile: r.sessionFile,
|
|
1649
1694
|
intercomTarget: r.intercomTarget,
|
|
1650
1695
|
model: r.model,
|
|
1651
1696
|
attemptedModels: r.attemptedModels,
|