pi-subagents 0.29.0 → 0.31.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 +43 -0
- package/README.md +125 -19
- package/agents/context-builder.md +3 -3
- package/agents/planner.md +1 -1
- package/agents/researcher.md +1 -1
- package/agents/scout.md +1 -1
- package/package.json +7 -7
- package/skills/pi-subagents/SKILL.md +30 -0
- package/src/agents/agent-management.ts +189 -8
- package/src/agents/agent-serializer.ts +35 -12
- package/src/agents/agents.ts +243 -24
- package/src/agents/frontmatter.ts +66 -2
- package/src/agents/proactive-skills.ts +191 -0
- package/src/agents/skills.ts +117 -20
- package/src/extension/doctor.ts +20 -0
- package/src/extension/fanout-child.ts +2 -1
- package/src/extension/index.ts +50 -5
- package/src/extension/schemas.ts +40 -79
- package/src/intercom/intercom-bridge.ts +2 -3
- package/src/runs/background/async-execution.ts +180 -67
- package/src/runs/background/async-job-tracker.ts +56 -11
- package/src/runs/background/async-resume.ts +53 -5
- package/src/runs/background/async-status.ts +4 -1
- package/src/runs/background/chain-append.ts +282 -0
- package/src/runs/background/chain-root-attachment.ts +161 -0
- package/src/runs/background/result-watcher.ts +11 -2
- package/src/runs/background/run-status.ts +1 -0
- package/src/runs/background/stale-run-reconciler.ts +9 -4
- package/src/runs/background/subagent-runner.ts +158 -11
- package/src/runs/foreground/chain-execution.ts +26 -2
- package/src/runs/foreground/execution.ts +114 -8
- package/src/runs/foreground/subagent-executor.ts +611 -87
- package/src/runs/shared/acceptance.ts +285 -34
- package/src/runs/shared/chain-outputs.ts +23 -8
- package/src/runs/shared/completion-guard.ts +1 -1
- package/src/runs/shared/dynamic-fanout.ts +5 -3
- package/src/runs/shared/mcp-direct-tool-allowlist.ts +2 -2
- package/src/runs/shared/parallel-utils.ts +13 -1
- package/src/runs/shared/pi-args.ts +12 -3
- package/src/runs/shared/single-output.ts +15 -1
- package/src/runs/shared/subagent-control.ts +8 -11
- package/src/shared/settings.ts +1 -0
- package/src/shared/types.ts +17 -2
- package/src/shared/utils.ts +19 -1
- package/src/slash/prompt-template-bridge.ts +26 -3
- package/src/slash/slash-bridge.ts +3 -1
- package/src/slash/slash-commands.ts +34 -4
- package/src/tui/render.ts +265 -13
package/src/tui/render.ts
CHANGED
|
@@ -88,6 +88,33 @@ function truncLine(text: string, maxWidth: number): string {
|
|
|
88
88
|
return result + activeStyles.join("") + "…";
|
|
89
89
|
}
|
|
90
90
|
|
|
91
|
+
function wrapPlainText(text: string, maxWidth: number): string[] {
|
|
92
|
+
if (maxWidth <= 0) return [""];
|
|
93
|
+
const lines: string[] = [];
|
|
94
|
+
for (const rawLine of text.split("\n")) {
|
|
95
|
+
if (rawLine.length === 0) {
|
|
96
|
+
lines.push("");
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
let current = "";
|
|
100
|
+
let currentWidth = 0;
|
|
101
|
+
for (const seg of segmenter.segment(rawLine)) {
|
|
102
|
+
const grapheme = seg.segment;
|
|
103
|
+
const graphemeWidth = visibleWidth(grapheme);
|
|
104
|
+
if (currentWidth > 0 && currentWidth + graphemeWidth > maxWidth) {
|
|
105
|
+
lines.push(current);
|
|
106
|
+
current = grapheme;
|
|
107
|
+
currentWidth = graphemeWidth;
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
current += grapheme;
|
|
111
|
+
currentWidth += graphemeWidth;
|
|
112
|
+
}
|
|
113
|
+
lines.push(current);
|
|
114
|
+
}
|
|
115
|
+
return lines;
|
|
116
|
+
}
|
|
117
|
+
|
|
91
118
|
const RUNNING_FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
|
92
119
|
const STATIC_RUNNING_GLYPH = "●";
|
|
93
120
|
|
|
@@ -134,7 +161,7 @@ export function clearLegacyResultAnimationTimer(context: LegacyResultAnimationCo
|
|
|
134
161
|
function extractOutputTarget(task: string): string | undefined {
|
|
135
162
|
const writeToMatch = task.match(/\[Write to:\s*([^\]\n]+)\]/i);
|
|
136
163
|
if (writeToMatch?.[1]?.trim()) return writeToMatch[1].trim();
|
|
137
|
-
const findingsMatch = task.match(/Write your findings to
|
|
164
|
+
const findingsMatch = task.match(/Write your findings to(?: exactly this path)?:\s*([^\r\n]+)/i);
|
|
138
165
|
if (findingsMatch?.[1]?.trim()) return findingsMatch[1].trim();
|
|
139
166
|
const outputMatch = task.match(/[Oo]utput(?:\s+to)?\s*:\s*(\S+)/i);
|
|
140
167
|
if (outputMatch?.[1]?.trim()) return outputMatch[1].trim();
|
|
@@ -231,8 +258,11 @@ function resultStatusLine(result: Details["results"][number], output: string): s
|
|
|
231
258
|
return "Done";
|
|
232
259
|
}
|
|
233
260
|
|
|
234
|
-
function resultGlyph(result: Details["results"][number], output: string, theme: Theme, running = result.progress?.status === "running", seed = progressRunningSeed(result.progress ?? result.progressSummary)): string {
|
|
235
|
-
if (running)
|
|
261
|
+
function resultGlyph(result: Details["results"][number], output: string, theme: Theme, running = result.progress?.status === "running", seed = progressRunningSeed(result.progress ?? result.progressSummary), frame?: number): string {
|
|
262
|
+
if (running) {
|
|
263
|
+
if (frame !== undefined) return theme.fg("accent", runningGlyph((seed ?? 0) + frame));
|
|
264
|
+
return theme.fg("accent", runningGlyph(seed));
|
|
265
|
+
}
|
|
236
266
|
if (result.detached) return theme.fg("warning", "■");
|
|
237
267
|
if (result.interrupted) return theme.fg("warning", "■");
|
|
238
268
|
if (result.exitCode !== 0) return theme.fg("error", "✗");
|
|
@@ -899,11 +929,189 @@ function compactSingleWidgetLines(job: AsyncJobState, theme: Theme, width: numbe
|
|
|
899
929
|
return lines.map((line) => truncLine(line, width));
|
|
900
930
|
}
|
|
901
931
|
|
|
932
|
+
type WidgetRenderTier = "full" | "single-line" | "progressive";
|
|
933
|
+
|
|
934
|
+
interface WidgetLayoutSession {
|
|
935
|
+
expanded: boolean;
|
|
936
|
+
rows: number;
|
|
937
|
+
columns: number;
|
|
938
|
+
tier: WidgetRenderTier;
|
|
939
|
+
lockedRows?: number;
|
|
940
|
+
visibleJobKeys: string[];
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
const RESERVED_NON_WIDGET_ROWS = 19;
|
|
944
|
+
|
|
945
|
+
let widgetLayoutSession: WidgetLayoutSession | undefined;
|
|
946
|
+
|
|
947
|
+
function resetWidgetLayoutSession(): void {
|
|
948
|
+
widgetLayoutSession = undefined;
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
function estimateAvailableWidgetRows(): number {
|
|
952
|
+
const rows = process.stdout.rows || 30;
|
|
953
|
+
return Math.max(1, rows - RESERVED_NON_WIDGET_ROWS);
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
function currentTerminalRows(): number {
|
|
957
|
+
return process.stdout.rows || 30;
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
function currentTerminalColumns(): number {
|
|
961
|
+
return process.stdout.columns || 120;
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
function widgetSessionMatches(expanded: boolean): boolean {
|
|
965
|
+
return widgetLayoutSession?.expanded === expanded
|
|
966
|
+
&& widgetLayoutSession.rows === currentTerminalRows()
|
|
967
|
+
&& widgetLayoutSession.columns === currentTerminalColumns();
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
function widgetHeaderCounts(jobs: AsyncJobState[]): { running: AsyncJobState[]; queued: AsyncJobState[]; complete: AsyncJobState[]; failed: AsyncJobState[]; paused: AsyncJobState[] } {
|
|
971
|
+
return {
|
|
972
|
+
running: jobs.filter((job) => job.status === "running"),
|
|
973
|
+
queued: jobs.filter((job) => job.status === "queued"),
|
|
974
|
+
complete: jobs.filter((job) => job.status === "complete"),
|
|
975
|
+
failed: jobs.filter((job) => job.status === "failed"),
|
|
976
|
+
paused: jobs.filter((job) => job.status === "paused"),
|
|
977
|
+
};
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
function buildSingleLineWidgetLines(jobs: AsyncJobState[], theme: Theme, width: number): string[] {
|
|
981
|
+
const counts = widgetHeaderCounts(jobs);
|
|
982
|
+
const hasActive = counts.running.length > 0 || counts.queued.length > 0;
|
|
983
|
+
const glyph = counts.running.length > 0 ? runningGlyph(widgetJobsRunningSeed(counts.running)) : hasActive ? "●" : "○";
|
|
984
|
+
const parts: string[] = [];
|
|
985
|
+
if (counts.running.length > 0) parts.push(`${counts.running.length}/${jobs.length} running`);
|
|
986
|
+
if (counts.queued.length > 0) parts.push(`${counts.queued.length} queued`);
|
|
987
|
+
if (counts.failed.length > 0) parts.push(`${counts.failed.length} failed`);
|
|
988
|
+
if (counts.paused.length > 0) parts.push(`${counts.paused.length} paused`);
|
|
989
|
+
if (!hasActive && counts.complete.length > 0) parts.push(`${counts.complete.length}/${jobs.length} done`);
|
|
990
|
+
return [truncLine(`${theme.fg(hasActive ? "accent" : "dim", glyph)} ${theme.fg(hasActive ? "accent" : "dim", "subagents")} (${parts.join(", ") || `${jobs.length} total`})`, width)];
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
function orderedWidgetJobs(jobs: AsyncJobState[]): AsyncJobState[] {
|
|
994
|
+
return [
|
|
995
|
+
...jobs.filter((job) => job.status === "running"),
|
|
996
|
+
...jobs.filter((job) => job.status === "queued"),
|
|
997
|
+
...jobs.filter((job) => job.status !== "running" && job.status !== "queued"),
|
|
998
|
+
];
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
function progressiveJobKey(job: AsyncJobState): string {
|
|
1002
|
+
return job.asyncId;
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
function isProgressiveActiveJob(job: AsyncJobState | undefined): boolean {
|
|
1006
|
+
return job?.status === "running" || job?.status === "queued";
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
function selectProgressiveJobKeys(jobs: AsyncJobState[], previousKeys: string[], bodyRows: number): string[] {
|
|
1010
|
+
if (bodyRows <= 0) return [];
|
|
1011
|
+
const jobsByKey = new Map(jobs.map((job) => [progressiveJobKey(job), job]));
|
|
1012
|
+
const selected: string[] = [];
|
|
1013
|
+
const append = (key: string): void => {
|
|
1014
|
+
if (selected.includes(key) || !jobsByKey.has(key)) return;
|
|
1015
|
+
selected.push(key);
|
|
1016
|
+
};
|
|
1017
|
+
for (const key of previousKeys) {
|
|
1018
|
+
if (!isProgressiveActiveJob(jobsByKey.get(key))) continue;
|
|
1019
|
+
append(key);
|
|
1020
|
+
if (selected.length >= bodyRows) return selected;
|
|
1021
|
+
}
|
|
1022
|
+
for (const job of orderedWidgetJobs(jobs)) {
|
|
1023
|
+
if (!isProgressiveActiveJob(job)) continue;
|
|
1024
|
+
const key = progressiveJobKey(job);
|
|
1025
|
+
append(key);
|
|
1026
|
+
if (selected.length >= bodyRows) break;
|
|
1027
|
+
}
|
|
1028
|
+
if (selected.length >= bodyRows) return selected;
|
|
1029
|
+
for (const key of previousKeys) {
|
|
1030
|
+
if (isProgressiveActiveJob(jobsByKey.get(key))) continue;
|
|
1031
|
+
append(key);
|
|
1032
|
+
if (selected.length >= bodyRows) return selected;
|
|
1033
|
+
}
|
|
1034
|
+
for (const job of orderedWidgetJobs(jobs)) {
|
|
1035
|
+
const key = progressiveJobKey(job);
|
|
1036
|
+
append(key);
|
|
1037
|
+
if (selected.length >= bodyRows) break;
|
|
1038
|
+
}
|
|
1039
|
+
return selected;
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
function progressiveHeaderLine(jobs: AsyncJobState[], theme: Theme, width: number): string {
|
|
1043
|
+
const counts = widgetHeaderCounts(jobs);
|
|
1044
|
+
const hasActive = counts.running.length > 0 || counts.queued.length > 0;
|
|
1045
|
+
const glyph = counts.running.length > 0 ? runningGlyph(widgetJobsRunningSeed(counts.running)) : hasActive ? "●" : "○";
|
|
1046
|
+
const parts: string[] = [];
|
|
1047
|
+
if (counts.running.length > 0) parts.push(formatAgentRunningLabel(counts.running.length));
|
|
1048
|
+
if (counts.queued.length > 0) parts.push(`${counts.queued.length} queued`);
|
|
1049
|
+
if (!hasActive) {
|
|
1050
|
+
if (counts.failed.length > 0) parts.push(`${counts.failed.length} failed`);
|
|
1051
|
+
if (counts.paused.length > 0) parts.push(`${counts.paused.length} paused`);
|
|
1052
|
+
if (counts.complete.length > 0) parts.push(`${counts.complete.length}/${jobs.length} done`);
|
|
1053
|
+
}
|
|
1054
|
+
return truncLine(`${theme.fg(hasActive ? "accent" : "dim", glyph)} ${theme.fg(hasActive ? "accent" : "dim", "Async agents")} ${theme.fg("dim", "·")} ${theme.fg("dim", parts.join(", ") || `${jobs.length} total`)}`, width);
|
|
1055
|
+
}
|
|
1056
|
+
|
|
1057
|
+
function progressiveJobLine(job: AsyncJobState, theme: Theme, width: number): string {
|
|
1058
|
+
const stats = widgetStats(job, theme);
|
|
1059
|
+
const activity = widgetActivity(job);
|
|
1060
|
+
const status = job.status === "complete" ? "done" : job.status;
|
|
1061
|
+
const parts = [
|
|
1062
|
+
themeBold(theme, widgetJobName(job)),
|
|
1063
|
+
theme.fg("dim", status),
|
|
1064
|
+
stats,
|
|
1065
|
+
activity && activity.toLowerCase() !== status ? theme.fg("dim", activity) : "",
|
|
1066
|
+
].filter(Boolean);
|
|
1067
|
+
return truncLine(` ${widgetStatusGlyph(job, theme)} ${parts.join(` ${theme.fg("dim", "·")} `)}`, width);
|
|
1068
|
+
}
|
|
1069
|
+
|
|
1070
|
+
function progressiveHiddenLine(hiddenJobs: AsyncJobState[], theme: Theme, width: number): string {
|
|
1071
|
+
const counts = widgetHeaderCounts(hiddenJobs);
|
|
1072
|
+
const parts: string[] = [];
|
|
1073
|
+
if (counts.running.length > 0) parts.push(`${counts.running.length} running`);
|
|
1074
|
+
if (counts.queued.length > 0) parts.push(`${counts.queued.length} queued`);
|
|
1075
|
+
const finished = counts.complete.length + counts.failed.length + counts.paused.length;
|
|
1076
|
+
if (finished > 0) parts.push(`${finished} finished`);
|
|
1077
|
+
return truncLine(theme.fg("dim", ` +${hiddenJobs.length} more${parts.length ? ` (${parts.join(", ")})` : ""}`), width);
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
function buildProgressiveWidgetLines(jobs: AsyncJobState[], theme: Theme, width: number, lockedRows: number, previousKeys: string[]): { lines: string[]; visibleJobKeys: string[] } {
|
|
1081
|
+
const rowCount = Math.max(1, lockedRows);
|
|
1082
|
+
if (rowCount === 1) return { lines: buildSingleLineWidgetLines(jobs, theme, width), visibleJobKeys: [] };
|
|
1083
|
+
|
|
1084
|
+
const bodyRows = rowCount - 1;
|
|
1085
|
+
let visibleJobKeys = selectProgressiveJobKeys(jobs, previousKeys, bodyRows);
|
|
1086
|
+
const jobsByKey = new Map(jobs.map((job) => [progressiveJobKey(job), job]));
|
|
1087
|
+
let visibleJobs = visibleJobKeys.map((key) => jobsByKey.get(key)).filter((job): job is AsyncJobState => Boolean(job));
|
|
1088
|
+
let hiddenJobs = jobs.filter((job) => !visibleJobKeys.includes(progressiveJobKey(job)));
|
|
1089
|
+
const needsHiddenLine = hiddenJobs.length > 0;
|
|
1090
|
+
|
|
1091
|
+
if (needsHiddenLine && visibleJobs.length >= bodyRows && bodyRows > 0) {
|
|
1092
|
+
visibleJobs = visibleJobs.slice(0, bodyRows - 1);
|
|
1093
|
+
visibleJobKeys = visibleJobs.map(progressiveJobKey);
|
|
1094
|
+
hiddenJobs = jobs.filter((job) => !visibleJobKeys.includes(progressiveJobKey(job)));
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1097
|
+
const lines = [
|
|
1098
|
+
progressiveHeaderLine(jobs, theme, width),
|
|
1099
|
+
...visibleJobs.map((job) => progressiveJobLine(job, theme, width)),
|
|
1100
|
+
];
|
|
1101
|
+
if (hiddenJobs.length > 0 && lines.length < rowCount) lines.push(progressiveHiddenLine(hiddenJobs, theme, width));
|
|
1102
|
+
while (lines.length < rowCount) lines.push(" ");
|
|
1103
|
+
return { lines: lines.slice(0, rowCount), visibleJobKeys };
|
|
1104
|
+
}
|
|
1105
|
+
|
|
1106
|
+
function collapsedWidgetLineBudget(rows: number): number {
|
|
1107
|
+
return Math.max(10, Math.min(14, Math.floor(rows * 0.35)));
|
|
1108
|
+
}
|
|
1109
|
+
|
|
902
1110
|
function fitWidgetLineBudget(lines: string[], theme: Theme, width: number, expanded: boolean): string[] {
|
|
903
1111
|
const rows = process.stdout.rows || 30;
|
|
904
1112
|
const budget = expanded
|
|
905
1113
|
? Math.max(12, Math.min(24, Math.floor(rows * 0.55)))
|
|
906
|
-
:
|
|
1114
|
+
: collapsedWidgetLineBudget(rows);
|
|
907
1115
|
if (lines.length <= budget) return lines;
|
|
908
1116
|
const visibleLines = Math.max(1, budget - 1);
|
|
909
1117
|
const hiddenCount = lines.length - visibleLines;
|
|
@@ -913,6 +1121,43 @@ function fitWidgetLineBudget(lines: string[], theme: Theme, width: number, expan
|
|
|
913
1121
|
return [...lines.slice(0, visibleLines), truncLine(theme.fg("dim", hint), width)];
|
|
914
1122
|
}
|
|
915
1123
|
|
|
1124
|
+
function fitAdaptiveWidgetLines(jobs: AsyncJobState[], lines: string[], theme: Theme, width: number, expanded: boolean): string[] {
|
|
1125
|
+
if (expanded) {
|
|
1126
|
+
resetWidgetLayoutSession();
|
|
1127
|
+
return fitWidgetLineBudget(lines, theme, width, true);
|
|
1128
|
+
}
|
|
1129
|
+
|
|
1130
|
+
const hasMatchingSession = widgetSessionMatches(expanded);
|
|
1131
|
+
const rows = currentTerminalRows();
|
|
1132
|
+
const columns = currentTerminalColumns();
|
|
1133
|
+
const availableRows = estimateAvailableWidgetRows();
|
|
1134
|
+
|
|
1135
|
+
if (hasMatchingSession && widgetLayoutSession?.tier === "single-line") {
|
|
1136
|
+
return buildSingleLineWidgetLines(jobs, theme, width);
|
|
1137
|
+
}
|
|
1138
|
+
|
|
1139
|
+
if (hasMatchingSession && widgetLayoutSession?.tier === "progressive" && widgetLayoutSession.lockedRows !== undefined) {
|
|
1140
|
+
const rendered = buildProgressiveWidgetLines(jobs, theme, width, widgetLayoutSession.lockedRows, widgetLayoutSession.visibleJobKeys);
|
|
1141
|
+
widgetLayoutSession.visibleJobKeys = rendered.visibleJobKeys;
|
|
1142
|
+
return rendered.lines;
|
|
1143
|
+
}
|
|
1144
|
+
|
|
1145
|
+
if (lines.length <= availableRows) {
|
|
1146
|
+
widgetLayoutSession = { expanded, rows, columns, tier: "full", visibleJobKeys: [] };
|
|
1147
|
+
return fitWidgetLineBudget(lines, theme, width, false);
|
|
1148
|
+
}
|
|
1149
|
+
|
|
1150
|
+
if (availableRows <= 2) {
|
|
1151
|
+
widgetLayoutSession = { expanded, rows, columns, tier: "single-line", visibleJobKeys: [] };
|
|
1152
|
+
return buildSingleLineWidgetLines(jobs, theme, width);
|
|
1153
|
+
}
|
|
1154
|
+
|
|
1155
|
+
const lockedRows = Math.min(availableRows, collapsedWidgetLineBudget(rows));
|
|
1156
|
+
const rendered = buildProgressiveWidgetLines(jobs, theme, width, lockedRows, []);
|
|
1157
|
+
widgetLayoutSession = { expanded, rows, columns, tier: "progressive", lockedRows, visibleJobKeys: rendered.visibleJobKeys };
|
|
1158
|
+
return rendered.lines;
|
|
1159
|
+
}
|
|
1160
|
+
|
|
916
1161
|
function buildWidgetComponent(jobs: AsyncJobState[], expanded: boolean): (_tui: unknown, theme: Theme) => Component {
|
|
917
1162
|
return (_tui, theme) => {
|
|
918
1163
|
const width = getTermWidth();
|
|
@@ -922,7 +1167,7 @@ function buildWidgetComponent(jobs: AsyncJobState[], expanded: boolean): (_tui:
|
|
|
922
1167
|
? compactSingleWidgetLines(jobs[0]!, theme, width)
|
|
923
1168
|
: buildWidgetLines(jobs, theme, width, false);
|
|
924
1169
|
const container = new Container();
|
|
925
|
-
for (const line of
|
|
1170
|
+
for (const line of fitAdaptiveWidgetLines(jobs, lines, theme, width, expanded)) container.addChild(new Text(line, 1, 0));
|
|
926
1171
|
return container;
|
|
927
1172
|
};
|
|
928
1173
|
}
|
|
@@ -1002,6 +1247,7 @@ export function buildWidgetLines(jobs: AsyncJobState[], theme: Theme, width = ge
|
|
|
1002
1247
|
*/
|
|
1003
1248
|
export function renderWidget(ctx: ExtensionContext, jobs: AsyncJobState[]): void {
|
|
1004
1249
|
if (jobs.length === 0) {
|
|
1250
|
+
resetWidgetLayoutSession();
|
|
1005
1251
|
if (ctx.hasUI) ctx.ui.setWidget(WIDGET_KEY, undefined);
|
|
1006
1252
|
return;
|
|
1007
1253
|
}
|
|
@@ -1009,7 +1255,7 @@ export function renderWidget(ctx: ExtensionContext, jobs: AsyncJobState[]): void
|
|
|
1009
1255
|
ctx.ui.setWidget(WIDGET_KEY, buildWidgetComponent(jobs, ctx.ui.getToolsExpanded?.() ?? false));
|
|
1010
1256
|
}
|
|
1011
1257
|
|
|
1012
|
-
function renderSingleCompact(d: Details, r: Details["results"][number], theme: Theme): Component {
|
|
1258
|
+
function renderSingleCompact(d: Details, r: Details["results"][number], theme: Theme, frame?: number): Component {
|
|
1013
1259
|
const output = r.truncation?.text || getSingleResultOutput(r);
|
|
1014
1260
|
const progress = r.progress || r.progressSummary;
|
|
1015
1261
|
const isRunning = r.progress?.status === "running";
|
|
@@ -1021,7 +1267,7 @@ function renderSingleCompact(d: Details, r: Details["results"][number], theme: T
|
|
|
1021
1267
|
const c = new Container();
|
|
1022
1268
|
const width = getTermWidth() - 4;
|
|
1023
1269
|
const modelDisplay = modelThinkingBadge(theme, r.model);
|
|
1024
|
-
c.addChild(new Text(truncLine(`${resultGlyph(r, output, theme, isRunning)} ${theme.fg("toolTitle", theme.bold(r.agent))}${modelDisplay}${contextBadge}${stats ? ` ${theme.fg("dim", "·")} ${stats}` : ""}`, width), 0, 0));
|
|
1270
|
+
c.addChild(new Text(truncLine(`${resultGlyph(r, output, theme, isRunning, undefined, frame)} ${theme.fg("toolTitle", theme.bold(r.agent))}${modelDisplay}${contextBadge}${stats ? ` ${theme.fg("dim", "·")} ${stats}` : ""}`, width), 0, 0));
|
|
1025
1271
|
|
|
1026
1272
|
if (isRunning && r.progress) {
|
|
1027
1273
|
const progressSnapshotNow = snapshotNowForProgress(r.progress);
|
|
@@ -1045,7 +1291,7 @@ function renderSingleCompact(d: Details, r: Details["results"][number], theme: T
|
|
|
1045
1291
|
return c;
|
|
1046
1292
|
}
|
|
1047
1293
|
|
|
1048
|
-
function renderMultiCompact(d: Details, theme: Theme): Component {
|
|
1294
|
+
function renderMultiCompact(d: Details, theme: Theme, frame?: number): Component {
|
|
1049
1295
|
const hasRunning = d.progress?.some((p) => p.status === "running")
|
|
1050
1296
|
|| d.results.some((r) => r.progress?.status === "running")
|
|
1051
1297
|
|| workflowGraphHasStatus(d, ["running"]);
|
|
@@ -1071,7 +1317,7 @@ function renderMultiCompact(d: Details, theme: Theme): Component {
|
|
|
1071
1317
|
const itemTitle = multiLabel.itemTitle;
|
|
1072
1318
|
const stats = statJoin(theme, [multiLabel.headerLabel, formatProgressStats(theme, totalSummary)]);
|
|
1073
1319
|
const glyph = hasRunning
|
|
1074
|
-
? theme.fg("accent", runningGlyph(runningSeed(progressRunningSeed(totalSummary), d.currentStepIndex)))
|
|
1320
|
+
? theme.fg("accent", runningGlyph(frame !== undefined ? (runningSeed(progressRunningSeed(totalSummary), d.currentStepIndex) ?? 0) + frame : runningSeed(progressRunningSeed(totalSummary), d.currentStepIndex)))
|
|
1075
1321
|
: failed
|
|
1076
1322
|
? theme.fg("error", "✗")
|
|
1077
1323
|
: paused
|
|
@@ -1117,7 +1363,7 @@ function renderMultiCompact(d: Details, theme: Theme): Component {
|
|
|
1117
1363
|
const rPending = rProg && "status" in rProg && rProg.status === "pending";
|
|
1118
1364
|
const stepNumber = r.progress?.index !== undefined ? r.progress.index + 1 : progressFromArray?.index !== undefined ? progressFromArray.index + 1 : i + 1;
|
|
1119
1365
|
const stepStats = formatProgressStats(theme, rProg);
|
|
1120
|
-
const glyph = rPending ? theme.fg("dim", "◦") : resultGlyph(r, output, theme, rRunning, progressRunningSeed(rProg));
|
|
1366
|
+
const glyph = rPending ? theme.fg("dim", "◦") : resultGlyph(r, output, theme, rRunning, progressRunningSeed(rProg), frame);
|
|
1121
1367
|
const pendingLabel = rPending ? ` ${theme.fg("dim", "· pending")}` : "";
|
|
1122
1368
|
const stepLabel = resultRowLabel(d, multiLabel, i, stepNumber);
|
|
1123
1369
|
const line = `${glyph} ${stepLabel}: ${themeBold(theme, agentName)}${stepStats ? ` ${theme.fg("dim", "·")} ${stepStats}` : ""}${pendingLabel}`;
|
|
@@ -1144,13 +1390,19 @@ export function renderSubagentResult(
|
|
|
1144
1390
|
result: AgentToolResult<Details>,
|
|
1145
1391
|
options: { expanded: boolean },
|
|
1146
1392
|
theme: Theme,
|
|
1393
|
+
frame?: number,
|
|
1147
1394
|
): Component {
|
|
1148
1395
|
const d = result.details;
|
|
1149
1396
|
if (!d || !d.results.length) {
|
|
1150
1397
|
const t = result.content[0];
|
|
1151
1398
|
const text = t?.type === "text" ? t.text : "(no output)";
|
|
1152
1399
|
const contextPrefix = d?.context === "fork" ? `${theme.fg("warning", "[fork]")} ` : "";
|
|
1153
|
-
|
|
1400
|
+
const width = getTermWidth() - 4;
|
|
1401
|
+
if (!text.includes("\n")) return new Text(truncLine(`${contextPrefix}${text}`, width), 0, 0);
|
|
1402
|
+
const c = new Container();
|
|
1403
|
+
const wrapped = wrapPlainText(`${contextPrefix}${text}`, width);
|
|
1404
|
+
for (const line of wrapped) c.addChild(new Text(line, 0, 0));
|
|
1405
|
+
return c;
|
|
1154
1406
|
}
|
|
1155
1407
|
|
|
1156
1408
|
const expanded = options.expanded;
|
|
@@ -1158,7 +1410,7 @@ export function renderSubagentResult(
|
|
|
1158
1410
|
|
|
1159
1411
|
if (d.mode === "single" && d.results.length === 1) {
|
|
1160
1412
|
const r = d.results[0];
|
|
1161
|
-
if (!expanded) return renderSingleCompact(d, r, theme);
|
|
1413
|
+
if (!expanded) return renderSingleCompact(d, r, theme, frame);
|
|
1162
1414
|
const isRunning = r.progress?.status === "running";
|
|
1163
1415
|
const icon = isRunning
|
|
1164
1416
|
? theme.fg("warning", "running")
|
|
@@ -1252,7 +1504,7 @@ export function renderSubagentResult(
|
|
|
1252
1504
|
return c;
|
|
1253
1505
|
}
|
|
1254
1506
|
|
|
1255
|
-
if (!expanded) return renderMultiCompact(d, theme);
|
|
1507
|
+
if (!expanded) return renderMultiCompact(d, theme, frame);
|
|
1256
1508
|
|
|
1257
1509
|
const hasRunning = d.progress?.some((p) => p.status === "running")
|
|
1258
1510
|
|| d.results.some((r) => r.progress?.status === "running")
|