pi-prompt-template-model 0.6.6 → 0.6.7
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 +10 -0
- package/index.ts +5 -2
- package/package.json +1 -1
- package/subagent-runtime.ts +13 -10
- package/subagent-step.ts +99 -10
- package/subagent-widget.ts +78 -26
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.6.7] - 2026-03-28
|
|
4
|
+
|
|
5
|
+
### Changed
|
|
6
|
+
- Delegation progress widget now shows a unified tool stream where completed tools scroll chronologically with the active tool highlighted at the bottom, replacing the old design where the active tool flashed at the top and disappeared on completion.
|
|
7
|
+
- Delegation progress widget now renders the subagent's model name in the header (e.g., `delegate [fork] gpt-5.3-codex | 14 tools, 170k tok, 2m47s`).
|
|
8
|
+
- Delegation progress widget no longer caps tool history or output lines. The box grows to show the full execution trace.
|
|
9
|
+
- Delegation progress widget now refreshes every second during idle periods (model thinking) so the elapsed timer ticks smoothly instead of freezing between progress updates.
|
|
10
|
+
- Enriched the delegation bridge protocol to pass through full `recentOutputLines`, `recentTools` history, and `model` from pi-subagents progress data, replacing the old single-line `recentOutput` and missing tool/model fields.
|
|
11
|
+
- Removed `lastTool`/`lastToolArgs` tracking from live state (dead code after the unified tool stream redesign).
|
|
12
|
+
|
|
3
13
|
## [0.6.6] - 2026-03-28
|
|
4
14
|
|
|
5
15
|
### Added
|
package/index.ts
CHANGED
|
@@ -132,9 +132,12 @@ export default function promptModelExtension(pi: ExtensionAPI) {
|
|
|
132
132
|
|
|
133
133
|
for (const command of pi.getCommands()) {
|
|
134
134
|
if (command.source !== "skill") continue;
|
|
135
|
-
|
|
135
|
+
const sourceInfo = "sourceInfo" in command
|
|
136
|
+
? (command as { sourceInfo?: { path?: string } }).sourceInfo
|
|
137
|
+
: undefined;
|
|
138
|
+
if (!sourceInfo?.path) continue;
|
|
136
139
|
if (!candidates.has(command.name)) continue;
|
|
137
|
-
return
|
|
140
|
+
return sourceInfo.path;
|
|
138
141
|
}
|
|
139
142
|
|
|
140
143
|
return undefined;
|
package/package.json
CHANGED
package/subagent-runtime.ts
CHANGED
|
@@ -52,6 +52,9 @@ export interface DelegatedSubagentUpdate {
|
|
|
52
52
|
currentTool?: string;
|
|
53
53
|
currentToolArgs?: string;
|
|
54
54
|
recentOutput?: string;
|
|
55
|
+
recentOutputLines?: string[];
|
|
56
|
+
recentTools?: Array<{ tool: string; args: string }>;
|
|
57
|
+
model?: string;
|
|
55
58
|
toolCount?: number;
|
|
56
59
|
durationMs?: number;
|
|
57
60
|
tokens?: number;
|
|
@@ -65,6 +68,9 @@ export interface DelegatedSubagentTaskProgress {
|
|
|
65
68
|
currentTool?: string;
|
|
66
69
|
currentToolArgs?: string;
|
|
67
70
|
recentOutput?: string;
|
|
71
|
+
recentOutputLines?: string[];
|
|
72
|
+
recentTools?: Array<{ tool: string; args: string }>;
|
|
73
|
+
model?: string;
|
|
68
74
|
toolCount?: number;
|
|
69
75
|
durationMs?: number;
|
|
70
76
|
tokens?: number;
|
|
@@ -74,9 +80,9 @@ export interface DelegatedSubagentLiveState {
|
|
|
74
80
|
status?: string;
|
|
75
81
|
currentTool?: string;
|
|
76
82
|
currentToolArgs?: string;
|
|
77
|
-
lastTool?: string;
|
|
78
|
-
lastToolArgs?: string;
|
|
79
83
|
recentOutput: string[];
|
|
84
|
+
recentTools: Array<{ tool: string; args: string }>;
|
|
85
|
+
model?: string;
|
|
80
86
|
toolCount: number;
|
|
81
87
|
durationMs: number;
|
|
82
88
|
tokens: number;
|
|
@@ -151,6 +157,7 @@ export function updateDelegatedLiveState(requestId: string, update: Partial<Dele
|
|
|
151
157
|
const now = Date.now();
|
|
152
158
|
const existing = delegatedLiveState.get(requestId) ?? {
|
|
153
159
|
recentOutput: [],
|
|
160
|
+
recentTools: [],
|
|
154
161
|
toolCount: 0,
|
|
155
162
|
durationMs: 0,
|
|
156
163
|
tokens: 0,
|
|
@@ -158,21 +165,16 @@ export function updateDelegatedLiveState(requestId: string, update: Partial<Dele
|
|
|
158
165
|
startedAt: now,
|
|
159
166
|
updatedAt: now,
|
|
160
167
|
};
|
|
161
|
-
// When a tool finishes (currentTool goes undefined), preserve it as lastTool
|
|
162
|
-
const toolJustCleared = update.currentTool === undefined && existing.currentTool !== undefined;
|
|
163
|
-
const lastTool = toolJustCleared ? existing.currentTool : (update.currentTool ?? existing.lastTool);
|
|
164
|
-
const lastToolArgs = toolJustCleared ? existing.currentToolArgs : (update.currentToolArgs ?? existing.lastToolArgs);
|
|
165
|
-
|
|
166
168
|
const next: DelegatedSubagentLiveState = {
|
|
167
169
|
...existing,
|
|
168
170
|
...update,
|
|
169
171
|
recentOutput: update.recentOutput ?? existing.recentOutput,
|
|
172
|
+
recentTools: update.recentTools ?? existing.recentTools,
|
|
173
|
+
model: update.model ?? existing.model,
|
|
170
174
|
toolCount: update.toolCount ?? existing.toolCount,
|
|
171
175
|
durationMs: update.durationMs ?? (now - existing.startedAt),
|
|
172
176
|
tokens: update.tokens ?? existing.tokens,
|
|
173
177
|
taskProgress: update.taskProgress ?? existing.taskProgress,
|
|
174
|
-
lastTool,
|
|
175
|
-
lastToolArgs,
|
|
176
178
|
startedAt: existing.startedAt,
|
|
177
179
|
updatedAt: now,
|
|
178
180
|
};
|
|
@@ -184,6 +186,7 @@ export function appendDelegatedLiveOutput(requestId: string, line?: string): voi
|
|
|
184
186
|
const fallbackNow = Date.now();
|
|
185
187
|
const existing = delegatedLiveState.get(requestId) ?? {
|
|
186
188
|
recentOutput: [],
|
|
189
|
+
recentTools: [],
|
|
187
190
|
toolCount: 0,
|
|
188
191
|
durationMs: 0,
|
|
189
192
|
tokens: 0,
|
|
@@ -191,7 +194,7 @@ export function appendDelegatedLiveOutput(requestId: string, line?: string): voi
|
|
|
191
194
|
startedAt: fallbackNow,
|
|
192
195
|
updatedAt: fallbackNow,
|
|
193
196
|
};
|
|
194
|
-
const recentOutput = [...existing.recentOutput, line]
|
|
197
|
+
const recentOutput = [...existing.recentOutput, line];
|
|
195
198
|
delegatedLiveState.set(requestId, {
|
|
196
199
|
...existing,
|
|
197
200
|
recentOutput,
|
package/subagent-step.ts
CHANGED
|
@@ -204,6 +204,9 @@ function formatProgressStatus(update: DelegatedSubagentUpdate): string | undefin
|
|
|
204
204
|
if (update.currentTool) {
|
|
205
205
|
return `running ${update.currentTool}${update.currentToolArgs ? ` ${update.currentToolArgs}` : ""}`;
|
|
206
206
|
}
|
|
207
|
+
if (update.taskProgress?.some((task) => task.status === "running")) {
|
|
208
|
+
return "running";
|
|
209
|
+
}
|
|
207
210
|
if (update.toolCount && update.toolCount > 0) {
|
|
208
211
|
return `completed ${update.toolCount} tool${update.toolCount === 1 ? "" : "s"}`;
|
|
209
212
|
}
|
|
@@ -216,6 +219,38 @@ function formatParallelProgressStatus(update: DelegatedSubagentUpdate): string |
|
|
|
216
219
|
return `parallel ${completed}/${update.taskProgress.length} running`;
|
|
217
220
|
}
|
|
218
221
|
|
|
222
|
+
function hasOwn<T extends object>(value: T, key: PropertyKey): boolean {
|
|
223
|
+
return Object.prototype.hasOwnProperty.call(value, key);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function sanitizeOutputLines(lines: string[] | undefined): string[] {
|
|
227
|
+
if (!lines || lines.length === 0) return [];
|
|
228
|
+
return lines.filter((line): line is string => typeof line === "string" && line.trim() && line.trim() !== "(running...)");
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function collectNewOutputLines(previous: string[] | undefined, next: string[] | undefined): string[] {
|
|
232
|
+
const previousLines = sanitizeOutputLines(previous);
|
|
233
|
+
const nextLines = sanitizeOutputLines(next);
|
|
234
|
+
if (nextLines.length === 0) return [];
|
|
235
|
+
if (previousLines.length === 0) return nextLines;
|
|
236
|
+
|
|
237
|
+
const overlapLimit = Math.min(previousLines.length, nextLines.length);
|
|
238
|
+
for (let overlap = overlapLimit; overlap > 0; overlap--) {
|
|
239
|
+
let matches = true;
|
|
240
|
+
for (let index = 0; index < overlap; index++) {
|
|
241
|
+
if (previousLines[previousLines.length - overlap + index] !== nextLines[index]) {
|
|
242
|
+
matches = false;
|
|
243
|
+
break;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
if (matches) {
|
|
247
|
+
return nextLines.slice(overlap);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
return nextLines;
|
|
252
|
+
}
|
|
253
|
+
|
|
219
254
|
function mergeTaskProgress(
|
|
220
255
|
requestTasks: DelegatedSubagentTask[] | undefined,
|
|
221
256
|
existingProgress: DelegatedSubagentTaskProgress[] | undefined,
|
|
@@ -235,6 +270,9 @@ function mergeTaskProgress(
|
|
|
235
270
|
currentTool: existing?.currentTool,
|
|
236
271
|
currentToolArgs: existing?.currentToolArgs,
|
|
237
272
|
recentOutput: existing?.recentOutput,
|
|
273
|
+
recentOutputLines: existing?.recentOutputLines,
|
|
274
|
+
recentTools: existing?.recentTools,
|
|
275
|
+
model: existing?.model ?? task.model,
|
|
238
276
|
toolCount: existing?.toolCount,
|
|
239
277
|
durationMs: existing?.durationMs,
|
|
240
278
|
tokens: existing?.tokens,
|
|
@@ -255,11 +293,20 @@ function mergeTaskProgress(
|
|
|
255
293
|
}
|
|
256
294
|
if (targetIndex < 0) continue;
|
|
257
295
|
consumed.add(targetIndex);
|
|
296
|
+
const current = merged[targetIndex]!;
|
|
258
297
|
merged[targetIndex] = {
|
|
259
|
-
...merged[targetIndex],
|
|
260
|
-
...entry,
|
|
261
298
|
index: targetIndex,
|
|
262
|
-
agent:
|
|
299
|
+
agent: current.agent,
|
|
300
|
+
status: entry.status ?? current.status,
|
|
301
|
+
currentTool: hasOwn(entry, "currentTool") ? entry.currentTool : current.currentTool,
|
|
302
|
+
currentToolArgs: hasOwn(entry, "currentToolArgs") ? entry.currentToolArgs : current.currentToolArgs,
|
|
303
|
+
recentOutput: entry.recentOutput ?? current.recentOutput,
|
|
304
|
+
recentOutputLines: entry.recentOutputLines ?? current.recentOutputLines,
|
|
305
|
+
recentTools: entry.recentTools ?? current.recentTools,
|
|
306
|
+
model: entry.model ?? current.model,
|
|
307
|
+
toolCount: entry.toolCount ?? current.toolCount,
|
|
308
|
+
durationMs: entry.durationMs ?? current.durationMs,
|
|
309
|
+
tokens: entry.tokens ?? current.tokens,
|
|
263
310
|
};
|
|
264
311
|
}
|
|
265
312
|
|
|
@@ -316,18 +363,29 @@ async function requestDelegatedRun(
|
|
|
316
363
|
|
|
317
364
|
let lastProgressStatus = "";
|
|
318
365
|
let widgetSet = false;
|
|
366
|
+
let refreshTimer: ReturnType<typeof setInterval> | null = null;
|
|
319
367
|
|
|
320
368
|
const showWidget = () => {
|
|
321
369
|
if (!ctx.hasUI || widgetSet) return;
|
|
322
370
|
widgetSet = true;
|
|
323
371
|
ctx.ui.setWidget(
|
|
324
372
|
DELEGATED_WIDGET_KEY,
|
|
325
|
-
(_tui, theme) => createDelegatedProgressWidget(request.requestId, request.agent, request.context, request.task, request.tasks, theme),
|
|
373
|
+
(_tui, theme) => createDelegatedProgressWidget(request.requestId, request.agent, request.context, request.task, request.tasks, theme, request.model),
|
|
326
374
|
{ placement: "aboveEditor" },
|
|
327
375
|
);
|
|
376
|
+
// Force TUI repaints every second so the elapsed timer ticks during idle periods
|
|
377
|
+
refreshTimer = setInterval(() => {
|
|
378
|
+
if (done) return;
|
|
379
|
+
const statusLine = lastProgressStatus || "running...";
|
|
380
|
+
ctx.ui.setStatus("prompt-subagent", `delegating to ${requestLabel} · ${statusLine}`);
|
|
381
|
+
}, 1000);
|
|
328
382
|
};
|
|
329
383
|
|
|
330
384
|
const clearWidget = () => {
|
|
385
|
+
if (refreshTimer) {
|
|
386
|
+
clearInterval(refreshTimer);
|
|
387
|
+
refreshTimer = null;
|
|
388
|
+
}
|
|
331
389
|
if (ctx.hasUI && widgetSet) {
|
|
332
390
|
ctx.ui.setWidget(DELEGATED_WIDGET_KEY, undefined);
|
|
333
391
|
widgetSet = false;
|
|
@@ -338,9 +396,11 @@ async function requestDelegatedRun(
|
|
|
338
396
|
if (done || !data || typeof data !== "object") return;
|
|
339
397
|
const update = data as DelegatedSubagentUpdate;
|
|
340
398
|
if (update.requestId !== request.requestId) return;
|
|
399
|
+
|
|
400
|
+
const previousTaskProgress = getDelegatedLiveState(request.requestId)?.taskProgress;
|
|
341
401
|
const mergedTaskProgress = mergeTaskProgress(
|
|
342
402
|
request.tasks,
|
|
343
|
-
|
|
403
|
+
previousTaskProgress,
|
|
344
404
|
update.taskProgress,
|
|
345
405
|
);
|
|
346
406
|
const isParallel = (request.tasks?.length ?? 0) > 0;
|
|
@@ -353,18 +413,46 @@ async function requestDelegatedRun(
|
|
|
353
413
|
if (progressStatus) {
|
|
354
414
|
lastProgressStatus = progressStatus;
|
|
355
415
|
}
|
|
416
|
+
|
|
356
417
|
updateDelegatedLiveState(request.requestId, {
|
|
357
418
|
status: progressStatus ?? (lastProgressStatus || "running..."),
|
|
358
419
|
currentTool: update.currentTool,
|
|
359
420
|
currentToolArgs: update.currentToolArgs,
|
|
421
|
+
recentTools: update.recentTools,
|
|
422
|
+
model: update.model,
|
|
360
423
|
toolCount: update.toolCount,
|
|
361
424
|
durationMs: update.durationMs,
|
|
362
425
|
tokens: update.tokens,
|
|
363
426
|
taskProgress: mergedTaskProgress,
|
|
364
427
|
});
|
|
365
|
-
|
|
366
|
-
if (
|
|
428
|
+
|
|
429
|
+
if (!isParallel) {
|
|
430
|
+
if (update.recentOutputLines && update.recentOutputLines.length > 0) {
|
|
431
|
+
updateDelegatedLiveState(request.requestId, {
|
|
432
|
+
recentOutput: sanitizeOutputLines(update.recentOutputLines),
|
|
433
|
+
});
|
|
434
|
+
} else {
|
|
435
|
+
appendDelegatedLiveOutput(request.requestId, update.recentOutput);
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
if (isParallel && mergedTaskProgress) {
|
|
367
440
|
for (const task of mergedTaskProgress) {
|
|
441
|
+
const previousTask =
|
|
442
|
+
previousTaskProgress?.find((entry) => entry.index === task.index) ??
|
|
443
|
+
previousTaskProgress?.find((entry) => entry.agent === task.agent);
|
|
444
|
+
|
|
445
|
+
const newOutputLines = collectNewOutputLines(previousTask?.recentOutputLines, task.recentOutputLines);
|
|
446
|
+
if (newOutputLines.length > 0) {
|
|
447
|
+
for (const line of newOutputLines) {
|
|
448
|
+
appendDelegatedLiveOutput(request.requestId, line);
|
|
449
|
+
}
|
|
450
|
+
continue;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
if (!task.recentOutput || task.recentOutput === previousTask?.recentOutput) {
|
|
454
|
+
continue;
|
|
455
|
+
}
|
|
368
456
|
appendDelegatedLiveOutput(request.requestId, task.recentOutput);
|
|
369
457
|
}
|
|
370
458
|
}
|
|
@@ -567,11 +655,12 @@ export async function executeSubagentPromptStep(options: DelegatedPromptOptions)
|
|
|
567
655
|
agent: preparedTasks[0]!.agent,
|
|
568
656
|
};
|
|
569
657
|
} catch (error) {
|
|
570
|
-
const
|
|
658
|
+
const cause = error instanceof Error ? error : new Error(String(error));
|
|
659
|
+
const responseText = cause.message;
|
|
571
660
|
if (isParallelRequest) {
|
|
572
|
-
throw new Error(`Parallel delegated prompts (${promptLabel}) failed: ${responseText}
|
|
661
|
+
throw new Error(`Parallel delegated prompts (${promptLabel}) failed: ${responseText}`, { cause });
|
|
573
662
|
}
|
|
574
|
-
throw new Error(`Prompt \`${preparedTasks[0]!.promptName}\` delegated subagent \`${preparedTasks[0]!.agent}\` failed: ${responseText}
|
|
663
|
+
throw new Error(`Prompt \`${preparedTasks[0]!.promptName}\` delegated subagent \`${preparedTasks[0]!.agent}\` failed: ${responseText}`, { cause });
|
|
575
664
|
} finally {
|
|
576
665
|
clearDelegatedLiveState(request.requestId);
|
|
577
666
|
if (ctx.hasUI) {
|
package/subagent-widget.ts
CHANGED
|
@@ -18,6 +18,28 @@ function formatTokens(n: number | undefined): string {
|
|
|
18
18
|
return String(n);
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
+
function normalizeModelLabel(model: string | undefined): string | undefined {
|
|
22
|
+
if (!model) return undefined;
|
|
23
|
+
return model.includes("/") ? model.split("/").pop() : model;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function formatToolCall(tool: string, args: string): string {
|
|
27
|
+
const safeArgs = args ?? "";
|
|
28
|
+
switch (tool) {
|
|
29
|
+
case "bash": {
|
|
30
|
+
const cmd = safeArgs.replace(/[\n\t]/g, " ").trim();
|
|
31
|
+
return `$ ${cmd.length > 80 ? cmd.slice(0, 80) + "..." : cmd}`;
|
|
32
|
+
}
|
|
33
|
+
case "read": return `[read: ${safeArgs}]`;
|
|
34
|
+
case "write": return `[write: ${safeArgs}]`;
|
|
35
|
+
case "edit": return `[edit: ${safeArgs}]`;
|
|
36
|
+
default: {
|
|
37
|
+
const short = safeArgs.length > 60 ? safeArgs.slice(0, 60) + "..." : safeArgs;
|
|
38
|
+
return `[${tool}: ${short}]`;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
21
43
|
export function createDelegatedProgressWidget(
|
|
22
44
|
requestId: string,
|
|
23
45
|
agent: string,
|
|
@@ -25,11 +47,18 @@ export function createDelegatedProgressWidget(
|
|
|
25
47
|
task: string,
|
|
26
48
|
tasks: DelegatedSubagentTask[] | undefined,
|
|
27
49
|
theme: Theme,
|
|
50
|
+
model?: string,
|
|
28
51
|
): Container & { dispose?(): void } {
|
|
29
52
|
const contextSuffix = context === "fork" ? theme.fg("warning", " [fork]") : "";
|
|
30
|
-
const taskPreview = task.length >
|
|
53
|
+
const taskPreview = task.length > 200 ? `${task.slice(0, 200)}...` : task;
|
|
31
54
|
const parallelTasks = tasks ?? [];
|
|
32
55
|
const isParallel = parallelTasks.length > 0;
|
|
56
|
+
const parallelModels = [...new Set(parallelTasks
|
|
57
|
+
.map((task) => normalizeModelLabel(task.model))
|
|
58
|
+
.filter((entry): entry is string => !!entry))];
|
|
59
|
+
const requestModel = isParallel
|
|
60
|
+
? (parallelModels.length === 1 ? parallelModels[0] : undefined)
|
|
61
|
+
: normalizeModelLabel(model);
|
|
33
62
|
|
|
34
63
|
const container = new Container();
|
|
35
64
|
container.addChild(new Spacer(1));
|
|
@@ -44,7 +73,7 @@ export function createDelegatedProgressWidget(
|
|
|
44
73
|
const key = stateKey(state, elapsed);
|
|
45
74
|
if (key !== lastKey) {
|
|
46
75
|
lastKey = key;
|
|
47
|
-
rebuildBox(box, agent, contextSuffix, taskPreview, parallelTasks, isParallel, state, elapsed, theme);
|
|
76
|
+
rebuildBox(box, agent, contextSuffix, taskPreview, parallelTasks, isParallel, state, elapsed, theme, requestModel);
|
|
48
77
|
}
|
|
49
78
|
return Container.prototype.render.call(container, width);
|
|
50
79
|
};
|
|
@@ -55,11 +84,16 @@ export function createDelegatedProgressWidget(
|
|
|
55
84
|
function stateKey(state: DelegatedSubagentLiveState | undefined, elapsed: number): string {
|
|
56
85
|
if (!state) return "none";
|
|
57
86
|
const elapsedBucket = Math.floor(elapsed / 1000);
|
|
58
|
-
const tool = state.currentTool ??
|
|
87
|
+
const tool = state.currentTool ?? "";
|
|
88
|
+
const outputLen = state.recentOutput.length;
|
|
89
|
+
const outputTail = state.recentOutput.length > 0
|
|
90
|
+
? state.recentOutput[state.recentOutput.length - 1]?.slice(0, 80) ?? ""
|
|
91
|
+
: "";
|
|
92
|
+
const toolsLen = state.recentTools.length;
|
|
59
93
|
const taskProgressKey = state.taskProgress
|
|
60
94
|
.map((entry) => `${entry.index ?? ""}:${entry.agent}:${entry.status ?? ""}:${entry.currentTool ?? ""}:${entry.toolCount ?? 0}`)
|
|
61
95
|
.join("|");
|
|
62
|
-
return `${state.status}|${tool}|${state.toolCount}|${state.tokens}|${state.
|
|
96
|
+
return `${state.status}|${tool}|${state.toolCount}|${state.tokens}|${outputLen}:${outputTail}|${toolsLen}|${state.model ?? ""}|${taskProgressKey}|${elapsedBucket}`;
|
|
63
97
|
}
|
|
64
98
|
|
|
65
99
|
function rebuildBox(
|
|
@@ -72,34 +106,49 @@ function rebuildBox(
|
|
|
72
106
|
state: DelegatedSubagentLiveState | undefined,
|
|
73
107
|
elapsed: number,
|
|
74
108
|
theme: Theme,
|
|
109
|
+
requestModel?: string,
|
|
75
110
|
): void {
|
|
76
111
|
box.clear();
|
|
77
112
|
|
|
78
|
-
const
|
|
79
|
-
const
|
|
113
|
+
const taskProgress = state?.taskProgress ?? [];
|
|
114
|
+
const baseToolCount = state?.toolCount ?? 0;
|
|
115
|
+
const baseTokens = state?.tokens ?? 0;
|
|
116
|
+
const parallelToolCount = taskProgress.reduce((sum, entry) => sum + (entry.toolCount ?? 0), 0);
|
|
117
|
+
const parallelTokens = taskProgress.reduce((sum, entry) => sum + (entry.tokens ?? 0), 0);
|
|
118
|
+
const toolCount = isParallel && parallelToolCount > 0 ? parallelToolCount : baseToolCount;
|
|
119
|
+
const tokens = isParallel && parallelTokens > 0 ? parallelTokens : baseTokens;
|
|
120
|
+
const tokensLabel = formatTokens(tokens);
|
|
80
121
|
const duration = formatDuration(elapsed);
|
|
81
|
-
const isThinking = toolCount === 0 &&
|
|
122
|
+
const isThinking = toolCount === 0 && tokens === 0;
|
|
82
123
|
const icon = theme.fg("warning", "...");
|
|
124
|
+
const modelLabel = isParallel
|
|
125
|
+
? requestModel
|
|
126
|
+
: normalizeModelLabel(state?.model ?? requestModel);
|
|
127
|
+
const modelSuffix = modelLabel ? ` ${theme.fg("dim", modelLabel)}` : "";
|
|
83
128
|
const stats = isThinking
|
|
84
129
|
? `thinking, ${duration}`
|
|
85
|
-
: `${toolCount} tool${toolCount === 1 ? "" : "s"}, ${
|
|
86
|
-
const taskProgress = state?.taskProgress ?? [];
|
|
130
|
+
: `${toolCount} tool${toolCount === 1 ? "" : "s"}, ${tokensLabel} tok, ${duration}`;
|
|
87
131
|
|
|
132
|
+
// Header
|
|
88
133
|
if (isParallel) {
|
|
89
134
|
const completedCount = taskProgress.filter((entry) => entry.status === "completed").length;
|
|
90
135
|
const runningLabel = `parallel ${completedCount}/${parallelTasks.length} running`;
|
|
91
|
-
box.addChild(new Text(`${icon} ${theme.fg("toolTitle", theme.bold(runningLabel))}${contextSuffix} | ${stats}`, 0, 0));
|
|
136
|
+
box.addChild(new Text(`${icon} ${theme.fg("toolTitle", theme.bold(runningLabel))}${contextSuffix}${modelSuffix} | ${stats}`, 0, 0));
|
|
92
137
|
} else {
|
|
93
138
|
box.addChild(new Text(
|
|
94
|
-
`${icon} ${theme.fg("toolTitle", theme.bold(agent))}${contextSuffix} | ${stats}`,
|
|
139
|
+
`${icon} ${theme.fg("toolTitle", theme.bold(agent))}${contextSuffix}${modelSuffix} | ${stats}`,
|
|
95
140
|
0, 0,
|
|
96
141
|
));
|
|
97
142
|
}
|
|
98
143
|
box.addChild(new Spacer(1));
|
|
144
|
+
|
|
145
|
+
// Task preview
|
|
99
146
|
if (!isParallel) {
|
|
100
147
|
box.addChild(new Text(theme.fg("dim", `Task: ${taskPreview}`), 0, 0));
|
|
148
|
+
box.addChild(new Spacer(1));
|
|
101
149
|
}
|
|
102
150
|
|
|
151
|
+
// Parallel task list
|
|
103
152
|
if (isParallel) {
|
|
104
153
|
for (let index = 0; index < parallelTasks.length; index++) {
|
|
105
154
|
const task = parallelTasks[index]!;
|
|
@@ -111,35 +160,38 @@ function rebuildBox(
|
|
|
111
160
|
if (taskStatus === "running") {
|
|
112
161
|
const runningTool = progress.currentTool ? ` ${progress.currentTool}...` : "";
|
|
113
162
|
box.addChild(new Text(theme.fg("dim", ` ${task.agent}: running${runningTool}`), 0, 0));
|
|
114
|
-
|
|
115
|
-
}
|
|
116
|
-
if (taskStatus === "completed") {
|
|
163
|
+
} else if (taskStatus === "completed") {
|
|
117
164
|
const toolSuffix =
|
|
118
165
|
progress?.toolCount !== undefined
|
|
119
166
|
? ` (${progress.toolCount} tool${progress.toolCount === 1 ? "" : "s"})`
|
|
120
167
|
: "";
|
|
121
168
|
box.addChild(new Text(theme.fg("dim", ` ${task.agent}: completed${toolSuffix}`), 0, 0));
|
|
122
|
-
|
|
123
|
-
}
|
|
124
|
-
if (taskStatus === "failed") {
|
|
169
|
+
} else if (taskStatus === "failed") {
|
|
125
170
|
box.addChild(new Text(theme.fg("dim", ` ${task.agent}: failed`), 0, 0));
|
|
126
|
-
|
|
171
|
+
} else {
|
|
172
|
+
box.addChild(new Text(theme.fg("dim", ` ${task.agent}: pending`), 0, 0));
|
|
127
173
|
}
|
|
128
|
-
box.addChild(new Text(theme.fg("dim", ` ${task.agent}: pending`), 0, 0));
|
|
129
174
|
}
|
|
130
175
|
return;
|
|
131
176
|
}
|
|
132
177
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
178
|
+
// Unified tool stream: completed tools (dim) then active tool (warning) at bottom.
|
|
179
|
+
// When a tool finishes it moves from active → completed in place — no visual jump.
|
|
180
|
+
const recentTools = state?.recentTools ?? [];
|
|
181
|
+
for (const tool of recentTools) {
|
|
182
|
+
box.addChild(new Text(theme.fg("dim", formatToolCall(tool.tool, tool.args)), 0, 0));
|
|
183
|
+
}
|
|
184
|
+
if (state?.currentTool) {
|
|
185
|
+
const active = formatToolCall(state.currentTool, state.currentToolArgs ?? "");
|
|
186
|
+
box.addChild(new Text(theme.fg("warning", `> ${active}`), 0, 0));
|
|
139
187
|
}
|
|
140
188
|
|
|
189
|
+
// Recent output
|
|
141
190
|
if (state && state.recentOutput.length > 0) {
|
|
142
|
-
|
|
191
|
+
if (recentTools.length > 0 || state.currentTool) {
|
|
192
|
+
box.addChild(new Spacer(1));
|
|
193
|
+
}
|
|
194
|
+
for (const line of state.recentOutput) {
|
|
143
195
|
box.addChild(new Text(theme.fg("dim", ` ${line}`), 0, 0));
|
|
144
196
|
}
|
|
145
197
|
}
|