pi-subagents 0.40.0 → 0.42.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 +97 -0
- package/README.md +54 -1879
- package/agents/oracle.md +1 -0
- package/inspector-runner.mjs +11 -0
- package/package.json +13 -5
- package/prompts/parallel-context-build.md +1 -1
- package/prompts/parallel-handoff-plan.md +1 -1
- package/prompts/review-loop.md +1 -1
- package/skills/pi-subagents/SKILL.md +9 -7
- package/skills/pi-subagents/references/constraints-and-recipes.md +24 -32
- package/skills/pi-subagents/references/execution-controls.md +99 -97
- package/skills/pi-subagents/references/management-authoring-rpc.md +2 -2
- package/skills/pi-subagents/references/prompting-and-roles.md +23 -30
- package/src/agents/agent-management.ts +155 -65
- package/src/agents/agent-serializer.ts +19 -0
- package/src/agents/agents.ts +154 -71
- package/src/agents/chain-serializer.ts +10 -7
- package/src/agents/frontmatter.ts +5 -3
- package/src/agents/identity.ts +1 -1
- package/src/agents/proactive-skills.ts +13 -10
- package/src/agents/skills.ts +23 -6
- package/src/api/control-channel.ts +4 -0
- package/src/api/delegation.ts +26 -194
- package/src/api/external-runs.ts +129 -0
- package/src/api/intercom-bridge.ts +3 -0
- package/src/api/pi-args.ts +5 -0
- package/src/api/preflight.ts +3 -3
- package/src/api/shared-types.ts +19 -0
- package/src/extension/config.ts +10 -0
- package/src/extension/control-notices.ts +5 -39
- package/src/extension/doctor.ts +10 -9
- package/src/extension/fanout-child.ts +7 -4
- package/src/extension/index.ts +232 -68
- package/src/extension/rpc.ts +18 -12
- package/src/extension/schemas.ts +48 -37
- package/src/extension/tool-description.ts +36 -86
- package/src/inspectors/herdr/actions.ts +229 -0
- package/src/inspectors/herdr/client.ts +130 -0
- package/src/inspectors/herdr/inspector-runner.ts +141 -0
- package/src/inspectors/herdr/project-panes.ts +154 -0
- package/src/integrations/herdr-status.ts +330 -0
- package/src/intercom/intercom-bridge.ts +3 -2
- package/src/intercom/result-intercom.ts +5 -1
- package/src/missions/actions.ts +374 -0
- package/src/missions/lifecycle.ts +314 -0
- package/src/missions/store.ts +442 -0
- package/src/missions/types.ts +135 -0
- package/src/policy/authority.ts +46 -0
- package/src/profiles/profiles.ts +29 -3
- package/src/runs/background/async-execution.ts +110 -50
- package/src/runs/background/async-job-tracker.ts +27 -4
- package/src/runs/background/async-resume.ts +6 -6
- package/src/runs/background/async-status.ts +29 -1
- package/src/runs/background/auto-drain.ts +3 -3
- package/src/runs/background/chain-append.ts +3 -2
- package/src/runs/background/control-channel.ts +9 -7
- package/src/runs/background/fleet-view.ts +3 -4
- package/src/runs/background/notify.ts +2 -1
- package/src/runs/background/process-terminal.ts +5 -5
- package/src/runs/background/result-watcher.ts +13 -5
- package/src/runs/background/run-id-resolver.ts +3 -3
- package/src/runs/background/run-status.ts +35 -8
- package/src/runs/background/scheduled-runs.ts +602 -375
- package/src/runs/background/stale-run-reconciler.ts +3 -3
- package/src/runs/background/subagent-runner.ts +608 -445
- package/src/runs/background/subagent-wait.ts +50 -9
- package/src/runs/background/wait-subscriptions.ts +253 -0
- package/src/runs/background/wait-tool.ts +12 -4
- package/src/runs/foreground/async-steering-action.ts +3 -3
- package/src/runs/foreground/chain-clarify.ts +8 -4
- package/src/runs/foreground/chain-execution.ts +56 -30
- package/src/runs/foreground/execution.ts +34 -9
- package/src/runs/foreground/subagent-executor.ts +1036 -274
- package/src/runs/shared/acceptance.ts +28 -6
- package/src/runs/shared/child-protocol.ts +302 -22
- package/src/runs/shared/dynamic-fanout.ts +1 -1
- package/src/runs/shared/external-cli-runner.ts +130 -0
- package/src/runs/shared/long-running-guard.ts +42 -1
- package/src/runs/shared/mcp-direct-tool-allowlist.ts +2 -0
- package/src/runs/shared/nested-events.ts +59 -5
- package/src/runs/shared/nested-render.ts +9 -4
- package/src/runs/shared/parallel-handoff.ts +86 -2
- package/src/runs/shared/parallel-utils.ts +11 -2
- package/src/runs/shared/permissions.ts +95 -0
- package/src/runs/shared/pi-args.ts +468 -109
- package/src/runs/shared/pi-spawn.ts +11 -1
- package/src/runs/shared/run-history.ts +1 -1
- package/src/runs/shared/subagent-prompt-runtime.ts +36 -5
- package/src/runs/shared/subagent-startup-retry.ts +5 -2
- package/src/runs/shared/turn-budget.ts +6 -6
- package/src/runs/shared/worktree.ts +122 -12
- package/src/shared/accessible-dir.ts +29 -7
- package/src/shared/artifacts.ts +143 -1
- package/src/shared/fork-context.ts +3 -2
- package/src/shared/launch-contract.ts +1 -0
- package/src/shared/settings.ts +10 -0
- package/src/shared/types.ts +156 -14
- package/src/shared/utils.ts +8 -6
- package/src/slash/delegation-adapters.ts +32 -194
- package/src/slash/delegation-request.ts +43 -126
- package/src/slash/prompt-template-bridge.ts +158 -205
- package/src/slash/prompt-workflows.ts +21 -57
- package/src/slash/slash-bridge.ts +14 -0
- package/src/slash/slash-commands.ts +31 -632
- package/src/slash/subagents-admin.ts +18 -14
- package/src/tui/fleet-status.ts +156 -21
- package/src/tui/fleet-transcript.ts +110 -5
- package/src/tui/fleet.ts +57 -25
- package/src/tui/render.ts +368 -152
- package/src/types/pi-runtime-compat.d.ts +8 -0
- package/src/watchdog/lsp-diagnostics.ts +12 -7
- package/src/watchdog/model-selection.ts +2 -2
- package/src/watchdog/permission-arbiter.ts +145 -0
- package/src/watchdog/register-child.ts +1 -1
- package/src/watchdog/register-main.ts +1 -1
- package/src/watchdog/review.ts +4 -1
- package/src/watchdog/runtime.ts +3 -2
- package/src/workflows/chat-progress.ts +140 -0
- package/src/workflows/scripted-workflow.ts +415 -0
- package/agents/advisor.md +0 -73
- package/src/extension/chain-validation.ts +0 -181
package/src/tui/render.ts
CHANGED
|
@@ -24,6 +24,7 @@ import { flatToLogicalStepIndex } from "../runs/background/parallel-groups.ts";
|
|
|
24
24
|
import { formatNestedAggregate } from "../runs/shared/nested-render.ts";
|
|
25
25
|
import { aggregateStepStatus, formatActivityLabel, formatAgentRunningLabel, formatParallelOutcome } from "../shared/status-format.ts";
|
|
26
26
|
import { contextModeBadge, contextModePrefix } from "../runs/shared/context-mode.ts";
|
|
27
|
+
import { buildWorkflowChatProgressRows, type WorkflowChatProgressRow } from "../workflows/chat-progress.ts";
|
|
27
28
|
|
|
28
29
|
type Theme = ExtensionContext["ui"]["theme"];
|
|
29
30
|
|
|
@@ -143,6 +144,24 @@ function runningGlyph(seed?: number): string {
|
|
|
143
144
|
return RUNNING_FRAMES[Math.abs(seed) % RUNNING_FRAMES.length]!;
|
|
144
145
|
}
|
|
145
146
|
|
|
147
|
+
const WIDGET_ANIMATION_FRAME_MS = 500;
|
|
148
|
+
let widgetRequestRender: (() => void) | undefined;
|
|
149
|
+
|
|
150
|
+
function animatedSeed(seed: number | undefined, frame: number | undefined): number | undefined {
|
|
151
|
+
if (frame === undefined) return seed;
|
|
152
|
+
return (seed ?? 0) + frame;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function widgetAnimationFrame(): number {
|
|
156
|
+
return Math.floor(Date.now() / WIDGET_ANIMATION_FRAME_MS);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export function requestWidgetRender(): boolean {
|
|
160
|
+
if (!widgetRequestRender) return false;
|
|
161
|
+
widgetRequestRender();
|
|
162
|
+
return true;
|
|
163
|
+
}
|
|
164
|
+
|
|
146
165
|
function progressRunningSeed(progress: ProgressSeedSource | undefined): number | undefined {
|
|
147
166
|
if (!progress) return undefined;
|
|
148
167
|
return runningSeed(
|
|
@@ -277,17 +296,81 @@ function resultStatusLine(result: Details["results"][number], output: string): s
|
|
|
277
296
|
return "Done";
|
|
278
297
|
}
|
|
279
298
|
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
299
|
+
type ResultPresentation = {
|
|
300
|
+
glyph: string;
|
|
301
|
+
label: "running" | "detached" | "stopped" | "paused" | "failed" | "completed";
|
|
302
|
+
tone: "accent" | "warning" | "error" | "success";
|
|
303
|
+
};
|
|
304
|
+
|
|
305
|
+
function semanticResultPresentation(input: {
|
|
306
|
+
running?: boolean;
|
|
307
|
+
detached?: boolean;
|
|
308
|
+
stopped?: boolean;
|
|
309
|
+
interrupted?: boolean;
|
|
310
|
+
failed?: boolean;
|
|
311
|
+
completedWithoutOutput?: boolean;
|
|
312
|
+
seed?: number;
|
|
313
|
+
frame?: number;
|
|
314
|
+
}): ResultPresentation {
|
|
315
|
+
if (input.running) {
|
|
316
|
+
const glyph = input.frame !== undefined ? runningGlyph((input.seed ?? 0) + input.frame) : runningGlyph(input.seed);
|
|
317
|
+
return { glyph, label: "running", tone: "accent" };
|
|
284
318
|
}
|
|
285
|
-
if (
|
|
286
|
-
if (
|
|
287
|
-
if (
|
|
288
|
-
if (
|
|
289
|
-
|
|
290
|
-
|
|
319
|
+
if (input.detached) return { glyph: "■", label: "detached", tone: "warning" };
|
|
320
|
+
if (input.stopped) return { glyph: "■", label: "stopped", tone: "warning" };
|
|
321
|
+
if (input.interrupted) return { glyph: "■", label: "paused", tone: "warning" };
|
|
322
|
+
if (input.failed) return { glyph: "✗", label: "failed", tone: "error" };
|
|
323
|
+
return { glyph: "✓", label: "completed", tone: input.completedWithoutOutput ? "warning" : "success" };
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
function hasTerminalResultFlag(result: Details["results"][number]): boolean {
|
|
327
|
+
return Boolean(result.detached || result.stopped || result.interrupted);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
function hasTerminalResult(result: Details["results"][number]): boolean {
|
|
331
|
+
if (hasTerminalResultFlag(result)) return true;
|
|
332
|
+
const status = result.progress?.status;
|
|
333
|
+
if (status === "running" || status === "pending") return false;
|
|
334
|
+
return result.exitCode !== undefined;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
function isResultRunning(result: Details["results"][number], status = result.progress?.status): boolean {
|
|
338
|
+
return status === "running" && !hasTerminalResultFlag(result);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
function detailsHaveRunningResult(details: Details): boolean {
|
|
342
|
+
return details.progress?.some((progress) => {
|
|
343
|
+
if (progress.status !== "running") return false;
|
|
344
|
+
const result = details.results.find((entry) => entry.progress?.index === progress.index) ?? details.results[progress.index];
|
|
345
|
+
return !result || !hasTerminalResultFlag(result);
|
|
346
|
+
})
|
|
347
|
+
|| details.results.some((result) => isResultRunning(result))
|
|
348
|
+
|| workflowGraphHasStatus(details, ["running"]);
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
function resultPresentation(result: Details["results"][number], output: string, running = isResultRunning(result), seed = progressRunningSeed(result.progress ?? result.progressSummary), frame?: number): ResultPresentation {
|
|
352
|
+
return semanticResultPresentation({
|
|
353
|
+
running,
|
|
354
|
+
detached: result.detached,
|
|
355
|
+
stopped: result.stopped,
|
|
356
|
+
interrupted: result.interrupted,
|
|
357
|
+
failed: result.exitCode !== 0,
|
|
358
|
+
completedWithoutOutput: hasEmptyTextOutputWithoutOutputTarget(result.task, output),
|
|
359
|
+
seed,
|
|
360
|
+
frame,
|
|
361
|
+
});
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
function resultGlyph(result: Details["results"][number], output: string, theme: Theme, running = isResultRunning(result), seed = progressRunningSeed(result.progress ?? result.progressSummary), frame?: number): string {
|
|
365
|
+
const presentation = resultPresentation(result, output, running, seed, frame);
|
|
366
|
+
return theme.fg(presentation.tone, presentation.glyph);
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
function styledResultPresentation(presentation: ResultPresentation, theme: Theme): { glyph: string; label: string } {
|
|
370
|
+
return {
|
|
371
|
+
glyph: theme.fg(presentation.tone, presentation.glyph),
|
|
372
|
+
label: theme.fg(presentation.tone, presentation.label),
|
|
373
|
+
};
|
|
291
374
|
}
|
|
292
375
|
|
|
293
376
|
function compactCurrentActivity(progress: AgentProgress): string {
|
|
@@ -396,8 +479,8 @@ function widgetJobsRunningSeed(jobs: AsyncJobState[]): number | undefined {
|
|
|
396
479
|
return seed;
|
|
397
480
|
}
|
|
398
481
|
|
|
399
|
-
function widgetStatusGlyph(job: AsyncJobState, theme: Theme): string {
|
|
400
|
-
if (job.status === "running") return theme.fg("accent", runningGlyph(widgetJobRunningSeed(job)));
|
|
482
|
+
function widgetStatusGlyph(job: AsyncJobState, theme: Theme, frame?: number): string {
|
|
483
|
+
if (job.status === "running") return theme.fg("accent", runningGlyph(animatedSeed(widgetJobRunningSeed(job), frame)));
|
|
401
484
|
if (job.status === "queued") return theme.fg("muted", "◦");
|
|
402
485
|
if (job.status === "complete") return theme.fg("success", "✓");
|
|
403
486
|
if (job.status === "paused") return theme.fg("warning", "■");
|
|
@@ -405,8 +488,8 @@ function widgetStatusGlyph(job: AsyncJobState, theme: Theme): string {
|
|
|
405
488
|
return theme.fg("error", "✗");
|
|
406
489
|
}
|
|
407
490
|
|
|
408
|
-
function widgetStepGlyph(status: AsyncJobStep["status"], theme: Theme, seed?: number): string {
|
|
409
|
-
if (status === "running") return theme.fg("accent", runningGlyph(seed));
|
|
491
|
+
function widgetStepGlyph(status: AsyncJobStep["status"], theme: Theme, seed?: number, frame?: number): string {
|
|
492
|
+
if (status === "running") return theme.fg("accent", runningGlyph(animatedSeed(seed, frame)));
|
|
410
493
|
if (status === "complete" || status === "completed") return theme.fg("success", "✓");
|
|
411
494
|
if (status === "failed") return theme.fg("error", "✗");
|
|
412
495
|
if (status === "paused") return theme.fg("warning", "■");
|
|
@@ -471,7 +554,7 @@ function widgetParallelAgentDetails(job: AsyncJobState, theme: Theme, expanded =
|
|
|
471
554
|
const itemTitle = job.mode === "parallel" || job.activeParallelGroup ? "Agent" : "Step";
|
|
472
555
|
const modelDisplay = modelThinkingBadge(theme, step.model, step.thinking);
|
|
473
556
|
lines.push(` ${theme.fg("dim", `${marker} ${widgetStepGlyph(step.status, theme, widgetStepRunningSeed(step, index))} ${itemTitle} ${index + 1}/${total}: ${step.agent} · ${widgetStepStatus(step.status, theme)}${modelDisplay}${activity ? ` · ${activity}` : ""}`)}`);
|
|
474
|
-
for (const nestedLine of formatNestedWidgetLines(step.children, theme, width, expanded, job.updatedAt, expanded ? 8 :
|
|
557
|
+
for (const nestedLine of formatNestedWidgetLines(step.children, theme, width, expanded, job.updatedAt, expanded ? 8 : 6)) lines.push(` ${nestedLine}`);
|
|
475
558
|
}
|
|
476
559
|
return lines;
|
|
477
560
|
}
|
|
@@ -639,14 +722,12 @@ function buildMultiProgressLabel(details: Pick<Details, "mode" | "results" | "pr
|
|
|
639
722
|
|| details.progress?.find((progress) => progress.agent === result.agent && progress.status === "running");
|
|
640
723
|
const index = result.progress?.index ?? progressFromArray?.index ?? i;
|
|
641
724
|
if (index < 0 || index >= totalCount) continue;
|
|
642
|
-
const status = result.
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
? "completed"
|
|
649
|
-
: "failed");
|
|
725
|
+
const status = result.stopped
|
|
726
|
+
? "stopped"
|
|
727
|
+
: result.interrupted || result.detached
|
|
728
|
+
? "detached"
|
|
729
|
+
: result.progress?.status
|
|
730
|
+
?? (result.exitCode === 0 ? "completed" : "failed");
|
|
650
731
|
statuses[index] = status;
|
|
651
732
|
}
|
|
652
733
|
const running = statuses.filter((status) => status === "running").length;
|
|
@@ -667,8 +748,8 @@ function buildMultiProgressLabel(details: Pick<Details, "mode" | "results" | "pr
|
|
|
667
748
|
let done = 0;
|
|
668
749
|
for (let index = groupStart; index < groupEnd; index++) {
|
|
669
750
|
const progressEntry = details.progress?.find((progress) => progress.index === index);
|
|
670
|
-
const resultEntry = details.results.find((result) => result.progress?.index === index);
|
|
671
|
-
if (progressEntry?.status === "running") {
|
|
751
|
+
const resultEntry = details.results.find((result) => result.progress?.index === index) ?? details.results[index];
|
|
752
|
+
if (progressEntry?.status === "running" && (!resultEntry || !hasTerminalResultFlag(resultEntry))) {
|
|
672
753
|
running++;
|
|
673
754
|
continue;
|
|
674
755
|
}
|
|
@@ -847,9 +928,56 @@ function nestedActivity(input: Pick<NestedRunSummary | NestedStepSummary, "activ
|
|
|
847
928
|
function formatNestedWidgetLines(children: NestedRunSummary[] | undefined, theme: Theme, width: number, expanded: boolean, snapshotNow?: number, lineBudget = expanded ? 12 : 1): string[] {
|
|
848
929
|
if (!children?.length || lineBudget <= 0) return [];
|
|
849
930
|
if (!expanded) {
|
|
850
|
-
|
|
851
|
-
const
|
|
852
|
-
|
|
931
|
+
type CollapsedRow = { text: string; prefix: string };
|
|
932
|
+
const rows: CollapsedRow[] = [];
|
|
933
|
+
const maxLeaves = 4;
|
|
934
|
+
const maxLines = Math.min(6, lineBudget);
|
|
935
|
+
let leaves = 0;
|
|
936
|
+
let overflow = 0;
|
|
937
|
+
const appendLeaf = (step: NestedStepSummary | NestedRunSummary, prefix: string, fallback?: number): void => {
|
|
938
|
+
if (leaves >= maxLeaves) {
|
|
939
|
+
overflow++;
|
|
940
|
+
return;
|
|
941
|
+
}
|
|
942
|
+
leaves++;
|
|
943
|
+
const state = "status" in step ? step.status : step.state;
|
|
944
|
+
const modelThinking = formatModelThinking(step.model, step.thinking);
|
|
945
|
+
const activity = nestedActivity(step, state, snapshotNow ?? fallback);
|
|
946
|
+
const timestamp = "status" in step ? nestedStepTimestamp(step, fallback) : formatClockTime(nestedRunEventTime(step));
|
|
947
|
+
const error = step.error ? ` · ${step.error}` : "";
|
|
948
|
+
const name = "status" in step ? step.agent : nestedRunName(step);
|
|
949
|
+
rows.push({
|
|
950
|
+
prefix,
|
|
951
|
+
text: `${nestedTimestampPrefix(timestamp)}${nestedStatusGlyph(state, theme)} ${name} · ${state}${modelThinking ? ` · ${modelThinking}` : ""}${activity ? ` · ${activity}` : ""}${error}`,
|
|
952
|
+
});
|
|
953
|
+
};
|
|
954
|
+
for (const child of children) {
|
|
955
|
+
const steps = (child.mode === "parallel" || child.mode === "chain") ? child.steps ?? [] : [];
|
|
956
|
+
if (steps.length > 0) {
|
|
957
|
+
const ownerModelThinking = formatModelThinking(child.model, child.thinking);
|
|
958
|
+
const ownerActivity = nestedActivity(child, child.state, snapshotNow ?? child.lastUpdate);
|
|
959
|
+
const ownerError = child.error ? ` · ${child.error}` : "";
|
|
960
|
+
rows.push({
|
|
961
|
+
prefix: "↳ ",
|
|
962
|
+
text: `OWNER ${nestedStatusGlyph(child.state, theme, nestedRunSeed(child))} ${nestedRunName(child)} · ${child.state}${ownerModelThinking ? ` · ${ownerModelThinking}` : ""}${ownerActivity ? ` · ${ownerActivity}` : ""}${ownerError}`,
|
|
963
|
+
});
|
|
964
|
+
for (const step of steps) appendLeaf(step, "↳ │ ", child.lastUpdate);
|
|
965
|
+
} else {
|
|
966
|
+
appendLeaf(child, "↳ ", child.lastUpdate);
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
if (overflow > 0) rows.push({ prefix: "↳ ", text: `… +${overflow} more nested leaves` });
|
|
970
|
+
const visibleRows = rows.length <= maxLines
|
|
971
|
+
? rows
|
|
972
|
+
: overflow > 0
|
|
973
|
+
? [...rows.slice(0, Math.max(0, maxLines - 1)), rows.at(-1)!]
|
|
974
|
+
: rows.slice(0, maxLines);
|
|
975
|
+
return visibleRows.map((row, index) => {
|
|
976
|
+
const marker = index === visibleRows.length - 1 ? "└─" : "├─";
|
|
977
|
+
const prefix = row.prefix;
|
|
978
|
+
const text = row.text.startsWith("OWNER ") ? row.text.slice("OWNER ".length) : row.text;
|
|
979
|
+
return truncLine(theme.fg("dim", `${prefix}${marker} ${text}`), width);
|
|
980
|
+
});
|
|
853
981
|
}
|
|
854
982
|
const lines: string[] = [];
|
|
855
983
|
const maxDepth = 2;
|
|
@@ -869,7 +997,8 @@ function formatNestedWidgetLines(children: NestedRunSummary[] | undefined, theme
|
|
|
869
997
|
}
|
|
870
998
|
const activity = nestedActivity(child, child.state, snapshotNow ?? child.lastUpdate);
|
|
871
999
|
const error = child.error ? ` · ${child.error}` : "";
|
|
872
|
-
|
|
1000
|
+
const modelThinking = formatModelThinking(child.model, child.thinking);
|
|
1001
|
+
lines.push(theme.fg("dim", `${prefix}↳ ${nestedTimestampPrefix(formatClockTime(nestedRunEventTime(child)))}${nestedStatusGlyph(child.state, theme, nestedRunSeed(child))} ${nestedRunName(child)} · ${child.state}${modelThinking ? ` · ${modelThinking}` : ""} · ${activity}${error}`));
|
|
873
1002
|
if (depth === maxDepth) {
|
|
874
1003
|
const aggregate = formatNestedAggregate([...(child.steps?.flatMap((step) => step.children ?? []) ?? []), ...(child.children ?? [])]);
|
|
875
1004
|
if (aggregate && lines.length < lineBudget) lines.push(theme.fg("dim", `${prefix} ↳ ${aggregate}`));
|
|
@@ -877,7 +1006,8 @@ function formatNestedWidgetLines(children: NestedRunSummary[] | undefined, theme
|
|
|
877
1006
|
}
|
|
878
1007
|
for (const step of child.steps ?? []) {
|
|
879
1008
|
if (lines.length >= lineBudget) return;
|
|
880
|
-
|
|
1009
|
+
const modelThinking = formatModelThinking(step.model, step.thinking);
|
|
1010
|
+
lines.push(theme.fg("dim", `${prefix} ↳ ${nestedTimestampPrefix(nestedStepTimestamp(step, child.lastUpdate))}${nestedStatusGlyph(step.status, theme)} ${step.agent} · ${step.status}${modelThinking ? ` · ${modelThinking}` : ""} · ${nestedActivity(step, step.status, snapshotNow ?? child.lastUpdate)}`));
|
|
881
1011
|
append(step.children, depth + 1, `${prefix} `);
|
|
882
1012
|
}
|
|
883
1013
|
append(child.children, depth + 1, `${prefix} `);
|
|
@@ -896,14 +1026,15 @@ function foregroundStyleWidgetStepLines(
|
|
|
896
1026
|
total: number,
|
|
897
1027
|
expanded: boolean,
|
|
898
1028
|
width: number,
|
|
1029
|
+
frame?: number,
|
|
899
1030
|
): string[] {
|
|
900
1031
|
const status = widgetStepStatus(step.status, theme);
|
|
901
1032
|
const stats = widgetStepStats(theme, step);
|
|
902
1033
|
const modelDisplay = modelThinkingBadge(theme, step.model, step.thinking);
|
|
903
|
-
const lines = [` ${widgetStepGlyph(step.status, theme, widgetStepRunningSeed(step, index - 1))} ${itemTitle} ${index}/${total}: ${themeBold(theme, step.agent)}${contextModeBadge(theme, step.context)} ${theme.fg("dim", "·")} ${status}${modelDisplay}${stats ? ` ${theme.fg("dim", "·")} ${stats}` : ""}`];
|
|
1034
|
+
const lines = [` ${widgetStepGlyph(step.status, theme, widgetStepRunningSeed(step, index - 1), frame)} ${itemTitle} ${index}/${total}: ${themeBold(theme, step.agent)}${contextModeBadge(theme, step.context)} ${theme.fg("dim", "·")} ${status}${modelDisplay}${stats ? ` ${theme.fg("dim", "·")} ${stats}` : ""}`];
|
|
904
1035
|
const activity = widgetStepActivityLine(step, width, expanded, job.updatedAt);
|
|
905
1036
|
if (activity) lines.push(` ${theme.fg("dim", `⎿ ${activity}`)}`);
|
|
906
|
-
for (const nestedLine of formatNestedWidgetLines(step.children, theme, width, expanded, job.updatedAt)) {
|
|
1037
|
+
for (const nestedLine of formatNestedWidgetLines(step.children, theme, width, expanded, job.updatedAt, expanded ? 12 : 6)) {
|
|
907
1038
|
lines.push(` ${nestedLine}`);
|
|
908
1039
|
}
|
|
909
1040
|
if (step.status === "running") {
|
|
@@ -926,40 +1057,40 @@ function foregroundStyleWidgetStepLines(
|
|
|
926
1057
|
return lines;
|
|
927
1058
|
}
|
|
928
1059
|
|
|
929
|
-
function foregroundStyleWidgetDetails(job: AsyncJobState, theme: Theme, expanded: boolean, width: number): string[] {
|
|
1060
|
+
function foregroundStyleWidgetDetails(job: AsyncJobState, theme: Theme, expanded: boolean, width: number, frame?: number): string[] {
|
|
930
1061
|
if (!job.steps?.length) return [
|
|
931
1062
|
` ${theme.fg("dim", `⎿ ${widgetActivity(job)}`)}`,
|
|
932
|
-
...formatNestedWidgetLines(job.nestedChildren, theme, width, expanded, job.updatedAt).map((line) => ` ${line}`),
|
|
1063
|
+
...formatNestedWidgetLines(job.nestedChildren, theme, width, expanded, job.updatedAt, expanded ? 12 : 6).map((line) => ` ${line}`),
|
|
933
1064
|
];
|
|
934
1065
|
if (job.mode === "chain" && !job.activeParallelGroup && job.parallelGroups?.length) return widgetChainDetails(job, theme, expanded, width);
|
|
935
1066
|
const total = job.stepsTotal ?? job.steps.length;
|
|
936
1067
|
const itemTitle = job.mode === "parallel" || job.activeParallelGroup ? "Agent" : "Step";
|
|
937
1068
|
const lines: string[] = [];
|
|
938
1069
|
for (const [index, step] of job.steps.entries()) {
|
|
939
|
-
lines.push(...foregroundStyleWidgetStepLines(job, theme, step, itemTitle, index + 1, total, expanded, width));
|
|
1070
|
+
lines.push(...foregroundStyleWidgetStepLines(job, theme, step, itemTitle, index + 1, total, expanded, width, frame));
|
|
940
1071
|
}
|
|
941
1072
|
const attached = new Set(job.steps.flatMap((step) => step.children?.map((child) => child.id) ?? []));
|
|
942
1073
|
const unattached = job.nestedChildren?.filter((child) => !attached.has(child.id)) ?? [];
|
|
943
|
-
for (const nestedLine of formatNestedWidgetLines(unattached, theme, width, expanded, job.updatedAt)) {
|
|
1074
|
+
for (const nestedLine of formatNestedWidgetLines(unattached, theme, width, expanded, job.updatedAt, expanded ? 12 : 6)) {
|
|
944
1075
|
lines.push(` ${nestedLine}`);
|
|
945
1076
|
}
|
|
946
1077
|
return lines;
|
|
947
1078
|
}
|
|
948
1079
|
|
|
949
|
-
function buildSingleWidgetLines(job: AsyncJobState, theme: Theme, width: number, expanded: boolean): string[] {
|
|
1080
|
+
function buildSingleWidgetLines(job: AsyncJobState, theme: Theme, width: number, expanded: boolean, frame?: number): string[] {
|
|
950
1081
|
const stats = widgetStats(job, theme);
|
|
951
1082
|
const count = job.mode === "chain" ? job.chainStepCount : job.stepsTotal ?? job.agents?.length ?? job.steps?.length;
|
|
952
1083
|
const mode = widgetJobName(job);
|
|
953
1084
|
const title = `async subagent ${mode}${count && count > 1 ? ` (${count})` : ""}`;
|
|
954
1085
|
return [
|
|
955
1086
|
`${theme.fg("toolTitle", themeBold(theme, title))} ${theme.fg("dim", "· background")}`,
|
|
956
|
-
`${widgetStatusGlyph(job, theme)} ${themeBold(theme, mode)}${contextModeBadge(theme, job.context)}${stats ? ` ${theme.fg("dim", "·")} ${stats}` : ""}`,
|
|
957
|
-
...foregroundStyleWidgetDetails(job, theme, expanded, width),
|
|
1087
|
+
`${widgetStatusGlyph(job, theme, frame)} ${themeBold(theme, mode)}${contextModeBadge(theme, job.context)}${stats ? ` ${theme.fg("dim", "·")} ${stats}` : ""}`,
|
|
1088
|
+
...foregroundStyleWidgetDetails(job, theme, expanded, width, frame),
|
|
958
1089
|
].map((line) => truncLine(line, width));
|
|
959
1090
|
}
|
|
960
1091
|
|
|
961
|
-
function compactSingleWidgetLines(job: AsyncJobState, theme: Theme, width: number): string[] {
|
|
962
|
-
const fullLines = buildSingleWidgetLines(job, theme, width, false);
|
|
1092
|
+
function compactSingleWidgetLines(job: AsyncJobState, theme: Theme, width: number, frame?: number): string[] {
|
|
1093
|
+
const fullLines = buildSingleWidgetLines(job, theme, width, false, frame);
|
|
963
1094
|
if (fullLines.length <= 10 || !job.steps?.length || (job.mode !== "parallel" && !job.activeParallelGroup)) return fullLines;
|
|
964
1095
|
|
|
965
1096
|
const total = job.stepsTotal ?? job.steps.length;
|
|
@@ -971,8 +1102,8 @@ function compactSingleWidgetLines(job: AsyncJobState, theme: Theme, width: numbe
|
|
|
971
1102
|
const stepStats = widgetStepStats(theme, step);
|
|
972
1103
|
const activitySuffix = activity ? ` ${theme.fg("dim", "·")} ${theme.fg("dim", activity)}` : "";
|
|
973
1104
|
const modelDisplay = modelThinkingBadge(theme, step.model, step.thinking);
|
|
974
|
-
lines.push(` ${widgetStepGlyph(step.status, theme, widgetStepRunningSeed(step, index))} ${itemTitle} ${index + 1}/${total}: ${themeBold(theme, step.agent)}${contextModeBadge(theme, step.context)} ${theme.fg("dim", "·")} ${status}${modelDisplay}${activitySuffix}${stepStats ? ` ${theme.fg("dim", "·")} ${stepStats}` : ""}`);
|
|
975
|
-
for (const nestedLine of formatNestedWidgetLines(step.children, theme, width, false, job.updatedAt)) lines.push(` ${nestedLine}`);
|
|
1105
|
+
lines.push(` ${widgetStepGlyph(step.status, theme, widgetStepRunningSeed(step, index), frame)} ${itemTitle} ${index + 1}/${total}: ${themeBold(theme, step.agent)}${contextModeBadge(theme, step.context)} ${theme.fg("dim", "·")} ${status}${modelDisplay}${activitySuffix}${stepStats ? ` ${theme.fg("dim", "·")} ${stepStats}` : ""}`);
|
|
1106
|
+
for (const nestedLine of formatNestedWidgetLines(step.children, theme, width, false, job.updatedAt, 6)) lines.push(` ${nestedLine}`);
|
|
976
1107
|
}
|
|
977
1108
|
if (job.steps.some((step) => step.status === "running")) lines.push(theme.fg("accent", ` ${liveDetailHintText()}`));
|
|
978
1109
|
return lines.map((line) => truncLine(line, width));
|
|
@@ -1027,10 +1158,10 @@ function widgetHeaderCounts(jobs: AsyncJobState[]): { running: AsyncJobState[];
|
|
|
1027
1158
|
};
|
|
1028
1159
|
}
|
|
1029
1160
|
|
|
1030
|
-
function buildSingleLineWidgetLines(jobs: AsyncJobState[], theme: Theme, width: number): string[] {
|
|
1161
|
+
function buildSingleLineWidgetLines(jobs: AsyncJobState[], theme: Theme, width: number, frame?: number): string[] {
|
|
1031
1162
|
const counts = widgetHeaderCounts(jobs);
|
|
1032
1163
|
const hasActive = counts.running.length > 0 || counts.queued.length > 0;
|
|
1033
|
-
const glyph = counts.running.length > 0 ? runningGlyph(widgetJobsRunningSeed(counts.running)) : hasActive ? "●" : "○";
|
|
1164
|
+
const glyph = counts.running.length > 0 ? runningGlyph(animatedSeed(widgetJobsRunningSeed(counts.running), frame)) : hasActive ? "●" : "○";
|
|
1034
1165
|
const parts: string[] = [];
|
|
1035
1166
|
if (counts.running.length > 0) parts.push(`${counts.running.length}/${jobs.length} running`);
|
|
1036
1167
|
if (counts.queued.length > 0) parts.push(`${counts.queued.length} queued`);
|
|
@@ -1090,10 +1221,10 @@ function selectProgressiveJobKeys(jobs: AsyncJobState[], previousKeys: string[],
|
|
|
1090
1221
|
return selected;
|
|
1091
1222
|
}
|
|
1092
1223
|
|
|
1093
|
-
function progressiveHeaderLine(jobs: AsyncJobState[], theme: Theme, width: number): string {
|
|
1224
|
+
function progressiveHeaderLine(jobs: AsyncJobState[], theme: Theme, width: number, frame?: number): string {
|
|
1094
1225
|
const counts = widgetHeaderCounts(jobs);
|
|
1095
1226
|
const hasActive = counts.running.length > 0 || counts.queued.length > 0;
|
|
1096
|
-
const glyph = counts.running.length > 0 ? runningGlyph(widgetJobsRunningSeed(counts.running)) : hasActive ? "●" : "○";
|
|
1227
|
+
const glyph = counts.running.length > 0 ? runningGlyph(animatedSeed(widgetJobsRunningSeed(counts.running), frame)) : hasActive ? "●" : "○";
|
|
1097
1228
|
const parts: string[] = [];
|
|
1098
1229
|
if (counts.running.length > 0) parts.push(formatAgentRunningLabel(counts.running.length));
|
|
1099
1230
|
if (counts.queued.length > 0) parts.push(`${counts.queued.length} queued`);
|
|
@@ -1106,7 +1237,7 @@ function progressiveHeaderLine(jobs: AsyncJobState[], theme: Theme, width: numbe
|
|
|
1106
1237
|
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);
|
|
1107
1238
|
}
|
|
1108
1239
|
|
|
1109
|
-
function progressiveJobLine(job: AsyncJobState, theme: Theme, width: number): string {
|
|
1240
|
+
function progressiveJobLine(job: AsyncJobState, theme: Theme, width: number, frame?: number): string {
|
|
1110
1241
|
const stats = widgetStats(job, theme);
|
|
1111
1242
|
const activity = widgetActivity(job);
|
|
1112
1243
|
const status = job.status === "complete" ? "done" : job.status;
|
|
@@ -1116,7 +1247,7 @@ function progressiveJobLine(job: AsyncJobState, theme: Theme, width: number): st
|
|
|
1116
1247
|
stats,
|
|
1117
1248
|
activity && activity.toLowerCase() !== status ? theme.fg("dim", activity) : "",
|
|
1118
1249
|
].filter(Boolean);
|
|
1119
|
-
return truncLine(` ${widgetStatusGlyph(job, theme)} ${parts.join(` ${theme.fg("dim", "·")} `)}`, width);
|
|
1250
|
+
return truncLine(` ${widgetStatusGlyph(job, theme, frame)} ${parts.join(` ${theme.fg("dim", "·")} `)}`, width);
|
|
1120
1251
|
}
|
|
1121
1252
|
|
|
1122
1253
|
function progressiveHiddenLine(hiddenJobs: AsyncJobState[], theme: Theme, width: number): string {
|
|
@@ -1129,9 +1260,9 @@ function progressiveHiddenLine(hiddenJobs: AsyncJobState[], theme: Theme, width:
|
|
|
1129
1260
|
return truncLine(theme.fg("dim", ` +${hiddenJobs.length} more${parts.length ? ` (${parts.join(", ")})` : ""}`), width);
|
|
1130
1261
|
}
|
|
1131
1262
|
|
|
1132
|
-
function buildProgressiveWidgetLines(jobs: AsyncJobState[], theme: Theme, width: number, lockedRows: number, previousKeys: string[]): { lines: string[]; visibleJobKeys: string[] } {
|
|
1263
|
+
function buildProgressiveWidgetLines(jobs: AsyncJobState[], theme: Theme, width: number, lockedRows: number, previousKeys: string[], frame?: number): { lines: string[]; visibleJobKeys: string[] } {
|
|
1133
1264
|
const rowCount = Math.max(1, lockedRows);
|
|
1134
|
-
if (rowCount === 1) return { lines: buildSingleLineWidgetLines(jobs, theme, width), visibleJobKeys: [] };
|
|
1265
|
+
if (rowCount === 1) return { lines: buildSingleLineWidgetLines(jobs, theme, width, frame), visibleJobKeys: [] };
|
|
1135
1266
|
|
|
1136
1267
|
const bodyRows = rowCount - 1;
|
|
1137
1268
|
let visibleJobKeys = selectProgressiveJobKeys(jobs, previousKeys, bodyRows);
|
|
@@ -1147,8 +1278,8 @@ function buildProgressiveWidgetLines(jobs: AsyncJobState[], theme: Theme, width:
|
|
|
1147
1278
|
}
|
|
1148
1279
|
|
|
1149
1280
|
const lines = [
|
|
1150
|
-
progressiveHeaderLine(jobs, theme, width),
|
|
1151
|
-
...visibleJobs.map((job) => progressiveJobLine(job, theme, width)),
|
|
1281
|
+
progressiveHeaderLine(jobs, theme, width, frame),
|
|
1282
|
+
...visibleJobs.map((job) => progressiveJobLine(job, theme, width, frame)),
|
|
1152
1283
|
];
|
|
1153
1284
|
if (hiddenJobs.length > 0 && lines.length < rowCount) lines.push(progressiveHiddenLine(hiddenJobs, theme, width));
|
|
1154
1285
|
while (lines.length < rowCount) lines.push(" ");
|
|
@@ -1159,6 +1290,11 @@ function collapsedWidgetLineBudget(rows: number): number {
|
|
|
1159
1290
|
return Math.max(10, Math.min(14, Math.floor(rows * 0.35)));
|
|
1160
1291
|
}
|
|
1161
1292
|
|
|
1293
|
+
function paddedWidgetLine(line: string, width: number): string {
|
|
1294
|
+
const text = ` ${line} `;
|
|
1295
|
+
return `${text}${" ".repeat(Math.max(0, width - visibleWidth(text)))}`;
|
|
1296
|
+
}
|
|
1297
|
+
|
|
1162
1298
|
function fitWidgetLineBudget(lines: string[], theme: Theme, width: number, expanded: boolean): string[] {
|
|
1163
1299
|
const rows = process.stdout.rows || 30;
|
|
1164
1300
|
const budget = expanded
|
|
@@ -1173,7 +1309,7 @@ function fitWidgetLineBudget(lines: string[], theme: Theme, width: number, expan
|
|
|
1173
1309
|
return [...lines.slice(0, visibleLines), truncLine(theme.fg("dim", hint), width)];
|
|
1174
1310
|
}
|
|
1175
1311
|
|
|
1176
|
-
function fitAdaptiveWidgetLines(jobs: AsyncJobState[], lines: string[], theme: Theme, width: number, expanded: boolean): string[] {
|
|
1312
|
+
function fitAdaptiveWidgetLines(jobs: AsyncJobState[], lines: string[], theme: Theme, width: number, expanded: boolean, frame?: number): string[] {
|
|
1177
1313
|
if (expanded) {
|
|
1178
1314
|
resetWidgetLayoutSession();
|
|
1179
1315
|
return fitWidgetLineBudget(lines, theme, width, true);
|
|
@@ -1185,11 +1321,11 @@ function fitAdaptiveWidgetLines(jobs: AsyncJobState[], lines: string[], theme: T
|
|
|
1185
1321
|
const availableRows = estimateAvailableWidgetRows();
|
|
1186
1322
|
|
|
1187
1323
|
if (hasMatchingSession && widgetLayoutSession?.tier === "single-line") {
|
|
1188
|
-
return buildSingleLineWidgetLines(jobs, theme, width);
|
|
1324
|
+
return buildSingleLineWidgetLines(jobs, theme, width, frame);
|
|
1189
1325
|
}
|
|
1190
1326
|
|
|
1191
1327
|
if (hasMatchingSession && widgetLayoutSession?.tier === "progressive" && widgetLayoutSession.lockedRows !== undefined) {
|
|
1192
|
-
const rendered = buildProgressiveWidgetLines(jobs, theme, width, widgetLayoutSession.lockedRows, widgetLayoutSession.visibleJobKeys);
|
|
1328
|
+
const rendered = buildProgressiveWidgetLines(jobs, theme, width, widgetLayoutSession.lockedRows, widgetLayoutSession.visibleJobKeys, frame);
|
|
1193
1329
|
widgetLayoutSession.visibleJobKeys = rendered.visibleJobKeys;
|
|
1194
1330
|
return rendered.lines;
|
|
1195
1331
|
}
|
|
@@ -1201,39 +1337,48 @@ function fitAdaptiveWidgetLines(jobs: AsyncJobState[], lines: string[], theme: T
|
|
|
1201
1337
|
|
|
1202
1338
|
if (availableRows <= 2) {
|
|
1203
1339
|
widgetLayoutSession = { expanded, rows, columns, tier: "single-line", visibleJobKeys: [] };
|
|
1204
|
-
return buildSingleLineWidgetLines(jobs, theme, width);
|
|
1340
|
+
return buildSingleLineWidgetLines(jobs, theme, width, frame);
|
|
1205
1341
|
}
|
|
1206
1342
|
|
|
1207
1343
|
const lockedRows = Math.min(availableRows, collapsedWidgetLineBudget(rows));
|
|
1208
|
-
const rendered = buildProgressiveWidgetLines(jobs, theme, width, lockedRows, []);
|
|
1344
|
+
const rendered = buildProgressiveWidgetLines(jobs, theme, width, lockedRows, [], frame);
|
|
1209
1345
|
widgetLayoutSession = { expanded, rows, columns, tier: "progressive", lockedRows, visibleJobKeys: rendered.visibleJobKeys };
|
|
1210
1346
|
return rendered.lines;
|
|
1211
1347
|
}
|
|
1212
1348
|
|
|
1213
|
-
function buildWidgetComponent(jobs: AsyncJobState[], expanded: boolean): (
|
|
1214
|
-
return (
|
|
1215
|
-
const
|
|
1216
|
-
|
|
1217
|
-
? buildWidgetLines(jobs, theme, width, true)
|
|
1218
|
-
: jobs.length === 1
|
|
1219
|
-
? compactSingleWidgetLines(jobs[0]!, theme, width)
|
|
1220
|
-
: buildWidgetLines(jobs, theme, width, false);
|
|
1349
|
+
function buildWidgetComponent(jobs: AsyncJobState[], expanded: boolean): (tui: { requestRender(): void }, theme: Theme) => Component & { dispose?(): void } {
|
|
1350
|
+
return (tui, theme) => {
|
|
1351
|
+
const requestRender = () => tui.requestRender();
|
|
1352
|
+
widgetRequestRender = requestRender;
|
|
1221
1353
|
const container = new Container();
|
|
1222
|
-
|
|
1223
|
-
|
|
1354
|
+
container.render = (renderWidth: number): string[] => {
|
|
1355
|
+
const width = getTermWidth();
|
|
1356
|
+
const frame = widgetAnimationFrame();
|
|
1357
|
+
const lines = expanded
|
|
1358
|
+
? buildWidgetLines(jobs, theme, width, true, frame)
|
|
1359
|
+
: jobs.length === 1
|
|
1360
|
+
? compactSingleWidgetLines(jobs[0]!, theme, width, frame)
|
|
1361
|
+
: buildWidgetLines(jobs, theme, width, false, frame);
|
|
1362
|
+
return fitAdaptiveWidgetLines(jobs, lines, theme, width, expanded, frame).map((line) => paddedWidgetLine(line, renderWidth));
|
|
1363
|
+
};
|
|
1364
|
+
const component = container as Component & { dispose?(): void };
|
|
1365
|
+
component.dispose = () => {
|
|
1366
|
+
if (widgetRequestRender === requestRender) widgetRequestRender = undefined;
|
|
1367
|
+
};
|
|
1368
|
+
return component;
|
|
1224
1369
|
};
|
|
1225
1370
|
}
|
|
1226
1371
|
|
|
1227
|
-
export function buildWidgetLines(jobs: AsyncJobState[], theme: Theme, width = getTermWidth(), expanded = false): string[] {
|
|
1372
|
+
export function buildWidgetLines(jobs: AsyncJobState[], theme: Theme, width = getTermWidth(), expanded = false, frame?: number): string[] {
|
|
1228
1373
|
if (jobs.length === 0) return [];
|
|
1229
|
-
if (jobs.length === 1) return buildSingleWidgetLines(jobs[0]!, theme, width, expanded);
|
|
1374
|
+
if (jobs.length === 1) return buildSingleWidgetLines(jobs[0]!, theme, width, expanded, frame);
|
|
1230
1375
|
const running = jobs.filter((job) => job.status === "running");
|
|
1231
1376
|
const queued = jobs.filter((job) => job.status === "queued");
|
|
1232
1377
|
const finished = jobs.filter((job) => job.status !== "running" && job.status !== "queued");
|
|
1233
1378
|
|
|
1234
1379
|
const lines: string[] = [];
|
|
1235
1380
|
const hasActive = running.length > 0 || queued.length > 0;
|
|
1236
|
-
const headerGlyph = running.length > 0 ? runningGlyph(widgetJobsRunningSeed(running)) : hasActive ? "●" : "○";
|
|
1381
|
+
const headerGlyph = running.length > 0 ? runningGlyph(animatedSeed(widgetJobsRunningSeed(running), frame)) : hasActive ? "●" : "○";
|
|
1237
1382
|
lines.push(truncLine(`${theme.fg(hasActive ? "accent" : "dim", headerGlyph)} ${theme.fg(hasActive ? "accent" : "dim", "Async agents")} ${theme.fg("dim", "· background")}`, width));
|
|
1238
1383
|
|
|
1239
1384
|
const items: string[][] = [];
|
|
@@ -1246,7 +1391,7 @@ export function buildWidgetLines(jobs: AsyncJobState[], theme: Theme, width = ge
|
|
|
1246
1391
|
if (slots <= 0) { hiddenRunning++; continue; }
|
|
1247
1392
|
const stats = widgetStats(job, theme);
|
|
1248
1393
|
items.push([
|
|
1249
|
-
`${widgetStatusGlyph(job, theme)} ${themeBold(theme, widgetJobName(job))}${contextModeBadge(theme, job.context)}${stats ? ` ${theme.fg("dim", "·")} ${stats}` : ""}`,
|
|
1394
|
+
`${widgetStatusGlyph(job, theme, frame)} ${themeBold(theme, widgetJobName(job))}${contextModeBadge(theme, job.context)}${stats ? ` ${theme.fg("dim", "·")} ${stats}` : ""}`,
|
|
1250
1395
|
` ${theme.fg("dim", `⎿ ${widgetActivity(job)}`)}`,
|
|
1251
1396
|
...widgetParallelAgentDetails(job, theme, expanded, width),
|
|
1252
1397
|
]);
|
|
@@ -1263,7 +1408,7 @@ export function buildWidgetLines(jobs: AsyncJobState[], theme: Theme, width = ge
|
|
|
1263
1408
|
if (slots <= 0) { hiddenFinished++; continue; }
|
|
1264
1409
|
const stats = widgetStats(job, theme);
|
|
1265
1410
|
items.push([
|
|
1266
|
-
`${widgetStatusGlyph(job, theme)} ${themeBold(theme, widgetJobName(job))}${contextModeBadge(theme, job.context)}${stats ? ` ${theme.fg("dim", "·")} ${stats}` : ""}`,
|
|
1411
|
+
`${widgetStatusGlyph(job, theme, frame)} ${themeBold(theme, widgetJobName(job))}${contextModeBadge(theme, job.context)}${stats ? ` ${theme.fg("dim", "·")} ${stats}` : ""}`,
|
|
1267
1412
|
` ${theme.fg("dim", `⎿ ${widgetActivity(job)}`)}`,
|
|
1268
1413
|
...widgetParallelAgentDetails(job, theme, expanded, width),
|
|
1269
1414
|
]);
|
|
@@ -1300,6 +1445,7 @@ export function buildWidgetLines(jobs: AsyncJobState[], theme: Theme, width = ge
|
|
|
1300
1445
|
export function renderWidget(ctx: ExtensionContext, jobs: AsyncJobState[]): void {
|
|
1301
1446
|
if (jobs.length === 0) {
|
|
1302
1447
|
resetWidgetLayoutSession();
|
|
1448
|
+
widgetRequestRender = undefined;
|
|
1303
1449
|
if (ctx.hasUI) ctx.ui.setWidget(WIDGET_KEY, undefined);
|
|
1304
1450
|
return;
|
|
1305
1451
|
}
|
|
@@ -1310,7 +1456,7 @@ export function renderWidget(ctx: ExtensionContext, jobs: AsyncJobState[]): void
|
|
|
1310
1456
|
function renderSingleCompact(d: Details, r: Details["results"][number], theme: Theme, frame?: number): Component {
|
|
1311
1457
|
const output = r.truncation?.text || getSingleResultOutput(r);
|
|
1312
1458
|
const progress = r.progress || r.progressSummary;
|
|
1313
|
-
const isRunning = r
|
|
1459
|
+
const isRunning = isResultRunning(r);
|
|
1314
1460
|
const contextBadge = contextModeBadge(theme, r.context ?? d.context);
|
|
1315
1461
|
const stats = statJoin(theme, [
|
|
1316
1462
|
r.usage?.turns ? `⟳ ${r.usage.turns}` : "",
|
|
@@ -1349,16 +1495,64 @@ function renderSingleCompact(d: Details, r: Details["results"][number], theme: T
|
|
|
1349
1495
|
return c;
|
|
1350
1496
|
}
|
|
1351
1497
|
|
|
1498
|
+
function workflowRowGlyph(row: WorkflowChatProgressRow, theme: Theme, frame?: number): string {
|
|
1499
|
+
if (row.state === "running") return theme.fg("accent", runningGlyph(frame));
|
|
1500
|
+
if (row.state === "complete") return theme.fg("success", "✓");
|
|
1501
|
+
return theme.fg("error", "✗");
|
|
1502
|
+
}
|
|
1503
|
+
|
|
1504
|
+
function workflowRowStateLabel(row: WorkflowChatProgressRow, theme: Theme): string {
|
|
1505
|
+
const label = (row.state === "complete" ? "complete" : row.state).padEnd(8);
|
|
1506
|
+
if (row.state === "running") return theme.fg("accent", label);
|
|
1507
|
+
if (row.state === "complete") return theme.fg("success", label);
|
|
1508
|
+
return theme.fg("error", label);
|
|
1509
|
+
}
|
|
1510
|
+
|
|
1511
|
+
function workflowOverallState(rows: WorkflowChatProgressRow[], hasTerminalValue: boolean, isError?: boolean): "running" | "complete" | "failed" {
|
|
1512
|
+
if (isError || rows.some((row) => row.state === "failed")) return "failed";
|
|
1513
|
+
if ((rows.length > 0 && rows.every((row) => row.state === "complete")) || hasTerminalValue) return "complete";
|
|
1514
|
+
return "running";
|
|
1515
|
+
}
|
|
1516
|
+
|
|
1517
|
+
function renderWorkflowChatProgress(d: Details, result: AgentToolResult<Details>, theme: Theme, frame?: number): Component {
|
|
1518
|
+
const workflow = d.workflow;
|
|
1519
|
+
const rows = workflow ? buildWorkflowChatProgressRows(workflow.trace) : [];
|
|
1520
|
+
const state = workflowOverallState(rows, workflow?.value !== undefined, result.isError);
|
|
1521
|
+
const glyph = state === "running" ? theme.fg("accent", runningGlyph(frame)) : state === "complete" ? theme.fg("success", "✓") : theme.fg("error", "✗");
|
|
1522
|
+
const width = getTermWidth() - 4;
|
|
1523
|
+
const runId = d.runId ? d.runId.slice(0, 12) : "workflow";
|
|
1524
|
+
const repoLabel = d.chatProgress?.repoLabel ?? (d.chatProgress?.repoRelation === "same" ? "same repo" : "other repo");
|
|
1525
|
+
const phase = rows.find((row) => row.state === "running" && row.phase)?.phase ?? [...rows].reverse().find((row) => row.phase)?.phase;
|
|
1526
|
+
const c = new Container();
|
|
1527
|
+
c.addChild(new Text(truncLine(`${glyph} ${theme.fg("toolTitle", theme.bold("workflow"))} ${runId} ${theme.fg("dim", "·")} ${d.chatProgress?.repoRelation === "same" ? "same repo" : "other repo"} ${theme.fg("dim", "·")} ${state}`, width), 0, 0));
|
|
1528
|
+
c.addChild(new Text(truncLine(theme.fg("dim", ` Repo ${repoLabel}`), width), 0, 0));
|
|
1529
|
+
if (phase) c.addChild(new Text(truncLine(theme.fg("dim", ` Phase ${phase}`), width), 0, 0));
|
|
1530
|
+
if (rows.length === 0) {
|
|
1531
|
+
c.addChild(new Text(truncLine(theme.fg("dim", " ◦ waiting for workflow child launches"), width), 0, 0));
|
|
1532
|
+
return c;
|
|
1533
|
+
}
|
|
1534
|
+
for (const row of rows) {
|
|
1535
|
+
const status = workflowRowStateLabel(row, theme);
|
|
1536
|
+
const label = row.label && row.label !== row.key ? ` ${row.label}` : "";
|
|
1537
|
+
const duration = row.durationMs !== undefined ? ` ${theme.fg("dim", `· ${formatDuration(row.durationMs)}`)}` : "";
|
|
1538
|
+
const run = row.runId ? ` ${theme.fg("dim", `[${row.runId.slice(0, 8)}]`)}` : "";
|
|
1539
|
+
const error = row.error ? ` ${theme.fg("error", `· ${row.error}`)}` : "";
|
|
1540
|
+
c.addChild(new Text(truncLine(` ${workflowRowGlyph(row, theme, frame)} ${status} ${theme.bold(row.key)}${label}${run}${duration}${error}`, width), 0, 0));
|
|
1541
|
+
}
|
|
1542
|
+
if (workflow?.emits.length) c.addChild(new Text(truncLine(theme.fg("dim", ` Emits ${workflow.emits.length}`), width), 0, 0));
|
|
1543
|
+
return c;
|
|
1544
|
+
}
|
|
1545
|
+
|
|
1352
1546
|
function renderMultiCompact(d: Details, theme: Theme, frame?: number): Component {
|
|
1353
|
-
const hasRunning = d
|
|
1354
|
-
|
|
1355
|
-
|| workflowGraphHasStatus(d, ["
|
|
1356
|
-
const stopped = d.results.some((r) => r.stopped
|
|
1547
|
+
const hasRunning = detailsHaveRunningResult(d);
|
|
1548
|
+
const detached = d.results.some((r) => r.detached)
|
|
1549
|
+
|| workflowGraphHasStatus(d, ["detached"]);
|
|
1550
|
+
const stopped = d.results.some((r) => r.stopped)
|
|
1357
1551
|
|| workflowGraphHasStatus(d, ["stopped"]);
|
|
1358
|
-
const failed = d.results.some((r) => !r
|
|
1552
|
+
const failed = d.results.some((r) => !hasTerminalResultFlag(r) && r.exitCode !== 0 && !isResultRunning(r))
|
|
1359
1553
|
|| workflowGraphHasStatus(d, ["failed"]);
|
|
1360
|
-
const paused = d.results.some((r) =>
|
|
1361
|
-
|| workflowGraphHasStatus(d, ["paused"
|
|
1554
|
+
const paused = d.results.some((r) => r.interrupted)
|
|
1555
|
+
|| workflowGraphHasStatus(d, ["paused"]);
|
|
1362
1556
|
let totalSummary = d.progressSummary;
|
|
1363
1557
|
if (!totalSummary) {
|
|
1364
1558
|
let sawProgress = false;
|
|
@@ -1376,15 +1570,16 @@ function renderMultiCompact(d: Details, theme: Theme, frame?: number): Component
|
|
|
1376
1570
|
const multiLabel = buildMultiProgressLabel(d, hasRunning);
|
|
1377
1571
|
const itemTitle = multiLabel.itemTitle;
|
|
1378
1572
|
const stats = statJoin(theme, [multiLabel.headerLabel, formatProgressStats(theme, totalSummary), formatTotalCostStat(d.totalCost)]);
|
|
1379
|
-
const
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1573
|
+
const aggregatePresentation = semanticResultPresentation({
|
|
1574
|
+
running: hasRunning,
|
|
1575
|
+
detached,
|
|
1576
|
+
stopped,
|
|
1577
|
+
interrupted: paused,
|
|
1578
|
+
failed,
|
|
1579
|
+
seed: runningSeed(progressRunningSeed(totalSummary), d.currentStepIndex),
|
|
1580
|
+
frame,
|
|
1581
|
+
});
|
|
1582
|
+
const glyph = theme.fg(aggregatePresentation.tone, aggregatePresentation.glyph);
|
|
1388
1583
|
const contextBadge = contextModeBadge(theme, d.context);
|
|
1389
1584
|
const c = new Container();
|
|
1390
1585
|
const width = getTermWidth() - 4;
|
|
@@ -1421,7 +1616,7 @@ function renderMultiCompact(d: Details, theme: Theme, frame?: number): Component
|
|
|
1421
1616
|
const output = getSingleResultOutput(r);
|
|
1422
1617
|
const progressFromArray = d.progress?.find((p) => p.index === i) || d.progress?.find((p) => p.agent === r.agent && p.status === "running");
|
|
1423
1618
|
const rProg = r.progress || progressFromArray || r.progressSummary;
|
|
1424
|
-
const rRunning = rProg && "status" in rProg && rProg.status
|
|
1619
|
+
const rRunning = rProg && "status" in rProg && isResultRunning(r, rProg.status);
|
|
1425
1620
|
const rPending = rProg && "status" in rProg && rProg.status === "pending";
|
|
1426
1621
|
const stepNumber = r.progress?.index !== undefined ? r.progress.index + 1 : progressFromArray?.index !== undefined ? progressFromArray.index + 1 : i + 1;
|
|
1427
1622
|
const stepStats = formatProgressStats(theme, rProg);
|
|
@@ -1433,9 +1628,10 @@ function renderMultiCompact(d: Details, theme: Theme, frame?: number): Component
|
|
|
1433
1628
|
const line = `${glyph} ${stepLabel}: ${themeBold(theme, agentName)}${contextModeBadge(theme, r.context)}${rowModelDisplay}${stepStats ? ` ${theme.fg("dim", "·")} ${stepStats}` : ""}${pendingLabel}`;
|
|
1434
1629
|
c.addChild(new Text(truncLine(` ${line}`, width), 0, 0));
|
|
1435
1630
|
if (rRunning && rProg && "status" in rProg) {
|
|
1436
|
-
const
|
|
1631
|
+
const liveProgress = rProg as AgentProgress;
|
|
1632
|
+
const activity = compactCurrentActivity(liveProgress);
|
|
1437
1633
|
c.addChild(new Text(truncLine(theme.fg("dim", ` ⎿ ${activity}`), width), 0, 0));
|
|
1438
|
-
for (const nestedLine of formatNestedWidgetLines(r.children, theme, width, false, snapshotNowForProgress(
|
|
1634
|
+
for (const nestedLine of formatNestedWidgetLines(r.children, theme, width, false, snapshotNowForProgress(liveProgress))) {
|
|
1439
1635
|
c.addChild(new Text(truncLine(` ${nestedLine}`, width), 0, 0));
|
|
1440
1636
|
}
|
|
1441
1637
|
c.addChild(new Text(truncLine(theme.fg("accent", ` ${liveDetailHintText()}`), width), 0, 0));
|
|
@@ -1455,6 +1651,45 @@ function renderMultiCompact(d: Details, theme: Theme, frame?: number): Component
|
|
|
1455
1651
|
return c;
|
|
1456
1652
|
}
|
|
1457
1653
|
|
|
1654
|
+
export function renderSubagentSummary(
|
|
1655
|
+
result: AgentToolResult<Details>,
|
|
1656
|
+
options: { isPartial?: boolean },
|
|
1657
|
+
theme: Theme,
|
|
1658
|
+
): Component {
|
|
1659
|
+
const details = result.details;
|
|
1660
|
+
const results = details?.results ?? [];
|
|
1661
|
+
const hasSingleTerminalResult = results.length === 1 && hasTerminalResult(results[0]!);
|
|
1662
|
+
const hasOnlyTerminalResults = results.length > 0 && results.every(hasTerminalResult);
|
|
1663
|
+
const running = !hasSingleTerminalResult && !hasOnlyTerminalResults && (
|
|
1664
|
+
options.isPartial === true
|
|
1665
|
+
|| Boolean(details?.asyncId && details.mode !== "management")
|
|
1666
|
+
|| Boolean(details && detailsHaveRunningResult(details))
|
|
1667
|
+
);
|
|
1668
|
+
const stopped = results.some((entry) => entry.stopped)
|
|
1669
|
+
|| Boolean(details && workflowGraphHasStatus(details, ["stopped"]));
|
|
1670
|
+
const paused = results.some((entry) => entry.interrupted || entry.detached)
|
|
1671
|
+
|| Boolean(details && workflowGraphHasStatus(details, ["paused", "detached"]));
|
|
1672
|
+
const failed = result.isError === true
|
|
1673
|
+
|| results.some((entry) => !hasTerminalResultFlag(entry) && entry.exitCode !== 0 && !isResultRunning(entry))
|
|
1674
|
+
|| Boolean(details && workflowGraphHasStatus(details, ["failed"]));
|
|
1675
|
+
const state = running ? "running" : failed ? "failed" : stopped ? "stopped" : paused ? "paused" : "completed";
|
|
1676
|
+
const glyph = state === "running"
|
|
1677
|
+
? theme.fg("accent", STATIC_RUNNING_GLYPH)
|
|
1678
|
+
: state === "completed"
|
|
1679
|
+
? theme.fg("success", "✓")
|
|
1680
|
+
: state === "failed"
|
|
1681
|
+
? theme.fg("error", "✗")
|
|
1682
|
+
: theme.fg("warning", "■");
|
|
1683
|
+
const label = details?.mode === "single" && results.length === 1
|
|
1684
|
+
? results[0]?.agent || "subagent"
|
|
1685
|
+
: details?.mode || "subagent";
|
|
1686
|
+
return new Text(
|
|
1687
|
+
truncLine(`${glyph} ${theme.fg("toolTitle", theme.bold(label))} ${theme.fg("dim", "·")} ${theme.fg(state === "failed" ? "error" : state === "completed" ? "success" : state === "running" ? "accent" : "warning", state)}`, getTermWidth() - 4),
|
|
1688
|
+
0,
|
|
1689
|
+
0,
|
|
1690
|
+
);
|
|
1691
|
+
}
|
|
1692
|
+
|
|
1458
1693
|
/**
|
|
1459
1694
|
* Render a subagent result
|
|
1460
1695
|
*/
|
|
@@ -1465,6 +1700,7 @@ export function renderSubagentResult(
|
|
|
1465
1700
|
frame?: number,
|
|
1466
1701
|
): Component {
|
|
1467
1702
|
const d = result.details;
|
|
1703
|
+
if (d?.mode === "workflow" && d.chatProgress?.mode === "live-card" && !result.isError && d.workflow?.value === undefined) return renderWorkflowChatProgress(d, result, theme, frame);
|
|
1468
1704
|
if (!d || !d.results.length) {
|
|
1469
1705
|
const t = result.content[0];
|
|
1470
1706
|
const text = t?.type === "text" ? t.text : "(no output)";
|
|
@@ -1490,17 +1726,12 @@ export function renderSubagentResult(
|
|
|
1490
1726
|
|
|
1491
1727
|
if (d.mode === "single" && d.results.length === 1) {
|
|
1492
1728
|
const r = d.results[0];
|
|
1729
|
+
if (!r) return renderMultiCompact(d, theme, frame);
|
|
1493
1730
|
if (!expanded) return renderSingleCompact(d, r, theme, frame);
|
|
1494
|
-
const isRunning = r
|
|
1495
|
-
const icon = isRunning
|
|
1496
|
-
? theme.fg("warning", "running")
|
|
1497
|
-
: r.detached
|
|
1498
|
-
? theme.fg("warning", "detached")
|
|
1499
|
-
: r.exitCode === 0
|
|
1500
|
-
? theme.fg("success", "ok")
|
|
1501
|
-
: theme.fg("error", "failed");
|
|
1731
|
+
const isRunning = isResultRunning(r);
|
|
1502
1732
|
const contextBadge = contextModeBadge(theme, r.context ?? d.context);
|
|
1503
1733
|
const output = r.truncation?.text || getSingleResultOutput(r);
|
|
1734
|
+
const presentation = styledResultPresentation(resultPresentation(r, output, isRunning, undefined, frame), theme);
|
|
1504
1735
|
|
|
1505
1736
|
const progressInfo = isRunning && r.progress
|
|
1506
1737
|
? ` | ${r.progress.toolCount} tools, ${formatTokens(r.progress.tokens)} tok, ${formatDuration(r.progress.durationMs)}`
|
|
@@ -1512,7 +1743,7 @@ export function renderSubagentResult(
|
|
|
1512
1743
|
const fit = (text: string) => expanded ? text : truncLine(text, w);
|
|
1513
1744
|
const toolCallLines = getToolCallLines(r, expanded);
|
|
1514
1745
|
const c = new Container();
|
|
1515
|
-
c.addChild(new Text(fit(`${
|
|
1746
|
+
c.addChild(new Text(fit(`${presentation.glyph} ${theme.fg("toolTitle", theme.bold(r.agent))}${contextBadge}${progressInfo} ${theme.fg("dim", "·")} ${presentation.label}`), 0, 0));
|
|
1516
1747
|
c.addChild(new Spacer(1));
|
|
1517
1748
|
const taskMaxLen = Math.max(20, w - 8);
|
|
1518
1749
|
const taskPreview = expanded || r.task.length <= taskMaxLen
|
|
@@ -1593,31 +1824,30 @@ export function renderSubagentResult(
|
|
|
1593
1824
|
|
|
1594
1825
|
if (!expanded) return renderMultiCompact(d, theme, frame);
|
|
1595
1826
|
|
|
1596
|
-
const hasRunning = d
|
|
1597
|
-
|
|
1598
|
-
|| workflowGraphHasStatus(d, ["
|
|
1599
|
-
const
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1827
|
+
const hasRunning = detailsHaveRunningResult(d);
|
|
1828
|
+
const detached = d.results.some((r) => r.detached)
|
|
1829
|
+
|| workflowGraphHasStatus(d, ["detached"]);
|
|
1830
|
+
const stopped = d.results.some((r) => r.stopped)
|
|
1831
|
+
|| workflowGraphHasStatus(d, ["stopped"]);
|
|
1832
|
+
const failed = d.results.some((r) => !hasTerminalResultFlag(r) && r.exitCode !== 0 && !isResultRunning(r))
|
|
1833
|
+
|| workflowGraphHasStatus(d, ["failed"]);
|
|
1834
|
+
const paused = d.results.some((r) => r.interrupted)
|
|
1835
|
+
|| workflowGraphHasStatus(d, ["paused"]);
|
|
1836
|
+
const completedWithoutOutput = d.results.some((r) =>
|
|
1837
|
+
!hasTerminalResultFlag(r)
|
|
1838
|
+
&& r.exitCode === 0
|
|
1839
|
+
&& !isResultRunning(r)
|
|
1603
1840
|
&& hasEmptyTextOutputWithoutOutputTarget(r.task, getSingleResultOutput(r)),
|
|
1604
1841
|
);
|
|
1605
|
-
const
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
: hasWorkflowStop
|
|
1615
|
-
? theme.fg("warning", "stopped")
|
|
1616
|
-
: hasWorkflowPause
|
|
1617
|
-
? theme.fg("warning", "paused")
|
|
1618
|
-
: ok === d.results.length
|
|
1619
|
-
? theme.fg("success", "ok")
|
|
1620
|
-
: theme.fg("error", "failed");
|
|
1842
|
+
const presentation = styledResultPresentation(semanticResultPresentation({
|
|
1843
|
+
running: hasRunning,
|
|
1844
|
+
detached,
|
|
1845
|
+
stopped,
|
|
1846
|
+
interrupted: paused,
|
|
1847
|
+
failed,
|
|
1848
|
+
completedWithoutOutput,
|
|
1849
|
+
frame,
|
|
1850
|
+
}), theme);
|
|
1621
1851
|
|
|
1622
1852
|
const totalSummary =
|
|
1623
1853
|
d.progressSummary ||
|
|
@@ -1654,22 +1884,14 @@ export function renderSubagentResult(
|
|
|
1654
1884
|
? d.chainAgents
|
|
1655
1885
|
.map((agent, i) => {
|
|
1656
1886
|
const result = d.results[i];
|
|
1657
|
-
const isFailed = result && result.exitCode !== 0 && result.progress?.status !== "running";
|
|
1658
|
-
const isComplete = result && result.exitCode === 0 && result.progress?.status !== "running";
|
|
1659
|
-
const isEmptyWithoutTarget = Boolean(result)
|
|
1660
|
-
&& Boolean(isComplete)
|
|
1661
|
-
&& hasEmptyTextOutputWithoutOutputTarget(result.task, getSingleResultOutput(result));
|
|
1662
1887
|
const isCurrent = i === (d.currentStepIndex ?? d.results.length);
|
|
1663
|
-
const
|
|
1664
|
-
?
|
|
1665
|
-
:
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
? theme.fg("warning", "running")
|
|
1671
|
-
: theme.fg("dim", "pending");
|
|
1672
|
-
return `${stepIcon} ${agent}${contextModeBadge(theme, result?.context)}`;
|
|
1888
|
+
const stepPresentation = result
|
|
1889
|
+
? styledResultPresentation(resultPresentation(result, getSingleResultOutput(result), isCurrent && hasRunning && !hasTerminalResultFlag(result)), theme)
|
|
1890
|
+
: undefined;
|
|
1891
|
+
const stepStatus = stepPresentation
|
|
1892
|
+
? `${stepPresentation.glyph} ${stepPresentation.label}`
|
|
1893
|
+
: theme.fg("dim", "◦ pending");
|
|
1894
|
+
return `${stepStatus} ${agent}${contextModeBadge(theme, result?.context)}`;
|
|
1673
1895
|
})
|
|
1674
1896
|
.join(theme.fg("dim", " → "))
|
|
1675
1897
|
: null;
|
|
@@ -1679,7 +1901,7 @@ export function renderSubagentResult(
|
|
|
1679
1901
|
const c = new Container();
|
|
1680
1902
|
c.addChild(
|
|
1681
1903
|
new Text(
|
|
1682
|
-
fit(`${
|
|
1904
|
+
fit(`${presentation.glyph} ${theme.fg("toolTitle", theme.bold(modeLabel))}${contextBadge} · ${multiLabel.headerLabel}${summaryStr} ${theme.fg("dim", "·")} ${presentation.label}`),
|
|
1683
1905
|
0,
|
|
1684
1906
|
0,
|
|
1685
1907
|
),
|
|
@@ -1726,24 +1948,18 @@ export function renderSubagentResult(
|
|
|
1726
1948
|
const progressFromArray = d.progress?.find((p) => p.index === i)
|
|
1727
1949
|
|| d.progress?.find((p) => p.agent === r.agent && p.status === "running");
|
|
1728
1950
|
const rProg = r.progress || progressFromArray || r.progressSummary;
|
|
1729
|
-
const rRunning = rProg?.status
|
|
1951
|
+
const rRunning = isResultRunning(r, rProg?.status);
|
|
1730
1952
|
const stepNumber = typeof rProg?.index === "number" ? rProg.index + 1 : i + 1;
|
|
1731
1953
|
|
|
1732
1954
|
const resultOutput = getSingleResultOutput(r);
|
|
1733
|
-
const
|
|
1734
|
-
? theme.fg("warning", "running")
|
|
1735
|
-
: r.exitCode !== 0
|
|
1736
|
-
? theme.fg("error", "failed")
|
|
1737
|
-
: hasEmptyTextOutputWithoutOutputTarget(r.task, resultOutput)
|
|
1738
|
-
? theme.fg("warning", "warning")
|
|
1739
|
-
: theme.fg("success", "done");
|
|
1955
|
+
const rowPresentation = styledResultPresentation(resultPresentation(r, resultOutput, rRunning, progressRunningSeed(rProg), frame), theme);
|
|
1740
1956
|
const stats = rProg ? ` | ${rProg.toolCount} tools, ${formatDuration(rProg.durationMs)}` : "";
|
|
1741
|
-
const modelDisplay = modelThinkingBadge(theme, r.model);
|
|
1957
|
+
const modelDisplay = modelThinkingBadge(theme, r.model ?? rProg?.model, r.thinking ?? rProg?.thinking);
|
|
1742
1958
|
const stepLabel = entry.rowLabel ?? resultRowLabel(multiLabel, i, stepNumber);
|
|
1743
1959
|
const contextBadge = contextModeBadge(theme, r.context);
|
|
1744
1960
|
const stepHeader = rRunning
|
|
1745
|
-
? `${
|
|
1746
|
-
: `${
|
|
1961
|
+
? `${rowPresentation.glyph} ${stepLabel}: ${theme.bold(theme.fg("warning", r.agent))}${contextBadge}${modelDisplay}${stats} ${theme.fg("dim", "·")} ${rowPresentation.label}`
|
|
1962
|
+
: `${rowPresentation.glyph} ${stepLabel}: ${theme.bold(r.agent)}${contextBadge}${modelDisplay}${stats} ${theme.fg("dim", "·")} ${rowPresentation.label}`;
|
|
1747
1963
|
const toolCallLines = getToolCallLines(r, expanded);
|
|
1748
1964
|
c.addChild(new Text(fit(stepHeader), 0, 0));
|
|
1749
1965
|
|