pi-subagents 0.14.0 → 0.14.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 -4
- package/README.md +3 -3
- package/agent-manager-detail.ts +1 -1
- package/agent-manager-list.ts +3 -3
- package/chain-clarify.ts +4 -4
- package/chain-execution.ts +15 -13
- package/install.mjs +3 -3
- package/package.json +1 -1
- package/parallel-utils.ts +5 -5
- package/prompt-template-bridge.ts +19 -8
- package/render.ts +23 -50
- package/single-output.ts +2 -2
- package/subagent-executor.ts +9 -9
- package/subagent-runner.ts +4 -4
- package/types.ts +1 -1
- package/utils.ts +47 -11
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [0.14.1] - 2026-04-14
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
- Completed foreground subagent results now return compact payloads instead of inlining full raw message histories and per-result progress objects, preventing long tool-heavy sync runs from overwhelming the parent agent return path.
|
|
9
|
+
- Prompt-template delegation now rebuilds minimal assistant messages from compact foreground results when raw message arrays are intentionally omitted.
|
|
10
|
+
- UI/status wording now uses plain text labels instead of glyph-heavy markers across foreground rendering, parallel summaries, save-result receipts, installer output, agent manager views, clarify screens, and the corresponding README/CHANGELOG examples.
|
|
11
|
+
- Added a realistic foreground integration repro for issue `#80` and cleaned up the touched tests to remove the remaining blunt `as any` fixture casts.
|
|
12
|
+
|
|
5
13
|
## [0.14.0] - 2026-04-14
|
|
6
14
|
|
|
7
15
|
### Added
|
|
@@ -496,7 +504,7 @@
|
|
|
496
504
|
- Pre-selects current thinking level if already set
|
|
497
505
|
- **Model selector in chain TUI** - Press `[m]` to select a different model for any step
|
|
498
506
|
- Fuzzy search through all available models
|
|
499
|
-
- Shows current model with
|
|
507
|
+
- Shows the current model with a `current` badge
|
|
500
508
|
- Provider/model format (e.g., `anthropic/claude-haiku-4-5`)
|
|
501
509
|
- Override indicator (✎) when model differs from agent default
|
|
502
510
|
- **Model visibility in chain execution** - Shows which model each step is using
|
|
@@ -534,8 +542,8 @@
|
|
|
534
542
|
|
|
535
543
|
### Improved
|
|
536
544
|
- **Per-step progress indicators** - When progress is enabled, each step shows its role:
|
|
537
|
-
- Step 1:
|
|
538
|
-
- Step 2+:
|
|
545
|
+
- Step 1: `writes progress.md`
|
|
546
|
+
- Step 2+: `reads progress.md`
|
|
539
547
|
- Clear visualization of progress.md data flow through the chain
|
|
540
548
|
- **Comprehensive tool descriptions** - Better documentation of chain variables:
|
|
541
549
|
- Tool description now explains `{task}`, `{previous}`, `{chain_dir}` in detail
|
|
@@ -591,7 +599,7 @@
|
|
|
591
599
|
### Improved
|
|
592
600
|
- **Tool description now explicitly shows the three modes** (SINGLE, CHAIN, PARALLEL) with syntax - helps agents pick the right mode when user says "scout → planner"
|
|
593
601
|
- **Chain execution observability** - Now shows:
|
|
594
|
-
- Chain visualization with status
|
|
602
|
+
- Chain visualization with status labels: `done scout → running planner` (`done`, `running`, `pending`, `failed`) - sequential chains only
|
|
595
603
|
- Accurate step counter: "step 1/2" instead of misleading "1/1"
|
|
596
604
|
- Current tool and recent output for running step
|
|
597
605
|
|
package/README.md
CHANGED
|
@@ -921,8 +921,8 @@ Requirements:
|
|
|
921
921
|
During sync execution, the collapsed view shows real-time progress for single, chain, and parallel modes.
|
|
922
922
|
|
|
923
923
|
**Chains:**
|
|
924
|
-
- Header:
|
|
925
|
-
- Chain visualization with status:
|
|
924
|
+
- Header: `running chain 1/2 | 8 tools, 1.4k tok, 38s`
|
|
925
|
+
- Chain visualization with status: `done scout → running planner` (`done`, `running`, `pending`, `failed`)
|
|
926
926
|
- Current tool: `> read: packages/tui/src/...`
|
|
927
927
|
- Recent output lines (last 2-3 lines)
|
|
928
928
|
|
|
@@ -933,7 +933,7 @@ During sync execution, the collapsed view shows real-time progress for single, c
|
|
|
933
933
|
|
|
934
934
|
Press **Ctrl+O** to expand the full streaming view with complete output per step.
|
|
935
935
|
|
|
936
|
-
> **Note:** Chain visualization (the
|
|
936
|
+
> **Note:** Chain visualization (the `done scout → running planner` line) is only shown for sequential chains. Chains with parallel steps show per-step cards instead.
|
|
937
937
|
|
|
938
938
|
## Nested subagent recursion guard
|
|
939
939
|
|
package/agent-manager-detail.ts
CHANGED
|
@@ -106,7 +106,7 @@ function buildDetailLines(
|
|
|
106
106
|
|
|
107
107
|
for (const run of recentRuns) {
|
|
108
108
|
const when = pad(formatRelativeTime(run.ts), 8);
|
|
109
|
-
const status = run.status
|
|
109
|
+
const status = run.status;
|
|
110
110
|
const task = truncateToWidth(`"${run.task}"`, 34);
|
|
111
111
|
const tail = run.status === "ok" ? formatDuration(run.duration) : `exit ${run.exit ?? 1}`;
|
|
112
112
|
lines.push(truncateToWidth(` ${when} ${status} ${task} ${tail}`, contentWidth));
|
package/agent-manager-list.ts
CHANGED
|
@@ -200,9 +200,9 @@ export function renderList(
|
|
|
200
200
|
const count = selectionCount(state.selected, agent.id);
|
|
201
201
|
const isShadowed = agent.kind === "agent" && agent.source === "project" && userNames.has(agent.name);
|
|
202
202
|
|
|
203
|
-
const cursorChar = isCursor ? theme.fg("accent", "
|
|
204
|
-
const selectBadge = count >
|
|
205
|
-
const shadowMarker = isShadowed ? theme.fg("warning", "
|
|
203
|
+
const cursorChar = isCursor ? theme.fg("accent", ">") : " ";
|
|
204
|
+
const selectBadge = count > 0 ? theme.fg("accent", String(count).padStart(2)) : " ";
|
|
205
|
+
const shadowMarker = isShadowed ? theme.fg("warning", "!") : " ";
|
|
206
206
|
const prefix = `${cursorChar}${selectBadge}${shadowMarker} `;
|
|
207
207
|
|
|
208
208
|
const modelRaw = agent.kind === "chain" ? `${agent.stepCount ?? 0} steps` : (agent.model ?? "default");
|
package/chain-clarify.ts
CHANGED
|
@@ -987,7 +987,7 @@ export class ChainClarifyComponent implements Component {
|
|
|
987
987
|
const prefix = isSelected ? th.fg("accent", "→ ") : " ";
|
|
988
988
|
const modelText = isSelected ? th.fg("accent", model.id) : model.id;
|
|
989
989
|
const providerBadge = th.fg("dim", ` [${model.provider}]`);
|
|
990
|
-
const currentBadge = isCurrent ? th.fg("success", "
|
|
990
|
+
const currentBadge = isCurrent ? th.fg("success", " current") : "";
|
|
991
991
|
|
|
992
992
|
lines.push(this.row(` ${prefix}${modelText}${providerBadge}${currentBadge}`));
|
|
993
993
|
}
|
|
@@ -1269,7 +1269,7 @@ export class ChainClarifyComponent implements Component {
|
|
|
1269
1269
|
lines.push(this.row(` Chain Dir: ${th.fg("dim", chainDirPreview)}`));
|
|
1270
1270
|
|
|
1271
1271
|
const progressEnabled = this.agentConfigs.some((_, i) => this.getEffectiveBehavior(i).progress);
|
|
1272
|
-
const progressValue = progressEnabled ? th.fg("success", "
|
|
1272
|
+
const progressValue = progressEnabled ? th.fg("success", "enabled") : th.fg("dim", "disabled");
|
|
1273
1273
|
lines.push(this.row(` Progress: ${progressValue} ${th.fg("dim", "(press [p] to toggle)")}`));
|
|
1274
1274
|
lines.push(this.row(""));
|
|
1275
1275
|
|
|
@@ -1331,8 +1331,8 @@ export class ChainClarifyComponent implements Component {
|
|
|
1331
1331
|
if (progressEnabled) {
|
|
1332
1332
|
const isFirstStep = i === 0;
|
|
1333
1333
|
const progressAction = isFirstStep
|
|
1334
|
-
? th.fg("success", "
|
|
1335
|
-
: th.fg("accent", "
|
|
1334
|
+
? th.fg("success", "writes progress.md")
|
|
1335
|
+
: th.fg("accent", "reads progress.md");
|
|
1336
1336
|
const progressLabel = th.fg("dim", "progress: ");
|
|
1337
1337
|
lines.push(this.row(` ${progressLabel}${progressAction}`));
|
|
1338
1338
|
}
|
package/chain-execution.ts
CHANGED
|
@@ -28,7 +28,7 @@ import {
|
|
|
28
28
|
import { discoverAvailableSkills, normalizeSkillInput } from "./skills.ts";
|
|
29
29
|
import { runSync } from "./execution.ts";
|
|
30
30
|
import { buildChainSummary } from "./formatters.ts";
|
|
31
|
-
import { getSingleResultOutput, mapConcurrent } from "./utils.ts";
|
|
31
|
+
import { compactForegroundDetails, getSingleResultOutput, mapConcurrent } from "./utils.ts";
|
|
32
32
|
import { recordRun } from "./run-history.ts";
|
|
33
33
|
import {
|
|
34
34
|
cleanupWorktrees,
|
|
@@ -91,7 +91,7 @@ interface ParallelChainRunInput {
|
|
|
91
91
|
}
|
|
92
92
|
|
|
93
93
|
function buildChainExecutionDetails(input: ChainExecutionDetailsInput): Details {
|
|
94
|
-
return {
|
|
94
|
+
return compactForegroundDetails({
|
|
95
95
|
mode: "chain",
|
|
96
96
|
results: input.results,
|
|
97
97
|
progress: input.includeProgress ? input.allProgress : undefined,
|
|
@@ -99,7 +99,7 @@ function buildChainExecutionDetails(input: ChainExecutionDetailsInput): Details
|
|
|
99
99
|
chainAgents: input.chainAgents,
|
|
100
100
|
totalSteps: input.totalSteps,
|
|
101
101
|
currentStepIndex: input.currentStepIndex,
|
|
102
|
-
};
|
|
102
|
+
});
|
|
103
103
|
}
|
|
104
104
|
|
|
105
105
|
function buildChainExecutionErrorResult(message: string, input: ChainExecutionDetailsInput): ChainExecutionResult {
|
|
@@ -670,15 +670,16 @@ export async function executeChain(params: ChainExecutionParams): Promise<ChainE
|
|
|
670
670
|
});
|
|
671
671
|
return {
|
|
672
672
|
content: [{ type: "text", text: summary }],
|
|
673
|
-
details: {
|
|
674
|
-
mode: "chain",
|
|
673
|
+
details: buildChainExecutionDetails({
|
|
675
674
|
results,
|
|
676
|
-
|
|
677
|
-
|
|
675
|
+
includeProgress,
|
|
676
|
+
allProgress,
|
|
677
|
+
allArtifactPaths,
|
|
678
|
+
artifactsDir,
|
|
678
679
|
chainAgents,
|
|
679
680
|
totalSteps,
|
|
680
681
|
currentStepIndex: stepIndex,
|
|
681
|
-
},
|
|
682
|
+
}),
|
|
682
683
|
isError: true,
|
|
683
684
|
};
|
|
684
685
|
}
|
|
@@ -691,13 +692,14 @@ export async function executeChain(params: ChainExecutionParams): Promise<ChainE
|
|
|
691
692
|
|
|
692
693
|
return {
|
|
693
694
|
content: [{ type: "text", text: summary }],
|
|
694
|
-
details: {
|
|
695
|
-
mode: "chain",
|
|
695
|
+
details: buildChainExecutionDetails({
|
|
696
696
|
results,
|
|
697
|
-
|
|
698
|
-
|
|
697
|
+
includeProgress,
|
|
698
|
+
allProgress,
|
|
699
|
+
allArtifactPaths,
|
|
700
|
+
artifactsDir,
|
|
699
701
|
chainAgents,
|
|
700
702
|
totalSteps,
|
|
701
|
-
},
|
|
703
|
+
}),
|
|
702
704
|
};
|
|
703
705
|
}
|
package/install.mjs
CHANGED
|
@@ -38,7 +38,7 @@ if (isRemove) {
|
|
|
38
38
|
if (fs.existsSync(EXTENSION_DIR)) {
|
|
39
39
|
console.log(`Removing ${EXTENSION_DIR}...`);
|
|
40
40
|
fs.rmSync(EXTENSION_DIR, { recursive: true });
|
|
41
|
-
console.log("
|
|
41
|
+
console.log("pi-subagents removed");
|
|
42
42
|
} else {
|
|
43
43
|
console.log("pi-subagents is not installed");
|
|
44
44
|
}
|
|
@@ -61,7 +61,7 @@ if (fs.existsSync(EXTENSION_DIR)) {
|
|
|
61
61
|
console.log("Updating existing installation...");
|
|
62
62
|
try {
|
|
63
63
|
execSync("git pull", { cwd: EXTENSION_DIR, stdio: "inherit" });
|
|
64
|
-
console.log("\
|
|
64
|
+
console.log("\npi-subagents updated");
|
|
65
65
|
} catch (err) {
|
|
66
66
|
console.error("Failed to update. Try removing and reinstalling:");
|
|
67
67
|
console.error(" npx pi-subagents --remove && npx pi-subagents");
|
|
@@ -77,7 +77,7 @@ if (fs.existsSync(EXTENSION_DIR)) {
|
|
|
77
77
|
console.log(`Cloning to ${EXTENSION_DIR}...`);
|
|
78
78
|
try {
|
|
79
79
|
execSync(`git clone ${REPO_URL} "${EXTENSION_DIR}"`, { stdio: "inherit" });
|
|
80
|
-
console.log("\
|
|
80
|
+
console.log("\npi-subagents installed");
|
|
81
81
|
} catch (err) {
|
|
82
82
|
console.error("Failed to clone repository");
|
|
83
83
|
process.exit(1);
|
package/package.json
CHANGED
package/parallel-utils.ts
CHANGED
|
@@ -102,15 +102,15 @@ export function aggregateParallelOutputs(
|
|
|
102
102
|
const hasOutput = Boolean(r.output?.trim());
|
|
103
103
|
const status =
|
|
104
104
|
r.exitCode === -1
|
|
105
|
-
? "
|
|
105
|
+
? "SKIPPED"
|
|
106
106
|
: r.exitCode !== 0 && r.exitCode !== null
|
|
107
|
-
?
|
|
107
|
+
? `FAILED (exit code ${r.exitCode})${r.error ? `: ${r.error}` : ""}`
|
|
108
108
|
: r.error
|
|
109
|
-
?
|
|
109
|
+
? `WARNING: ${r.error}`
|
|
110
110
|
: !hasOutput && r.outputTargetPath && r.outputTargetExists === false
|
|
111
|
-
?
|
|
111
|
+
? `EMPTY OUTPUT (expected output file missing: ${r.outputTargetPath})`
|
|
112
112
|
: !hasOutput && !r.outputTargetPath
|
|
113
|
-
? "
|
|
113
|
+
? "EMPTY OUTPUT (no textual response returned)"
|
|
114
114
|
: "";
|
|
115
115
|
const body = status ? (hasOutput ? `${status}\n${r.output}` : status) : r.output;
|
|
116
116
|
return `${header}\n${body}`;
|
|
@@ -78,6 +78,7 @@ interface PromptTemplateBridgeResult {
|
|
|
78
78
|
results?: Array<{
|
|
79
79
|
agent?: string;
|
|
80
80
|
messages?: unknown[];
|
|
81
|
+
finalOutput?: string;
|
|
81
82
|
exitCode?: number;
|
|
82
83
|
error?: string;
|
|
83
84
|
model?: string;
|
|
@@ -180,12 +181,13 @@ function sanitizeRecentTools(
|
|
|
180
181
|
tools: Array<{ tool?: string; args?: string }> | undefined,
|
|
181
182
|
): Array<{ tool: string; args: string }> | undefined {
|
|
182
183
|
if (!tools || tools.length === 0) return undefined;
|
|
183
|
-
const sanitized = tools
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
tool: entry.tool
|
|
184
|
+
const sanitized = tools.flatMap((entry) => {
|
|
185
|
+
if (typeof entry.tool !== "string" || entry.tool.trim().length === 0) return [];
|
|
186
|
+
return [{
|
|
187
|
+
tool: entry.tool,
|
|
187
188
|
args: typeof entry.args === "string" ? entry.args : String(entry.args ?? ""),
|
|
188
|
-
}
|
|
189
|
+
}];
|
|
190
|
+
});
|
|
189
191
|
return sanitized.length > 0 ? sanitized : undefined;
|
|
190
192
|
}
|
|
191
193
|
|
|
@@ -207,6 +209,15 @@ function resolveProgressModel(
|
|
|
207
209
|
return firstWithModel?.model;
|
|
208
210
|
}
|
|
209
211
|
|
|
212
|
+
function buildDelegationMessages(result: { messages?: unknown[]; finalOutput?: string }, fallbackText?: string): unknown[] {
|
|
213
|
+
if (Array.isArray(result.messages) && result.messages.length > 0) return result.messages;
|
|
214
|
+
const text = typeof result.finalOutput === "string" && result.finalOutput.trim().length > 0
|
|
215
|
+
? result.finalOutput.trim()
|
|
216
|
+
: fallbackText;
|
|
217
|
+
if (!text) return [];
|
|
218
|
+
return [{ role: "assistant", content: [{ type: "text", text }] }];
|
|
219
|
+
}
|
|
220
|
+
|
|
210
221
|
function toDelegationUpdate(requestId: string, update: PromptTemplateBridgeResult): PromptTemplateDelegationUpdate | undefined {
|
|
211
222
|
const progress = update.details?.progress?.[0];
|
|
212
223
|
const taskProgress = update.details?.progress?.map((entry) => {
|
|
@@ -324,7 +335,8 @@ export function registerPromptTemplateDelegationBridge<Ctx extends { cwd?: strin
|
|
|
324
335
|
options.events.emit(PROMPT_TEMPLATE_SUBAGENT_UPDATE_EVENT, payload);
|
|
325
336
|
},
|
|
326
337
|
);
|
|
327
|
-
const
|
|
338
|
+
const contentText = firstTextContent(result.content);
|
|
339
|
+
const messages = buildDelegationMessages(result.details?.results?.[0] ?? {}, contentText);
|
|
328
340
|
const parallelResults = request.tasks
|
|
329
341
|
? request.tasks.map<PromptTemplateDelegationParallelResult>((task, index) => {
|
|
330
342
|
const step = result.details?.results?.[index];
|
|
@@ -340,13 +352,12 @@ export function registerPromptTemplateDelegationBridge<Ctx extends { cwd?: strin
|
|
|
340
352
|
const errorText = step.error;
|
|
341
353
|
return {
|
|
342
354
|
agent: step.agent ?? task.agent,
|
|
343
|
-
messages: step
|
|
355
|
+
messages: buildDelegationMessages(step),
|
|
344
356
|
isError: (exitCode !== undefined && exitCode !== 0) || !!errorText,
|
|
345
357
|
errorText: errorText || undefined,
|
|
346
358
|
};
|
|
347
359
|
})
|
|
348
360
|
: undefined;
|
|
349
|
-
const contentText = firstTextContent(result.content);
|
|
350
361
|
const response: PromptTemplateDelegationResponse = {
|
|
351
362
|
...request,
|
|
352
363
|
messages,
|
package/render.ts
CHANGED
|
@@ -20,7 +20,6 @@ function getTermWidth(): number {
|
|
|
20
20
|
return process.stdout.columns || 120;
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
// Grapheme segmenter for proper Unicode handling (shared instance)
|
|
24
23
|
const segmenter = new Intl.Segmenter(undefined, { granularity: "grapheme" });
|
|
25
24
|
|
|
26
25
|
/**
|
|
@@ -35,42 +34,38 @@ const segmenter = new Intl.Segmenter(undefined, { granularity: "grapheme" });
|
|
|
35
34
|
function truncLine(text: string, maxWidth: number): string {
|
|
36
35
|
if (visibleWidth(text) <= maxWidth) return text;
|
|
37
36
|
|
|
38
|
-
const targetWidth = maxWidth - 1;
|
|
37
|
+
const targetWidth = maxWidth - 1;
|
|
39
38
|
let result = "";
|
|
40
39
|
let currentWidth = 0;
|
|
41
|
-
let activeStyles: string[] = [];
|
|
40
|
+
let activeStyles: string[] = [];
|
|
42
41
|
let i = 0;
|
|
43
42
|
|
|
44
43
|
while (i < text.length) {
|
|
45
|
-
// Check for ANSI escape code
|
|
46
44
|
const ansiMatch = text.slice(i).match(/^\x1b\[[0-9;]*m/);
|
|
47
45
|
if (ansiMatch) {
|
|
48
46
|
const code = ansiMatch[0];
|
|
49
47
|
result += code;
|
|
50
48
|
|
|
51
49
|
if (code === "\x1b[0m" || code === "\x1b[m") {
|
|
52
|
-
activeStyles = [];
|
|
50
|
+
activeStyles = [];
|
|
53
51
|
} else {
|
|
54
|
-
activeStyles.push(code);
|
|
52
|
+
activeStyles.push(code);
|
|
55
53
|
}
|
|
56
54
|
i += code.length;
|
|
57
55
|
continue;
|
|
58
56
|
}
|
|
59
57
|
|
|
60
|
-
// Find end of non-ANSI text segment
|
|
61
58
|
let end = i;
|
|
62
59
|
while (end < text.length && !text.slice(end).match(/^\x1b\[[0-9;]*m/)) {
|
|
63
60
|
end++;
|
|
64
61
|
}
|
|
65
62
|
|
|
66
|
-
// Segment into graphemes for proper Unicode handling
|
|
67
63
|
const textPortion = text.slice(i, end);
|
|
68
64
|
for (const seg of segmenter.segment(textPortion)) {
|
|
69
65
|
const grapheme = seg.segment;
|
|
70
66
|
const graphemeWidth = visibleWidth(grapheme);
|
|
71
67
|
|
|
72
68
|
if (currentWidth + graphemeWidth > targetWidth) {
|
|
73
|
-
// Re-apply all active styles before ellipsis to preserve background/colors
|
|
74
69
|
return result + activeStyles.join("") + "…";
|
|
75
70
|
}
|
|
76
71
|
|
|
@@ -80,16 +75,11 @@ function truncLine(text: string, maxWidth: number): string {
|
|
|
80
75
|
i = end;
|
|
81
76
|
}
|
|
82
77
|
|
|
83
|
-
// Reached end without exceeding width (shouldn't happen given initial check)
|
|
84
78
|
return result + activeStyles.join("") + "…";
|
|
85
79
|
}
|
|
86
80
|
|
|
87
|
-
// Track last rendered widget state to avoid no-op re-renders
|
|
88
81
|
let lastWidgetHash = "";
|
|
89
82
|
|
|
90
|
-
/**
|
|
91
|
-
* Compute a simple hash of job states for change detection
|
|
92
|
-
*/
|
|
93
83
|
function computeWidgetHash(jobs: AsyncJobState[]): string {
|
|
94
84
|
return jobs.slice(0, MAX_WIDGET_JOBS).map(job =>
|
|
95
85
|
`${job.asyncId}:${job.status}:${job.currentStep}:${job.updatedAt}:${job.totalTokens?.total ?? 0}`
|
|
@@ -124,13 +114,11 @@ export function renderWidget(ctx: ExtensionContext, jobs: AsyncJobState[]): void
|
|
|
124
114
|
return;
|
|
125
115
|
}
|
|
126
116
|
|
|
127
|
-
// Check if anything changed since last render
|
|
128
|
-
// Always re-render if any displayed job is running (output tail updates constantly)
|
|
129
117
|
const displayedJobs = jobs.slice(0, MAX_WIDGET_JOBS);
|
|
130
118
|
const hasRunningJobs = displayedJobs.some(job => job.status === "running");
|
|
131
119
|
const newHash = computeWidgetHash(jobs);
|
|
132
120
|
if (!hasRunningJobs && newHash === lastWidgetHash) {
|
|
133
|
-
return;
|
|
121
|
+
return;
|
|
134
122
|
}
|
|
135
123
|
lastWidgetHash = newHash;
|
|
136
124
|
|
|
@@ -194,12 +182,12 @@ export function renderSubagentResult(
|
|
|
194
182
|
const r = d.results[0];
|
|
195
183
|
const isRunning = r.progress?.status === "running";
|
|
196
184
|
const icon = isRunning
|
|
197
|
-
? theme.fg("warning", "
|
|
185
|
+
? theme.fg("warning", "running")
|
|
198
186
|
: r.detached
|
|
199
|
-
? theme.fg("warning", "
|
|
187
|
+
? theme.fg("warning", "detached")
|
|
200
188
|
: r.exitCode === 0
|
|
201
189
|
? theme.fg("success", "ok")
|
|
202
|
-
: theme.fg("error", "
|
|
190
|
+
: theme.fg("error", "failed");
|
|
203
191
|
const contextBadge = d.context === "fork" ? theme.fg("warning", " [fork]") : "";
|
|
204
192
|
const output = r.truncation?.text || getSingleResultOutput(r);
|
|
205
193
|
|
|
@@ -265,7 +253,7 @@ export function renderSubagentResult(
|
|
|
265
253
|
c.addChild(new Text(truncLine(theme.fg("dim", `Skills: ${r.skills.join(", ")}`), w), 0, 0));
|
|
266
254
|
}
|
|
267
255
|
if (r.skillsWarning) {
|
|
268
|
-
c.addChild(new Text(truncLine(theme.fg("warning",
|
|
256
|
+
c.addChild(new Text(truncLine(theme.fg("warning", `Warning: ${r.skillsWarning}`), w), 0, 0));
|
|
269
257
|
}
|
|
270
258
|
if (r.attemptedModels && r.attemptedModels.length > 1) {
|
|
271
259
|
c.addChild(new Text(truncLine(theme.fg("dim", `Fallbacks: ${r.attemptedModels.join(" → ")}`), w), 0, 0));
|
|
@@ -291,12 +279,12 @@ export function renderSubagentResult(
|
|
|
291
279
|
&& hasEmptyTextOutputWithoutOutputTarget(r.task, getSingleResultOutput(r)),
|
|
292
280
|
);
|
|
293
281
|
const icon = hasRunning
|
|
294
|
-
? theme.fg("warning", "
|
|
282
|
+
? theme.fg("warning", "running")
|
|
295
283
|
: hasEmptyWithoutTarget
|
|
296
|
-
? theme.fg("warning", "
|
|
284
|
+
? theme.fg("warning", "warning")
|
|
297
285
|
: ok === d.results.length
|
|
298
286
|
? theme.fg("success", "ok")
|
|
299
|
-
: theme.fg("error", "
|
|
287
|
+
: theme.fg("error", "failed");
|
|
300
288
|
|
|
301
289
|
const totalSummary =
|
|
302
290
|
d.progressSummary ||
|
|
@@ -323,17 +311,11 @@ export function renderSubagentResult(
|
|
|
323
311
|
|
|
324
312
|
const modeLabel = d.mode;
|
|
325
313
|
const contextBadge = d.context === "fork" ? theme.fg("warning", " [fork]") : "";
|
|
326
|
-
// For parallel-in-chain, show task count (results) for consistency with step display
|
|
327
|
-
// For sequential chains, show logical step count
|
|
328
314
|
const hasParallelInChain = d.chainAgents?.some((a) => a.startsWith("["));
|
|
329
315
|
const totalCount = hasParallelInChain ? d.results.length : (d.totalSteps ?? d.results.length);
|
|
330
316
|
const currentStep = d.currentStepIndex !== undefined ? d.currentStepIndex + 1 : ok + 1;
|
|
331
317
|
const stepInfo = hasRunning ? ` ${currentStep}/${totalCount}` : ` ${ok}/${totalCount}`;
|
|
332
318
|
|
|
333
|
-
// Build chain visualization: "scout → planner" with status icons
|
|
334
|
-
// Note: Only works correctly for sequential chains. Chains with parallel steps
|
|
335
|
-
// (indicated by "[agent1+agent2]" format) have multiple results per step,
|
|
336
|
-
// breaking the 1:1 mapping between chainAgents and results.
|
|
337
319
|
const chainVis = d.chainAgents?.length && !hasParallelInChain
|
|
338
320
|
? d.chainAgents
|
|
339
321
|
.map((agent, i) => {
|
|
@@ -345,14 +327,14 @@ export function renderSubagentResult(
|
|
|
345
327
|
&& hasEmptyTextOutputWithoutOutputTarget(result.task, getSingleResultOutput(result));
|
|
346
328
|
const isCurrent = i === (d.currentStepIndex ?? d.results.length);
|
|
347
329
|
const stepIcon = isFailed
|
|
348
|
-
? theme.fg("error", "
|
|
330
|
+
? theme.fg("error", "failed")
|
|
349
331
|
: isEmptyWithoutTarget
|
|
350
|
-
? theme.fg("warning", "
|
|
332
|
+
? theme.fg("warning", "warning")
|
|
351
333
|
: isComplete
|
|
352
|
-
? theme.fg("success", "
|
|
334
|
+
? theme.fg("success", "done")
|
|
353
335
|
: isCurrent && hasRunning
|
|
354
|
-
? theme.fg("warning", "
|
|
355
|
-
: theme.fg("dim", "
|
|
336
|
+
? theme.fg("warning", "running")
|
|
337
|
+
: theme.fg("dim", "pending");
|
|
356
338
|
return `${stepIcon} ${agent}`;
|
|
357
339
|
})
|
|
358
340
|
.join(theme.fg("dim", " → "))
|
|
@@ -367,15 +349,10 @@ export function renderSubagentResult(
|
|
|
367
349
|
0,
|
|
368
350
|
),
|
|
369
351
|
);
|
|
370
|
-
// Show chain visualization
|
|
371
352
|
if (chainVis) {
|
|
372
353
|
c.addChild(new Text(truncLine(` ${chainVis}`, w), 0, 0));
|
|
373
354
|
}
|
|
374
355
|
|
|
375
|
-
// === STATIC STEP LAYOUT (like clarification UI) ===
|
|
376
|
-
// Each step gets a fixed section with task/output/status
|
|
377
|
-
// Note: For chains with parallel steps, chainAgents indices don't map 1:1 to results
|
|
378
|
-
// (parallel steps produce multiple results). Fall back to result-based iteration.
|
|
379
356
|
const useResultsDirectly = hasParallelInChain || !d.chainAgents?.length;
|
|
380
357
|
const stepsToShow = useResultsDirectly ? d.results.length : d.chainAgents!.length;
|
|
381
358
|
|
|
@@ -388,9 +365,8 @@ export function renderSubagentResult(
|
|
|
388
365
|
: (d.chainAgents![i] || r?.agent || `step-${i + 1}`);
|
|
389
366
|
|
|
390
367
|
if (!r) {
|
|
391
|
-
// Pending step
|
|
392
368
|
c.addChild(new Text(truncLine(theme.fg("dim", ` Step ${i + 1}: ${agentName}`), w), 0, 0));
|
|
393
|
-
c.addChild(new Text(theme.fg("dim", ` status:
|
|
369
|
+
c.addChild(new Text(theme.fg("dim", ` status: pending`), 0, 0));
|
|
394
370
|
c.addChild(new Spacer(1));
|
|
395
371
|
continue;
|
|
396
372
|
}
|
|
@@ -402,12 +378,12 @@ export function renderSubagentResult(
|
|
|
402
378
|
|
|
403
379
|
const resultOutput = getSingleResultOutput(r);
|
|
404
380
|
const statusIcon = rRunning
|
|
405
|
-
? theme.fg("warning", "
|
|
381
|
+
? theme.fg("warning", "running")
|
|
406
382
|
: r.exitCode !== 0
|
|
407
|
-
? theme.fg("error", "
|
|
383
|
+
? theme.fg("error", "failed")
|
|
408
384
|
: hasEmptyTextOutputWithoutOutputTarget(r.task, resultOutput)
|
|
409
|
-
? theme.fg("warning", "
|
|
410
|
-
: theme.fg("success", "
|
|
385
|
+
? theme.fg("warning", "warning")
|
|
386
|
+
: theme.fg("success", "done");
|
|
411
387
|
const stats = rProg ? ` | ${rProg.toolCount} tools, ${formatDuration(rProg.durationMs)}` : "";
|
|
412
388
|
const modelDisplay = r.model ? theme.fg("dim", ` (${r.model})`) : "";
|
|
413
389
|
const stepHeader = rRunning
|
|
@@ -430,7 +406,7 @@ export function renderSubagentResult(
|
|
|
430
406
|
c.addChild(new Text(truncLine(theme.fg("dim", ` skills: ${r.skills.join(", ")}`), w), 0, 0));
|
|
431
407
|
}
|
|
432
408
|
if (r.skillsWarning) {
|
|
433
|
-
c.addChild(new Text(truncLine(theme.fg("warning", `
|
|
409
|
+
c.addChild(new Text(truncLine(theme.fg("warning", ` Warning: ${r.skillsWarning}`), w), 0, 0));
|
|
434
410
|
}
|
|
435
411
|
if (r.attemptedModels && r.attemptedModels.length > 1) {
|
|
436
412
|
c.addChild(new Text(truncLine(theme.fg("dim", ` fallbacks: ${r.attemptedModels.join(" → ")}`), w), 0, 0));
|
|
@@ -440,7 +416,6 @@ export function renderSubagentResult(
|
|
|
440
416
|
if (rProg.skills?.length) {
|
|
441
417
|
c.addChild(new Text(truncLine(theme.fg("accent", ` skills: ${rProg.skills.join(", ")}`), w), 0, 0));
|
|
442
418
|
}
|
|
443
|
-
// Current tool for running step
|
|
444
419
|
if (rProg.currentTool) {
|
|
445
420
|
const maxToolArgsLen = Math.max(50, w - 20);
|
|
446
421
|
const toolArgsPreview = rProg.currentToolArgs
|
|
@@ -453,7 +428,6 @@ export function renderSubagentResult(
|
|
|
453
428
|
: rProg.currentTool;
|
|
454
429
|
c.addChild(new Text(truncLine(theme.fg("warning", ` > ${toolLine}`), w), 0, 0));
|
|
455
430
|
}
|
|
456
|
-
// Recent tools
|
|
457
431
|
if (rProg.recentTools?.length) {
|
|
458
432
|
for (const t of rProg.recentTools.slice(-3)) {
|
|
459
433
|
const maxArgsLen = Math.max(40, w - 30);
|
|
@@ -463,7 +437,6 @@ export function renderSubagentResult(
|
|
|
463
437
|
c.addChild(new Text(truncLine(theme.fg("dim", ` ${t.tool}: ${argsPreview}`), w), 0, 0));
|
|
464
438
|
}
|
|
465
439
|
}
|
|
466
|
-
// Recent output - let truncLine handle truncation entirely
|
|
467
440
|
const recentLines = (rProg.recentOutput ?? []).slice(-5);
|
|
468
441
|
for (const line of recentLines) {
|
|
469
442
|
c.addChild(new Text(truncLine(theme.fg("dim", ` ${line}`), w), 0, 0));
|
package/single-output.ts
CHANGED
|
@@ -84,11 +84,11 @@ export function finalizeSingleOutput(params: {
|
|
|
84
84
|
}): { displayOutput: string; savedPath?: string; saveError?: string } {
|
|
85
85
|
let displayOutput = params.truncatedOutput || params.fullOutput;
|
|
86
86
|
if (params.exitCode === 0 && params.savedPath) {
|
|
87
|
-
displayOutput += `\n\
|
|
87
|
+
displayOutput += `\n\nOutput saved to: ${params.savedPath}`;
|
|
88
88
|
return { displayOutput, savedPath: params.savedPath };
|
|
89
89
|
}
|
|
90
90
|
if (params.exitCode === 0 && params.saveError && params.outputPath) {
|
|
91
|
-
displayOutput += `\n\
|
|
91
|
+
displayOutput += `\n\nFailed to save output to: ${params.outputPath}\n${params.saveError}`;
|
|
92
92
|
return { displayOutput, saveError: params.saveError };
|
|
93
93
|
}
|
|
94
94
|
return { displayOutput };
|
package/subagent-executor.ts
CHANGED
|
@@ -25,7 +25,7 @@ import { executeAsyncChain, executeAsyncSingle, isAsyncAvailable } from "./async
|
|
|
25
25
|
import { createForkContextResolver } from "./fork-context.ts";
|
|
26
26
|
import { applyIntercomBridgeToAgent, resolveIntercomBridge, resolveIntercomSessionTarget } from "./intercom-bridge.ts";
|
|
27
27
|
import { finalizeSingleOutput, injectSingleOutputInstruction, resolveSingleOutputPath } from "./single-output.ts";
|
|
28
|
-
import { getSingleResultOutput, mapConcurrent } from "./utils.ts";
|
|
28
|
+
import { compactForegroundDetails, getSingleResultOutput, mapConcurrent } from "./utils.ts";
|
|
29
29
|
import {
|
|
30
30
|
cleanupWorktrees,
|
|
31
31
|
createWorktrees,
|
|
@@ -885,12 +885,12 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
885
885
|
|
|
886
886
|
return {
|
|
887
887
|
content: [{ type: "text", text: fullContent }],
|
|
888
|
-
details: {
|
|
888
|
+
details: compactForegroundDetails({
|
|
889
889
|
mode: "parallel",
|
|
890
890
|
results,
|
|
891
891
|
progress: params.includeProgress ? allProgress : undefined,
|
|
892
892
|
artifacts: allArtifactPaths.length ? { dir: artifactsDir, files: allArtifactPaths } : undefined,
|
|
893
|
-
},
|
|
893
|
+
}),
|
|
894
894
|
};
|
|
895
895
|
} finally {
|
|
896
896
|
if (worktreeSetup) cleanupWorktrees(worktreeSetup);
|
|
@@ -1063,37 +1063,37 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
1063
1063
|
if (r.detached) {
|
|
1064
1064
|
return {
|
|
1065
1065
|
content: [{ type: "text", text: `Detached for intercom coordination: ${params.agent}` }],
|
|
1066
|
-
details: {
|
|
1066
|
+
details: compactForegroundDetails({
|
|
1067
1067
|
mode: "single",
|
|
1068
1068
|
results: [r],
|
|
1069
1069
|
progress: params.includeProgress ? allProgress : undefined,
|
|
1070
1070
|
artifacts: allArtifactPaths.length ? { dir: artifactsDir, files: allArtifactPaths } : undefined,
|
|
1071
1071
|
truncation: r.truncation,
|
|
1072
|
-
},
|
|
1072
|
+
}),
|
|
1073
1073
|
};
|
|
1074
1074
|
}
|
|
1075
1075
|
|
|
1076
1076
|
if (r.exitCode !== 0)
|
|
1077
1077
|
return {
|
|
1078
1078
|
content: [{ type: "text", text: r.error || "Failed" }],
|
|
1079
|
-
details: {
|
|
1079
|
+
details: compactForegroundDetails({
|
|
1080
1080
|
mode: "single",
|
|
1081
1081
|
results: [r],
|
|
1082
1082
|
progress: params.includeProgress ? allProgress : undefined,
|
|
1083
1083
|
artifacts: allArtifactPaths.length ? { dir: artifactsDir, files: allArtifactPaths } : undefined,
|
|
1084
1084
|
truncation: r.truncation,
|
|
1085
|
-
},
|
|
1085
|
+
}),
|
|
1086
1086
|
isError: true,
|
|
1087
1087
|
};
|
|
1088
1088
|
return {
|
|
1089
1089
|
content: [{ type: "text", text: finalizedOutput.displayOutput || "(no output)" }],
|
|
1090
|
-
details: {
|
|
1090
|
+
details: compactForegroundDetails({
|
|
1091
1091
|
mode: "single",
|
|
1092
1092
|
results: [r],
|
|
1093
1093
|
progress: params.includeProgress ? allProgress : undefined,
|
|
1094
1094
|
artifacts: allArtifactPaths.length ? { dir: artifactsDir, files: allArtifactPaths } : undefined,
|
|
1095
1095
|
truncation: r.truncation,
|
|
1096
|
-
},
|
|
1096
|
+
}),
|
|
1097
1097
|
};
|
|
1098
1098
|
}
|
|
1099
1099
|
|
package/subagent-runner.ts
CHANGED
|
@@ -555,12 +555,12 @@ async function runSingleStep(
|
|
|
555
555
|
}
|
|
556
556
|
if (resolvedOutput.savedPath) {
|
|
557
557
|
outputForSummary = outputForSummary
|
|
558
|
-
? `${outputForSummary}\n\
|
|
559
|
-
:
|
|
558
|
+
? `${outputForSummary}\n\nOutput saved to: ${resolvedOutput.savedPath}`
|
|
559
|
+
: `Output saved to: ${resolvedOutput.savedPath}`;
|
|
560
560
|
} else if (resolvedOutput.saveError && step.outputPath && finalResult?.exitCode === 0) {
|
|
561
561
|
outputForSummary = outputForSummary
|
|
562
|
-
? `${outputForSummary}\n\
|
|
563
|
-
:
|
|
562
|
+
? `${outputForSummary}\n\nFailed to save output to: ${step.outputPath}\n${resolvedOutput.saveError}`
|
|
563
|
+
: `Failed to save output to: ${step.outputPath}\n${resolvedOutput.saveError}`;
|
|
564
564
|
}
|
|
565
565
|
|
|
566
566
|
if (artifactPaths && ctx.artifactConfig?.enabled !== false) {
|
package/types.ts
CHANGED
package/utils.ts
CHANGED
|
@@ -6,13 +6,12 @@ import * as fs from "node:fs";
|
|
|
6
6
|
import * as os from "node:os";
|
|
7
7
|
import * as path from "node:path";
|
|
8
8
|
import type { Message } from "@mariozechner/pi-ai";
|
|
9
|
-
import type { AsyncStatus, DisplayItem, ErrorInfo, SingleResult } from "./types.ts";
|
|
9
|
+
import type { AgentProgress, AsyncStatus, Details, DisplayItem, ErrorInfo, SingleResult } from "./types.ts";
|
|
10
10
|
|
|
11
11
|
// ============================================================================
|
|
12
12
|
// File System Utilities
|
|
13
13
|
// ============================================================================
|
|
14
14
|
|
|
15
|
-
// Cache for status file reads - avoid re-reading unchanged files
|
|
16
15
|
const statusCache = new Map<string, { mtime: number; status: AsyncStatus }>();
|
|
17
16
|
|
|
18
17
|
function getErrorMessage(error: unknown): string {
|
|
@@ -74,7 +73,6 @@ export function readStatus(asyncDir: string): AsyncStatus | null {
|
|
|
74
73
|
return status;
|
|
75
74
|
}
|
|
76
75
|
|
|
77
|
-
// Cache for output tail reads - avoid re-reading unchanged files
|
|
78
76
|
const outputTailCache = new Map<string, { mtime: number; size: number; lines: string[] }>();
|
|
79
77
|
|
|
80
78
|
/**
|
|
@@ -87,7 +85,6 @@ export function getOutputTail(outputFile: string | undefined, maxLines: number =
|
|
|
87
85
|
const stat = fs.statSync(outputFile);
|
|
88
86
|
if (stat.size === 0) return [];
|
|
89
87
|
|
|
90
|
-
// Check cache using both mtime and size (size changes more frequently during writes)
|
|
91
88
|
const cached = outputTailCache.get(outputFile);
|
|
92
89
|
if (cached && cached.mtime === stat.mtimeMs && cached.size === stat.size) {
|
|
93
90
|
return cached.lines;
|
|
@@ -102,9 +99,7 @@ export function getOutputTail(outputFile: string | undefined, maxLines: number =
|
|
|
102
99
|
const allLines = content.split("\n").filter((l) => l.trim());
|
|
103
100
|
const lines = allLines.slice(-maxLines).map((l) => l.slice(0, 120) + (l.length > 120 ? "..." : ""));
|
|
104
101
|
|
|
105
|
-
// Cache the result
|
|
106
102
|
outputTailCache.set(outputFile, { mtime: stat.mtimeMs, size: stat.size, lines });
|
|
107
|
-
// Limit cache size
|
|
108
103
|
if (outputTailCache.size > 20) {
|
|
109
104
|
const firstKey = outputTailCache.keys().next().value;
|
|
110
105
|
if (firstKey) outputTailCache.delete(firstKey);
|
|
@@ -112,12 +107,15 @@ export function getOutputTail(outputFile: string | undefined, maxLines: number =
|
|
|
112
107
|
|
|
113
108
|
return lines;
|
|
114
109
|
} catch {
|
|
110
|
+
// Output tails are UI-only hints; unreadable or missing files should render as no tail.
|
|
115
111
|
return [];
|
|
116
112
|
} finally {
|
|
117
113
|
if (fd !== null) {
|
|
118
114
|
try {
|
|
119
115
|
fs.closeSync(fd);
|
|
120
|
-
} catch {
|
|
116
|
+
} catch {
|
|
117
|
+
// Closing the best-effort tail file handle should not surface over the main status view.
|
|
118
|
+
}
|
|
121
119
|
}
|
|
122
120
|
}
|
|
123
121
|
}
|
|
@@ -125,16 +123,16 @@ export function getOutputTail(outputFile: string | undefined, maxLines: number =
|
|
|
125
123
|
/**
|
|
126
124
|
* Get human-readable last activity time for a file
|
|
127
125
|
*/
|
|
128
|
-
export function getLastActivity(outputFile: string | undefined): string {
|
|
126
|
+
export function getLastActivity(outputFile: string | undefined): string {
|
|
129
127
|
if (!outputFile) return "";
|
|
130
128
|
try {
|
|
131
|
-
// Single stat call - throws if file doesn't exist
|
|
132
129
|
const stat = fs.statSync(outputFile);
|
|
133
130
|
const ago = Date.now() - stat.mtimeMs;
|
|
134
131
|
if (ago < 1000) return "active now";
|
|
135
132
|
if (ago < 60000) return `active ${Math.floor(ago / 1000)}s ago`;
|
|
136
133
|
return `active ${Math.floor(ago / 60000)}m ago`;
|
|
137
134
|
} catch {
|
|
135
|
+
// Last-activity text is best effort; missing files should simply omit the hint.
|
|
138
136
|
return "";
|
|
139
137
|
}
|
|
140
138
|
}
|
|
@@ -201,13 +199,14 @@ export function getFinalOutput(messages: Message[]): string {
|
|
|
201
199
|
}
|
|
202
200
|
|
|
203
201
|
export function getSingleResultOutput(result: Pick<SingleResult, "finalOutput" | "messages">): string {
|
|
204
|
-
return result.finalOutput ?? getFinalOutput(result.messages);
|
|
202
|
+
return result.finalOutput ?? getFinalOutput(result.messages ?? []);
|
|
205
203
|
}
|
|
206
204
|
|
|
207
205
|
/**
|
|
208
206
|
* Extract display items (text and tool calls) from messages
|
|
209
207
|
*/
|
|
210
|
-
export function getDisplayItems(messages: Message[]): DisplayItem[] {
|
|
208
|
+
export function getDisplayItems(messages: Message[] | undefined): DisplayItem[] {
|
|
209
|
+
if (!messages || messages.length === 0) return [];
|
|
211
210
|
const items: DisplayItem[] = [];
|
|
212
211
|
for (const msg of messages) {
|
|
213
212
|
if (msg.role === "assistant") {
|
|
@@ -220,6 +219,43 @@ export function getDisplayItems(messages: Message[]): DisplayItem[] {
|
|
|
220
219
|
return items;
|
|
221
220
|
}
|
|
222
221
|
|
|
222
|
+
function compactCompletedProgress(progress: AgentProgress): AgentProgress {
|
|
223
|
+
if (progress.status === "running") return progress;
|
|
224
|
+
return {
|
|
225
|
+
index: progress.index,
|
|
226
|
+
agent: progress.agent,
|
|
227
|
+
status: progress.status,
|
|
228
|
+
task: progress.task,
|
|
229
|
+
skills: progress.skills,
|
|
230
|
+
toolCount: progress.toolCount,
|
|
231
|
+
tokens: progress.tokens,
|
|
232
|
+
durationMs: progress.durationMs,
|
|
233
|
+
error: progress.error,
|
|
234
|
+
failedTool: progress.failedTool,
|
|
235
|
+
recentTools: [],
|
|
236
|
+
recentOutput: [],
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
export function compactForegroundResult(result: SingleResult): SingleResult {
|
|
241
|
+
if (result.progress?.status === "running") return result;
|
|
242
|
+
return {
|
|
243
|
+
...result,
|
|
244
|
+
messages: undefined,
|
|
245
|
+
progress: undefined,
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
export function compactForegroundDetails(details: Details): Details {
|
|
250
|
+
return {
|
|
251
|
+
...details,
|
|
252
|
+
results: details.results.map(compactForegroundResult),
|
|
253
|
+
progress: details.progress
|
|
254
|
+
? details.progress.map(compactCompletedProgress)
|
|
255
|
+
: undefined,
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
|
|
223
259
|
/**
|
|
224
260
|
* Detect errors in subagent execution from messages (only errors with no subsequent success)
|
|
225
261
|
*/
|