pi-prompt-template-model 0.6.6 → 0.6.8
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 +15 -0
- package/index.ts +56 -7
- 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,20 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.6.8] - 2026-03-28
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
- Delegated prompt results are now injected back into the parent conversation as a user message, triggering an agent turn so the parent agent can process and respond to the delegation outcome. Applies to single delegated runs, delegated loops, and delegated chain steps.
|
|
7
|
+
|
|
8
|
+
## [0.6.7] - 2026-03-28
|
|
9
|
+
|
|
10
|
+
### Changed
|
|
11
|
+
- 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.
|
|
12
|
+
- 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`).
|
|
13
|
+
- Delegation progress widget no longer caps tool history or output lines. The box grows to show the full execution trace.
|
|
14
|
+
- Delegation progress widget now refreshes every second during idle periods (model thinking) so the elapsed timer ticks smoothly instead of freezing between progress updates.
|
|
15
|
+
- 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.
|
|
16
|
+
- Removed `lastTool`/`lastToolArgs` tracking from live state (dead code after the unified tool stream redesign).
|
|
17
|
+
|
|
3
18
|
## [0.6.6] - 2026-03-28
|
|
4
19
|
|
|
5
20
|
### Added
|
package/index.ts
CHANGED
|
@@ -45,6 +45,7 @@ interface ExecutionErrorState {
|
|
|
45
45
|
|
|
46
46
|
interface PromptStepResult {
|
|
47
47
|
changed: boolean;
|
|
48
|
+
text?: string;
|
|
48
49
|
}
|
|
49
50
|
|
|
50
51
|
export default function promptModelExtension(pi: ExtensionAPI) {
|
|
@@ -132,9 +133,12 @@ export default function promptModelExtension(pi: ExtensionAPI) {
|
|
|
132
133
|
|
|
133
134
|
for (const command of pi.getCommands()) {
|
|
134
135
|
if (command.source !== "skill") continue;
|
|
135
|
-
|
|
136
|
+
const sourceInfo = "sourceInfo" in command
|
|
137
|
+
? (command as { sourceInfo?: { path?: string } }).sourceInfo
|
|
138
|
+
: undefined;
|
|
139
|
+
if (!sourceInfo?.path) continue;
|
|
136
140
|
if (!candidates.has(command.name)) continue;
|
|
137
|
-
return
|
|
141
|
+
return sourceInfo.path;
|
|
138
142
|
}
|
|
139
143
|
|
|
140
144
|
return undefined;
|
|
@@ -215,7 +219,7 @@ export default function promptModelExtension(pi: ExtensionAPI) {
|
|
|
215
219
|
notify(ctx, `Prompt \`${prompt.name}\` is not configured for delegated execution.`, "error");
|
|
216
220
|
return "aborted";
|
|
217
221
|
}
|
|
218
|
-
return { changed: delegated.changed };
|
|
222
|
+
return { changed: delegated.changed, text: delegated.text };
|
|
219
223
|
} catch (error) {
|
|
220
224
|
notify(ctx, error instanceof Error ? error.message : String(error), "error");
|
|
221
225
|
return { changed: false };
|
|
@@ -418,6 +422,8 @@ export default function promptModelExtension(pi: ExtensionAPI) {
|
|
|
418
422
|
let completedIterations = 0;
|
|
419
423
|
let converged = false;
|
|
420
424
|
let loopErrorState: ExecutionErrorState = { hasError: false, error: undefined };
|
|
425
|
+
let lastDelegatedText: string | undefined;
|
|
426
|
+
let loopAborted = false;
|
|
421
427
|
|
|
422
428
|
try {
|
|
423
429
|
for (let i = 0; i < effectiveMax; i++) {
|
|
@@ -428,6 +434,7 @@ export default function promptModelExtension(pi: ExtensionAPI) {
|
|
|
428
434
|
const prompt = prompts.get(name);
|
|
429
435
|
if (!prompt) {
|
|
430
436
|
notify(ctx, `Prompt "${name}" no longer exists`, "error");
|
|
437
|
+
loopAborted = true;
|
|
431
438
|
break;
|
|
432
439
|
}
|
|
433
440
|
const effectivePrompt = { ...prompt, ...(cwdOverride ? { cwd: cwdOverride } : {}), ...promptOverrides };
|
|
@@ -465,13 +472,20 @@ export default function promptModelExtension(pi: ExtensionAPI) {
|
|
|
465
472
|
undefined,
|
|
466
473
|
loopContext,
|
|
467
474
|
);
|
|
468
|
-
if (stepResult === "aborted")
|
|
475
|
+
if (stepResult === "aborted") {
|
|
476
|
+
loopAborted = true;
|
|
477
|
+
break;
|
|
478
|
+
}
|
|
479
|
+
const delegatedStep = shouldDelegatePrompt(iterationPrompt, subagentOverride);
|
|
480
|
+
if (delegatedStep) {
|
|
481
|
+
lastDelegatedText = stepResult.text;
|
|
482
|
+
}
|
|
469
483
|
|
|
470
484
|
currentModel = getCurrentModel(ctx);
|
|
471
485
|
currentThinking = pi.getThinkingLevel();
|
|
472
486
|
completedIterations++;
|
|
473
487
|
|
|
474
|
-
const iterationChanged =
|
|
488
|
+
const iterationChanged = delegatedStep
|
|
475
489
|
? stepResult.changed
|
|
476
490
|
: didIterationMakeChanges(getIterationEntries(ctx, iterationStartId));
|
|
477
491
|
if (useConverge && (isUnlimited || effectiveMax > 1) && !iterationChanged) {
|
|
@@ -484,6 +498,7 @@ export default function promptModelExtension(pi: ExtensionAPI) {
|
|
|
484
498
|
const result = await ctx.navigateTree(anchorId, { summarize: true });
|
|
485
499
|
freshCollapse = null;
|
|
486
500
|
if (result.cancelled) {
|
|
501
|
+
loopAborted = true;
|
|
487
502
|
notify(ctx, "Loop cancelled", "warning");
|
|
488
503
|
break;
|
|
489
504
|
}
|
|
@@ -514,6 +529,15 @@ export default function promptModelExtension(pi: ExtensionAPI) {
|
|
|
514
529
|
}
|
|
515
530
|
}
|
|
516
531
|
|
|
532
|
+
if (lastDelegatedText && !loopErrorState.hasError && !loopAborted) {
|
|
533
|
+
const label = converged
|
|
534
|
+
? `Delegated loop converged after ${completedIterations} iteration(s): ${name}`
|
|
535
|
+
: `Delegated loop completed ${completedIterations} iteration(s): ${name}`;
|
|
536
|
+
pi.sendUserMessage(`[${label}]\n\n${lastDelegatedText}`);
|
|
537
|
+
await waitForTurnStart(ctx);
|
|
538
|
+
await ctx.waitForIdle();
|
|
539
|
+
}
|
|
540
|
+
|
|
517
541
|
if (loopErrorState.hasError) {
|
|
518
542
|
throw loopErrorState.error;
|
|
519
543
|
}
|
|
@@ -607,6 +631,8 @@ export default function promptModelExtension(pi: ExtensionAPI) {
|
|
|
607
631
|
let completedIterations = 0;
|
|
608
632
|
let converged = false;
|
|
609
633
|
let chainErrorState: ExecutionErrorState = { hasError: false, error: undefined };
|
|
634
|
+
let lastDelegatedText: string | undefined;
|
|
635
|
+
let chainAborted = false;
|
|
610
636
|
if (effectiveMax > 1) {
|
|
611
637
|
loopState = { currentIteration: 1, totalIterations };
|
|
612
638
|
accumulatedSummaries = [];
|
|
@@ -619,7 +645,10 @@ export default function promptModelExtension(pi: ExtensionAPI) {
|
|
|
619
645
|
loopState!.currentIteration = iteration + 1;
|
|
620
646
|
updateLoopStatus(ctx);
|
|
621
647
|
refreshPrompts(ctx.cwd, ctx);
|
|
622
|
-
if (!validateChainSteps())
|
|
648
|
+
if (!validateChainSteps()) {
|
|
649
|
+
chainAborted = true;
|
|
650
|
+
break;
|
|
651
|
+
}
|
|
623
652
|
}
|
|
624
653
|
|
|
625
654
|
const templates = steps.map((step) =>
|
|
@@ -697,6 +726,7 @@ export default function promptModelExtension(pi: ExtensionAPI) {
|
|
|
697
726
|
aborted = true;
|
|
698
727
|
break;
|
|
699
728
|
}
|
|
729
|
+
lastDelegatedText = delegated.text;
|
|
700
730
|
|
|
701
731
|
currentModel = getCurrentModel(ctx);
|
|
702
732
|
currentThinking = pi.getThinkingLevel();
|
|
@@ -761,9 +791,13 @@ export default function promptModelExtension(pi: ExtensionAPI) {
|
|
|
761
791
|
stepLoopContext,
|
|
762
792
|
);
|
|
763
793
|
if (stepResult === "aborted") {
|
|
794
|
+
chainAborted = true;
|
|
764
795
|
aborted = true;
|
|
765
796
|
break;
|
|
766
797
|
}
|
|
798
|
+
if (shouldDelegatePrompt(singleStep.prompt, subagentOverride)) {
|
|
799
|
+
lastDelegatedText = stepResult.text;
|
|
800
|
+
}
|
|
767
801
|
|
|
768
802
|
currentModel = getCurrentModel(ctx);
|
|
769
803
|
currentThinking = pi.getThinkingLevel();
|
|
@@ -787,7 +821,10 @@ export default function promptModelExtension(pi: ExtensionAPI) {
|
|
|
787
821
|
chainStepSummaries.push(generateChainStepSummary(stepEntries, singleStep.prompt.name, stepNumber));
|
|
788
822
|
}
|
|
789
823
|
|
|
790
|
-
if (aborted)
|
|
824
|
+
if (aborted) {
|
|
825
|
+
chainAborted = true;
|
|
826
|
+
break;
|
|
827
|
+
}
|
|
791
828
|
completedIterations++;
|
|
792
829
|
|
|
793
830
|
if (useConverge && (isUnlimited || effectiveMax > 1) && !iterationChanged) {
|
|
@@ -800,6 +837,7 @@ export default function promptModelExtension(pi: ExtensionAPI) {
|
|
|
800
837
|
const result = await ctx.navigateTree(anchorId, { summarize: true });
|
|
801
838
|
freshCollapse = null;
|
|
802
839
|
if (result.cancelled) {
|
|
840
|
+
chainAborted = true;
|
|
803
841
|
notify(ctx, "Loop cancelled", "warning");
|
|
804
842
|
break;
|
|
805
843
|
}
|
|
@@ -835,6 +873,12 @@ export default function promptModelExtension(pi: ExtensionAPI) {
|
|
|
835
873
|
}
|
|
836
874
|
}
|
|
837
875
|
|
|
876
|
+
if (lastDelegatedText && !chainErrorState.hasError && !chainAborted) {
|
|
877
|
+
pi.sendUserMessage(`[Delegated chain complete: ${chainStepNames}]\n\n${lastDelegatedText}`);
|
|
878
|
+
await waitForTurnStart(ctx);
|
|
879
|
+
await ctx.waitForIdle();
|
|
880
|
+
}
|
|
881
|
+
|
|
838
882
|
if (chainErrorState.hasError) {
|
|
839
883
|
throw chainErrorState.error;
|
|
840
884
|
}
|
|
@@ -934,6 +978,11 @@ export default function promptModelExtension(pi: ExtensionAPI) {
|
|
|
934
978
|
subagent.override,
|
|
935
979
|
);
|
|
936
980
|
if (stepResult === "aborted") return;
|
|
981
|
+
if (shouldDelegatePrompt(effectivePrompt, subagent.override) && stepResult.text) {
|
|
982
|
+
pi.sendUserMessage(`[Delegated result: ${name}]\n\n${stepResult.text}`);
|
|
983
|
+
await waitForTurnStart(ctx);
|
|
984
|
+
await ctx.waitForIdle();
|
|
985
|
+
}
|
|
937
986
|
|
|
938
987
|
if (!shouldDelegatePrompt(effectivePrompt, subagent.override) && prompt.restore) {
|
|
939
988
|
const currentModel = getCurrentModel(ctx);
|
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
|
}
|