pi-subagents 0.59.0 → 0.60.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 +37 -0
- package/docs/tool-reference.md +4 -1
- package/docs/workflows.md +2 -2
- package/package.json +1 -1
- package/skills/council-mode/SKILL.md +48 -243
- package/skills/council-mode/references/pass-contracts.md +150 -0
- package/skills/pi-subagents/SKILL.md +87 -37
- package/skills/pi-subagents/references/constraints-and-recipes.md +29 -233
- package/skills/pi-subagents/references/execution-controls.md +47 -6
- package/skills/pi-subagents/references/management-authoring-rpc.md +2 -2
- package/skills/pi-subagents/references/multi-lane-orchestration.md +13 -1
- package/skills/pi-subagents/references/prompting-and-roles.md +34 -27
- package/skills/pi-subagents/references/review-and-validation.md +73 -0
- package/src/agents/agent-management.ts +154 -25
- package/src/api/shared-types.ts +2 -0
- package/src/extension/public-execution.ts +1 -0
- package/src/extension/schemas.ts +1 -0
- package/src/extension/tool-description.ts +8 -4
- package/src/runs/background/async-execution.ts +2 -2
- package/src/runs/background/async-job-tracker.ts +3 -0
- package/src/runs/background/async-status.ts +45 -2
- package/src/runs/background/control-channel.ts +3 -2
- package/src/runs/background/run-status.ts +13 -2
- package/src/runs/background/subagent-runner.ts +5 -1
- package/src/runs/background/subagent-wait.ts +10 -2
- package/src/runs/background/wait-completions.ts +3 -0
- package/src/runs/foreground/execution.ts +11 -2
- package/src/runs/foreground/subagent-executor.ts +98 -1
- package/src/runs/shared/async-status-projection.ts +138 -4
- package/src/runs/shared/background-process-options.ts +9 -0
- package/src/runs/shared/mcp-direct-tool-grant.ts +2 -5
- package/src/runs/shared/mutation-evidence.ts +52 -3
- package/src/runs/shared/pi-args.ts +47 -1
- package/src/runs/shared/single-output.ts +45 -18
- package/src/runs/shared/subagent-prompt-runtime.ts +20 -2
- package/src/runs/shared/workflow-graph.ts +15 -0
- package/src/shared/types.ts +34 -1
- package/src/tui/fleet-status.ts +11 -3
- package/src/tui/render-helpers.ts +31 -0
- package/src/tui/render.ts +597 -112
- package/src/watchdog/change-signature.ts +40 -1
- package/src/workflows/host-command.ts +6 -1
- package/src/workflows/scripted-workflow.ts +53 -2
package/src/tui/render.ts
CHANGED
|
@@ -31,11 +31,13 @@ import { flatToLogicalStepIndex } from "../runs/background/parallel-groups.ts";
|
|
|
31
31
|
import { formatNestedAggregate } from "../runs/shared/nested-render.ts";
|
|
32
32
|
import { aggregateStepStatus, formatActivityLabel, formatAgentRunningLabel, formatParallelOutcome } from "../shared/status-format.ts";
|
|
33
33
|
import { contextModeBadge, contextModePrefix } from "../runs/shared/context-mode.ts";
|
|
34
|
+
import { shouldSuppressSingleStep, stripRepeatedAgentPrefix, withDuplicateLabelDiscriminators } from "./render-helpers.ts";
|
|
34
35
|
import { buildWorkflowChatProgressRows, type WorkflowChatProgressRow } from "../workflows/chat-progress.ts";
|
|
35
36
|
import { formatWorkflowPreflight, formatWorkflowPreflightPlanSummary, formatWorkflowPreflightWarningSummary, formatWorkflowPreflightWarnings } from "../workflows/workflow-preflight.ts";
|
|
36
37
|
import { encodeAsyncStatusSnapshotWidget } from "../runs/background/async-status-snapshot.ts";
|
|
37
38
|
import { projectAsyncWorkflowRows, type AsyncStatusWorkflowRow } from "../runs/shared/async-status-projection.ts";
|
|
38
39
|
import { hostStepReportName, hostStepVerdictLabel } from "../runs/shared/host-step-status.ts";
|
|
40
|
+
import { workflowGraphStageNodes } from "../runs/shared/workflow-graph.ts";
|
|
39
41
|
|
|
40
42
|
type Theme = ExtensionContext["ui"]["theme"];
|
|
41
43
|
|
|
@@ -192,7 +194,10 @@ function runningSeed(...values: Array<number | undefined>): number | undefined {
|
|
|
192
194
|
|
|
193
195
|
function runningGlyph(seed?: number): string {
|
|
194
196
|
if (seed === undefined) return STATIC_RUNNING_GLYPH;
|
|
195
|
-
|
|
197
|
+
// Free-running wall-clock spinner: advance ~8 fps regardless of model
|
|
198
|
+
// activity; keep the seed so parallel rows stay phase-offset.
|
|
199
|
+
const clock = Math.floor(Date.now() / 125);
|
|
200
|
+
return RUNNING_FRAMES[Math.abs(seed + clock) % RUNNING_FRAMES.length]!;
|
|
196
201
|
}
|
|
197
202
|
|
|
198
203
|
function animatedSeed(seed: number | undefined, frame: number | undefined): number | undefined {
|
|
@@ -259,6 +264,7 @@ const noisyStatusPatterns = [
|
|
|
259
264
|
/^(?:checking|fetching|reading|inspecting|verifying|collecting|confirming|polling)\b/i,
|
|
260
265
|
/^(?:async\s+subagent\s+)?[\w.-]+\s*·\s*(?:step|agent)\s+\d+\/\d+\s*·/i,
|
|
261
266
|
/^(?:Step|Agent)\s+\d+\/\d+:\s+[\w.-]+\s*·\s*(?:running|queued|pending|complete|completed)\b/i,
|
|
267
|
+
/^(?:async\s+subagent\s+)?[\w.-]+(?:\s+\[(?:fresh|fork|mixed)\])?\s*·\s*(?:running|queued|pending|complete|completed|done)\b/i,
|
|
262
268
|
/^Press\s+\S+\s+for\s+live\s+detail$/i,
|
|
263
269
|
/^output:\s+.+\/async-subagent-runs\//i,
|
|
264
270
|
];
|
|
@@ -309,9 +315,129 @@ function boundedLaneValue(value: string | undefined, maxChars = LANE_VALUE_MAX_C
|
|
|
309
315
|
return previewDisplayText(oneLine(value), maxChars);
|
|
310
316
|
}
|
|
311
317
|
|
|
318
|
+
function workflowNodeStepStatus(status: WorkflowNodeStatus): AsyncJobStep["status"] {
|
|
319
|
+
switch (status) {
|
|
320
|
+
case "completed":
|
|
321
|
+
return "completed";
|
|
322
|
+
case "detached":
|
|
323
|
+
return "paused";
|
|
324
|
+
default:
|
|
325
|
+
return status;
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
function workflowNodeStatusLabel(status: WorkflowNodeStatus): string {
|
|
330
|
+
switch (status) {
|
|
331
|
+
case "completed":
|
|
332
|
+
return "complete";
|
|
333
|
+
case "detached":
|
|
334
|
+
return "paused";
|
|
335
|
+
default:
|
|
336
|
+
return status;
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
function workflowStepPriority(step: AsyncJobStep, currentNodeId?: string): number {
|
|
341
|
+
const isCurrent = step.workflowKey === currentNodeId;
|
|
342
|
+
if (step.status === "running" || (isCurrent && step.status !== "complete" && step.status !== "completed")) return 0;
|
|
343
|
+
const gate = laneGate(step);
|
|
344
|
+
if (
|
|
345
|
+
step.status === "failed"
|
|
346
|
+
|| step.status === "partial"
|
|
347
|
+
|| step.status === "paused"
|
|
348
|
+
|| step.status === "stopped"
|
|
349
|
+
|| step.status === "rejected"
|
|
350
|
+
|| step.toolBudgetBlocked === true
|
|
351
|
+
|| step.turnBudgetExceeded === true
|
|
352
|
+
|| step.activityState === "needs_attention"
|
|
353
|
+
|| step.watchdog?.phase === "stale"
|
|
354
|
+
|| gate !== undefined
|
|
355
|
+
) return 1;
|
|
356
|
+
if (step.status === "pending") return 2;
|
|
357
|
+
return 3;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
/** Merge materialized child rows with the planned workflow stages for rendering. */
|
|
361
|
+
function workflowWidgetSteps(job: AsyncJobState): AsyncJobStep[] {
|
|
362
|
+
const planned = job.mode === "workflow" ? workflowGraphStageNodes(job.workflowGraph) : [];
|
|
363
|
+
const loaded = job.steps ?? [];
|
|
364
|
+
if (planned.length === 0) return loaded;
|
|
365
|
+
|
|
366
|
+
const loadedIndexesByKey = new Map<string, number[]>();
|
|
367
|
+
for (const [index, step] of loaded.entries()) {
|
|
368
|
+
if (!step.workflowKey) continue;
|
|
369
|
+
const indexes = loadedIndexesByKey.get(step.workflowKey) ?? [];
|
|
370
|
+
indexes.push(index);
|
|
371
|
+
loadedIndexesByKey.set(step.workflowKey, indexes);
|
|
372
|
+
}
|
|
373
|
+
const consumed = new Set<number>();
|
|
374
|
+
const entries: Array<{ step: AsyncJobStep; order: number }> = [];
|
|
375
|
+
for (const [order, node] of planned.entries()) {
|
|
376
|
+
const loadedIndex = loadedIndexesByKey.get(node.id)?.find((index) => !consumed.has(index));
|
|
377
|
+
if (loadedIndex !== undefined) {
|
|
378
|
+
consumed.add(loadedIndex);
|
|
379
|
+
const loadedStep = loaded[loadedIndex]!;
|
|
380
|
+
entries.push({
|
|
381
|
+
step: {
|
|
382
|
+
...loadedStep,
|
|
383
|
+
index: node.flatIndex ?? loadedStep.index ?? order,
|
|
384
|
+
agent: loadedStep.agent || node.agent || job.agents?.[0] || "workflow",
|
|
385
|
+
phase: loadedStep.phase ?? node.phase,
|
|
386
|
+
label: loadedStep.label ?? node.label,
|
|
387
|
+
workflowKey: loadedStep.workflowKey ?? node.id,
|
|
388
|
+
...(loadedStep.outputName === undefined && node.outputName !== undefined ? { outputName: node.outputName } : {}),
|
|
389
|
+
...(loadedStep.structured === undefined && node.structured !== undefined ? { structured: node.structured } : {}),
|
|
390
|
+
...(loadedStep.error === undefined && node.error !== undefined ? { error: node.error } : {}),
|
|
391
|
+
},
|
|
392
|
+
order,
|
|
393
|
+
});
|
|
394
|
+
continue;
|
|
395
|
+
}
|
|
396
|
+
entries.push({
|
|
397
|
+
step: {
|
|
398
|
+
index: node.flatIndex ?? order,
|
|
399
|
+
agent: node.agent ?? job.agents?.[0] ?? "workflow",
|
|
400
|
+
status: workflowNodeStepStatus(node.status),
|
|
401
|
+
workflowKey: node.id,
|
|
402
|
+
label: node.label,
|
|
403
|
+
...(node.phase ? { phase: node.phase } : {}),
|
|
404
|
+
...(node.outputName ? { outputName: node.outputName } : {}),
|
|
405
|
+
...(node.structured !== undefined ? { structured: node.structured } : {}),
|
|
406
|
+
...(node.error ? { error: node.error } : {}),
|
|
407
|
+
},
|
|
408
|
+
order,
|
|
409
|
+
});
|
|
410
|
+
}
|
|
411
|
+
for (const [index, step] of loaded.entries()) {
|
|
412
|
+
if (!consumed.has(index)) entries.push({ step, order: planned.length + index });
|
|
413
|
+
}
|
|
414
|
+
entries.sort((left, right) => workflowStepPriority(left.step, job.workflowGraph?.currentNodeId) - workflowStepPriority(right.step, job.workflowGraph?.currentNodeId) || left.order - right.order);
|
|
415
|
+
return entries.map(({ step }) => step);
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
function workflowStageProgress(job: AsyncJobState): { total: number; current?: number } | undefined {
|
|
419
|
+
if (job.mode !== "workflow") return undefined;
|
|
420
|
+
const stages = workflowGraphStageNodes(job.workflowGraph);
|
|
421
|
+
if (stages.length === 0) return undefined;
|
|
422
|
+
const currentId = job.workflowGraph?.currentNodeId;
|
|
423
|
+
const currentIndex = currentId ? stages.findIndex((stage) => stage.id === currentId) : -1;
|
|
424
|
+
if (currentIndex >= 0 && stages[currentIndex]?.status !== "completed") return { total: stages.length, current: currentIndex };
|
|
425
|
+
const runningIndex = stages.findIndex((stage) => stage.status === "running");
|
|
426
|
+
if (runningIndex >= 0) return { total: stages.length, current: runningIndex };
|
|
427
|
+
const indexedStage = job.currentStep !== undefined && job.currentStep >= 0 ? stages[job.currentStep] : undefined;
|
|
428
|
+
if (indexedStage && indexedStage.status !== "completed") return { total: stages.length, current: job.currentStep };
|
|
429
|
+
if (stages.every((stage) => stage.status === "completed")) return { total: stages.length, current: stages.length - 1 };
|
|
430
|
+
return { total: stages.length };
|
|
431
|
+
}
|
|
432
|
+
|
|
312
433
|
function laneStepForJob(job: AsyncJobState): AsyncJobStep | undefined {
|
|
313
|
-
const steps = job
|
|
434
|
+
const steps = workflowWidgetSteps(job);
|
|
314
435
|
if (steps.length === 0) return undefined;
|
|
436
|
+
const graphCurrent = job.workflowGraph?.currentNodeId;
|
|
437
|
+
if (graphCurrent) {
|
|
438
|
+
const current = steps.find((step) => step.workflowKey === graphCurrent);
|
|
439
|
+
if (current) return current;
|
|
440
|
+
}
|
|
315
441
|
if (job.currentStep !== undefined) {
|
|
316
442
|
const current = steps[job.currentStep];
|
|
317
443
|
if (current && (current.index === undefined || current.index === job.currentStep)) return current;
|
|
@@ -454,7 +580,7 @@ function workflowPreflightLines(job: AsyncJobState, expanded = false): string[]
|
|
|
454
580
|
}
|
|
455
581
|
|
|
456
582
|
function workflowLabelForResult(details: Details, resultIndex: number): string | undefined {
|
|
457
|
-
const flatIndex = details
|
|
583
|
+
const flatIndex = foregroundResultIndex(details, resultIndex);
|
|
458
584
|
const visit = (nodes: NonNullable<Details["workflowGraph"]>["nodes"]): string | undefined => {
|
|
459
585
|
for (const node of nodes) {
|
|
460
586
|
if (node.flatIndex === flatIndex && node.label.trim()) return node.label;
|
|
@@ -466,6 +592,52 @@ function workflowLabelForResult(details: Details, resultIndex: number): string |
|
|
|
466
592
|
return details.workflowGraph ? visit(details.workflowGraph.nodes) : undefined;
|
|
467
593
|
}
|
|
468
594
|
|
|
595
|
+
function foregroundProgressForResult(details: Details, resultIndex: number): AgentProgress | undefined {
|
|
596
|
+
const result = details.results[resultIndex];
|
|
597
|
+
if (result?.progress) return result.progress;
|
|
598
|
+
const index = result?.index ?? resultIndex;
|
|
599
|
+
const indexed = details.progress?.find((progress) => progress.index === index);
|
|
600
|
+
if (indexed) return indexed;
|
|
601
|
+
return result?.agent
|
|
602
|
+
? details.progress?.find((progress) => progress.agent === result.agent && progress.status === "running")
|
|
603
|
+
: undefined;
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
function foregroundResultIndex(details: Details, resultIndex: number): number {
|
|
607
|
+
const result = details.results[resultIndex];
|
|
608
|
+
if (typeof result?.index === "number") return result.index;
|
|
609
|
+
return foregroundProgressForResult(details, resultIndex)?.index ?? resultIndex;
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
function foregroundResultDisplayName(
|
|
613
|
+
details: Details,
|
|
614
|
+
resultIndex: number,
|
|
615
|
+
result: Details["results"][number] | undefined,
|
|
616
|
+
fallback: string,
|
|
617
|
+
): string {
|
|
618
|
+
const progress = foregroundProgressForResult(details, resultIndex);
|
|
619
|
+
const agent = normalizedParallelDisplayText(result?.agent) ?? normalizedParallelDisplayText(progress?.agent);
|
|
620
|
+
const workflowLabel = normalizedParallelDisplayText(workflowLabelForResult(details, resultIndex));
|
|
621
|
+
if (workflowLabel) return compactTaskText(undefined, workflowLabel) ?? workflowLabel;
|
|
622
|
+
|
|
623
|
+
const sessionName = normalizedParallelDisplayText(result?.sessionName) ?? normalizedParallelDisplayText(progress?.sessionName);
|
|
624
|
+
if (sessionName) {
|
|
625
|
+
const sessionTask = stripRepeatedAgentPrefix(sessionName, agent);
|
|
626
|
+
if (sessionTask && sessionTask !== PROMPT_REDACTED && sessionTask.toLowerCase() !== agent?.toLowerCase()) {
|
|
627
|
+
return compactTaskText(sessionTask) ?? sessionTask;
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
return compactTaskText(result?.task) ?? compactTaskText(progress?.task) ?? agent ?? fallback;
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
function foregroundSingleDisplayName(result: Details["results"][number] | undefined): string {
|
|
635
|
+
return normalizedParallelDisplayText(result?.agent)
|
|
636
|
+
?? normalizedParallelDisplayText(result?.sessionName)
|
|
637
|
+
?? compactTaskText(result?.task)
|
|
638
|
+
?? "subagent";
|
|
639
|
+
}
|
|
640
|
+
|
|
469
641
|
function hasLiveOutputSignal(line: string): boolean {
|
|
470
642
|
const clean = oneLine(line);
|
|
471
643
|
return liveOutputWordSignalPattern.test(clean) || liveOutputCodeSignalPattern.test(clean);
|
|
@@ -779,6 +951,14 @@ function widgetStepRenderKey(step: AsyncJobStep, index: number, expanded = false
|
|
|
779
951
|
step.review?.status,
|
|
780
952
|
step.toolBudgetBlocked,
|
|
781
953
|
step.turnBudgetExceeded,
|
|
954
|
+
step.timedOut,
|
|
955
|
+
step.stopped,
|
|
956
|
+
step.execution?.status,
|
|
957
|
+
step.execution?.error,
|
|
958
|
+
step.execution?.timedOut,
|
|
959
|
+
step.execution?.interrupted,
|
|
960
|
+
step.execution?.stopped,
|
|
961
|
+
step.execution?.detached,
|
|
782
962
|
step.watchdog?.phase,
|
|
783
963
|
step.error,
|
|
784
964
|
expanded ? expandedStepActivityRenderKey(step) : undefined,
|
|
@@ -844,6 +1024,10 @@ export function widgetRenderKey(job: AsyncJobState, expanded = false): string {
|
|
|
844
1024
|
chainStepCount: job.chainStepCount,
|
|
845
1025
|
parallelGroups: job.parallelGroups,
|
|
846
1026
|
workflowHostSteps: projectAsyncWorkflowRows([], job.hostSteps).map(hostStepRenderKey),
|
|
1027
|
+
workflowGraph: job.mode === "workflow" && job.workflowGraph ? {
|
|
1028
|
+
currentNodeId: job.workflowGraph.currentNodeId,
|
|
1029
|
+
stages: workflowGraphStageNodes(job.workflowGraph).map((node) => [node.id, node.status, node.agent, node.phase, node.label, node.flatIndex, node.outputName, node.structured, node.error]),
|
|
1030
|
+
} : undefined,
|
|
847
1031
|
preflight: expanded ? job.preflight : job.preflight ? formatWorkflowPreflightPlanSummary(job.preflight) : undefined,
|
|
848
1032
|
preflightWarnings: expanded ? job.workflow?.preflightWarnings : job.workflow?.preflightWarnings?.length || undefined,
|
|
849
1033
|
steps: job.steps?.map((step, index) => widgetStepRenderKey(step, index, expanded)),
|
|
@@ -855,6 +1039,8 @@ export function widgetRenderKey(job: AsyncJobState, expanded = false): string {
|
|
|
855
1039
|
activeParallelGroup: job.activeParallelGroup,
|
|
856
1040
|
startedAt: job.startedAt,
|
|
857
1041
|
updatedAt: job.updatedAt,
|
|
1042
|
+
timedOut: job.timedOut,
|
|
1043
|
+
stopped: job.stopped,
|
|
858
1044
|
totalTokens: job.totalTokens,
|
|
859
1045
|
});
|
|
860
1046
|
}
|
|
@@ -874,6 +1060,65 @@ function widgetJobName(job: AsyncJobState): string {
|
|
|
874
1060
|
return job.mode ?? "subagent";
|
|
875
1061
|
}
|
|
876
1062
|
|
|
1063
|
+
function isSingleChildAsyncJob(job: AsyncJobState): boolean {
|
|
1064
|
+
return job.mode === "single"
|
|
1065
|
+
&& job.steps?.length === 1
|
|
1066
|
+
&& shouldSuppressSingleStep(job.chainStepCount, job.stepsTotal);
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1069
|
+
function isCompletedWidgetStepStatus(status: AsyncJobStep["status"]): boolean {
|
|
1070
|
+
return status === "complete" || status === "completed";
|
|
1071
|
+
}
|
|
1072
|
+
|
|
1073
|
+
function singleChildAgentName(job: AsyncJobState, step: AsyncJobStep): string {
|
|
1074
|
+
return job.agents?.length === 1 ? job.agents[0]! : step.agent || widgetJobName(job);
|
|
1075
|
+
}
|
|
1076
|
+
|
|
1077
|
+
function hasSingleChildDetailEvidence(job: AsyncJobState, step: AsyncJobStep): boolean {
|
|
1078
|
+
return Boolean(
|
|
1079
|
+
step.error?.trim()
|
|
1080
|
+
|| step.execution?.error?.trim()
|
|
1081
|
+
|| step.phase?.trim()
|
|
1082
|
+
|| step.label?.trim()
|
|
1083
|
+
|| step.workflowKey?.trim()
|
|
1084
|
+
|| step.outputName?.trim()
|
|
1085
|
+
|| step.lane
|
|
1086
|
+
|| job.workflowKey?.trim()
|
|
1087
|
+
|| job.lane
|
|
1088
|
+
|| laneTraceForJob(job)?.phase?.trim()
|
|
1089
|
+
|| laneTraceForJob(job)?.label?.trim()
|
|
1090
|
+
|| step.timedOut
|
|
1091
|
+
|| step.stopped
|
|
1092
|
+
|| step.execution?.timedOut
|
|
1093
|
+
|| step.execution?.interrupted
|
|
1094
|
+
|| step.execution?.stopped
|
|
1095
|
+
|| step.execution?.detached
|
|
1096
|
+
|| job.timedOut
|
|
1097
|
+
|| job.stopped
|
|
1098
|
+
|| ["failed", "partial", "paused", "stopped", "detached"].includes(step.execution?.status ?? "")
|
|
1099
|
+
|| step.acceptance?.status === "rejected"
|
|
1100
|
+
|| step.acceptance?.reviewResult?.status === "blockers"
|
|
1101
|
+
|| step.review?.status === "blockers",
|
|
1102
|
+
);
|
|
1103
|
+
}
|
|
1104
|
+
|
|
1105
|
+
function shouldCollapseSingleChildDetails(job: AsyncJobState, step: AsyncJobStep): boolean {
|
|
1106
|
+
if (!isSingleChildAsyncJob(job) || hasSingleChildDetailEvidence(job, step)) return false;
|
|
1107
|
+
if (job.status === "running") return step.status === "running";
|
|
1108
|
+
if (job.status === "complete") return isCompletedWidgetStepStatus(step.status);
|
|
1109
|
+
return false;
|
|
1110
|
+
}
|
|
1111
|
+
|
|
1112
|
+
function singleChildTask(job: AsyncJobState, step: AsyncJobStep): string | undefined {
|
|
1113
|
+
const task = compactTaskText(step.description, step.label);
|
|
1114
|
+
if (task) return task;
|
|
1115
|
+
const sessionName = step.sessionName?.trim();
|
|
1116
|
+
if (!sessionName) return undefined;
|
|
1117
|
+
const agentName = singleChildAgentName(job, step);
|
|
1118
|
+
const sessionTask = stripRepeatedAgentPrefix(sessionName, agentName);
|
|
1119
|
+
return sessionTask === agentName ? undefined : compactTaskText(sessionTask);
|
|
1120
|
+
}
|
|
1121
|
+
|
|
877
1122
|
function widgetActivity(job: AsyncJobState): string {
|
|
878
1123
|
const facts: string[] = [];
|
|
879
1124
|
if (job.currentTool && job.currentToolStartedAt !== undefined && job.updatedAt !== undefined) facts.push(`${job.currentTool} ${formatDuration(Math.max(0, job.updatedAt - job.currentToolStartedAt))}`);
|
|
@@ -982,8 +1227,7 @@ function widgetChainDetails(job: AsyncJobState, theme: Theme, expanded = false,
|
|
|
982
1227
|
for (const span of buildAsyncChainStepSpans(total, job.steps.length, job.parallelGroups)) {
|
|
983
1228
|
const steps = job.steps.slice(span.start, span.start + span.count);
|
|
984
1229
|
if (span.isParallel) {
|
|
985
|
-
|
|
986
|
-
lines.push(` ${widgetStepGlyph(status, theme, widgetStepsRunningSeed(steps), frame)} Step ${span.stepIndex + 1}/${total}: ${themeBold(theme, "parallel group")} ${theme.fg("dim", "·")} ${theme.fg("dim", formatParallelOutcome(steps, span.count))}`);
|
|
1230
|
+
lines.push(...parallelWidgetGroupDetails(job, theme, { steps, total: span.count, stepIndex: span.stepIndex, chainTotal: total }, expanded, width, frame, false));
|
|
987
1231
|
continue;
|
|
988
1232
|
}
|
|
989
1233
|
const step = steps[0];
|
|
@@ -1000,6 +1244,8 @@ function widgetParallelAgentDetails(job: AsyncJobState, theme: Theme, expanded =
|
|
|
1000
1244
|
if (!job.steps?.length) return [];
|
|
1001
1245
|
if (job.mode !== "parallel" && job.mode !== "chain") return [];
|
|
1002
1246
|
if (job.mode === "chain" && !job.activeParallelGroup && job.parallelGroups?.length) return widgetChainDetails(job, theme, expanded, width, frame);
|
|
1247
|
+
const group = activeParallelWidgetGroup(job);
|
|
1248
|
+
if (group) return parallelWidgetGroupDetails(job, theme, group, expanded, width, frame, Boolean(job.activeParallelGroup));
|
|
1003
1249
|
const total = job.stepsTotal ?? job.steps.length;
|
|
1004
1250
|
const lines: string[] = [];
|
|
1005
1251
|
for (const [index, step] of job.steps.entries()) {
|
|
@@ -1090,6 +1336,127 @@ function buildAsyncChainStepSpans(total: number, stepCount: number, parallelGrou
|
|
|
1090
1336
|
return spans;
|
|
1091
1337
|
}
|
|
1092
1338
|
|
|
1339
|
+
interface ParallelWidgetGroup {
|
|
1340
|
+
steps: AsyncJobStep[];
|
|
1341
|
+
total: number;
|
|
1342
|
+
stepIndex?: number;
|
|
1343
|
+
chainTotal?: number;
|
|
1344
|
+
}
|
|
1345
|
+
|
|
1346
|
+
interface ParallelWidgetStepRow {
|
|
1347
|
+
step: AsyncJobStep;
|
|
1348
|
+
index: number;
|
|
1349
|
+
rowLabel: string;
|
|
1350
|
+
}
|
|
1351
|
+
|
|
1352
|
+
function normalizedParallelDisplayText(value: string | undefined): string | undefined {
|
|
1353
|
+
if (!value?.trim()) return undefined;
|
|
1354
|
+
const normalized = oneLine(value);
|
|
1355
|
+
return normalized && normalized !== PROMPT_REDACTED ? normalized : undefined;
|
|
1356
|
+
}
|
|
1357
|
+
|
|
1358
|
+
function parallelWidgetStepDisplayName(step: AsyncJobStep): string {
|
|
1359
|
+
const agent = normalizedParallelDisplayText(step.agent);
|
|
1360
|
+
const explicitLabel = normalizedParallelDisplayText(step.label);
|
|
1361
|
+
if (explicitLabel) {
|
|
1362
|
+
const label = compactTaskText(step.description, explicitLabel) ?? explicitLabel;
|
|
1363
|
+
return agent ? `${label} (${agent})` : label;
|
|
1364
|
+
}
|
|
1365
|
+
|
|
1366
|
+
const sessionName = normalizedParallelDisplayText(step.sessionName);
|
|
1367
|
+
if (sessionName) {
|
|
1368
|
+
const sessionTask = stripRepeatedAgentPrefix(sessionName, agent);
|
|
1369
|
+
if (sessionTask && sessionTask !== PROMPT_REDACTED && sessionTask.toLowerCase() !== agent?.toLowerCase()) return sessionTask;
|
|
1370
|
+
}
|
|
1371
|
+
|
|
1372
|
+
return compactTaskText(step.description) ?? agent ?? "subagent";
|
|
1373
|
+
}
|
|
1374
|
+
|
|
1375
|
+
function parallelWidgetStepPriority(step: AsyncJobStep): number {
|
|
1376
|
+
if (step.status === "running" || step.activityState === "needs_attention") return 0;
|
|
1377
|
+
switch (step.status) {
|
|
1378
|
+
case "failed":
|
|
1379
|
+
case "rejected":
|
|
1380
|
+
case "partial":
|
|
1381
|
+
case "stopped":
|
|
1382
|
+
case "paused":
|
|
1383
|
+
return 1;
|
|
1384
|
+
case "complete":
|
|
1385
|
+
case "completed":
|
|
1386
|
+
return 2;
|
|
1387
|
+
default:
|
|
1388
|
+
return 3;
|
|
1389
|
+
}
|
|
1390
|
+
}
|
|
1391
|
+
|
|
1392
|
+
function parallelWidgetStepRows(steps: AsyncJobStep[], total: number, prioritizeActive: boolean): ParallelWidgetStepRow[] {
|
|
1393
|
+
const indexed = steps.map((step, index) => ({ step, index, displayName: parallelWidgetStepDisplayName(step) }));
|
|
1394
|
+
if (prioritizeActive) indexed.sort((left, right) => parallelWidgetStepPriority(left.step) - parallelWidgetStepPriority(right.step) || left.index - right.index);
|
|
1395
|
+
return withDuplicateLabelDiscriminators(indexed, total).map(({ displayName, ...row }) => row);
|
|
1396
|
+
}
|
|
1397
|
+
|
|
1398
|
+
function activeParallelWidgetGroup(job: AsyncJobState): ParallelWidgetGroup | undefined {
|
|
1399
|
+
const steps = job.steps ?? [];
|
|
1400
|
+
if (!steps.length) return undefined;
|
|
1401
|
+
if (job.mode === "parallel") return { steps, total: job.stepsTotal ?? steps.length };
|
|
1402
|
+
if (job.mode !== "chain" || !job.activeParallelGroup) return undefined;
|
|
1403
|
+
|
|
1404
|
+
const chainTotal = job.chainStepCount ?? job.stepsTotal ?? steps.length;
|
|
1405
|
+
const spans = buildAsyncChainStepSpans(chainTotal, steps.length, job.parallelGroups);
|
|
1406
|
+
const currentStep = job.currentStep;
|
|
1407
|
+
const activeSpan = currentStep === undefined
|
|
1408
|
+
? spans.find((span) => span.isParallel)
|
|
1409
|
+
: spans.find((span) => span.isParallel
|
|
1410
|
+
&& currentStep >= span.start
|
|
1411
|
+
&& currentStep < span.start + span.count);
|
|
1412
|
+
return {
|
|
1413
|
+
steps,
|
|
1414
|
+
total: activeSpan?.count ?? job.stepsTotal ?? steps.length,
|
|
1415
|
+
stepIndex: activeSpan?.stepIndex,
|
|
1416
|
+
chainTotal,
|
|
1417
|
+
};
|
|
1418
|
+
}
|
|
1419
|
+
|
|
1420
|
+
function parallelWidgetGroupHeader(
|
|
1421
|
+
group: ParallelWidgetGroup,
|
|
1422
|
+
theme: Theme,
|
|
1423
|
+
frame?: number,
|
|
1424
|
+
): string {
|
|
1425
|
+
const status = aggregateStepStatus(group.steps);
|
|
1426
|
+
const label = group.stepIndex !== undefined && group.chainTotal !== undefined
|
|
1427
|
+
? `Step ${group.stepIndex + 1}/${group.chainTotal}: parallel group`
|
|
1428
|
+
: "parallel group";
|
|
1429
|
+
return ` ${widgetStepGlyph(status, theme, widgetStepsRunningSeed(group.steps), frame)} ${themeBold(theme, label)} ${theme.fg("dim", "·")} ${theme.fg("dim", formatParallelOutcome(group.steps, group.total))}`;
|
|
1430
|
+
}
|
|
1431
|
+
|
|
1432
|
+
function parallelWidgetGroupDetails(
|
|
1433
|
+
job: AsyncJobState,
|
|
1434
|
+
theme: Theme,
|
|
1435
|
+
group: ParallelWidgetGroup,
|
|
1436
|
+
expanded: boolean,
|
|
1437
|
+
width: number,
|
|
1438
|
+
frame: number | undefined,
|
|
1439
|
+
prioritizeActive: boolean,
|
|
1440
|
+
): string[] {
|
|
1441
|
+
const lines = [parallelWidgetGroupHeader(group, theme, frame)];
|
|
1442
|
+
const rows = parallelWidgetStepRows(group.steps, group.total, prioritizeActive);
|
|
1443
|
+
for (const [rowIndex, row] of rows.entries()) {
|
|
1444
|
+
const marker = rowIndex === rows.length - 1 && rows.length >= group.total ? "└─" : "├─";
|
|
1445
|
+
lines.push(...foregroundStyleWidgetStepLines(job, theme, row.step, "Agent", row.index + 1, group.total, expanded, width, frame, {
|
|
1446
|
+
rowLabel: row.rowLabel,
|
|
1447
|
+
rowIndent: " ",
|
|
1448
|
+
detailIndent: " ",
|
|
1449
|
+
rowMarker: marker,
|
|
1450
|
+
includeCurrentPath: true,
|
|
1451
|
+
}));
|
|
1452
|
+
}
|
|
1453
|
+
for (let index = rows.length; index < group.total; index++) {
|
|
1454
|
+
const marker = index === group.total - 1 ? "└─" : "├─";
|
|
1455
|
+
lines.push(` ${marker} ${theme.fg("muted", "◦")} ${theme.fg("dim", "pending")}`);
|
|
1456
|
+
}
|
|
1457
|
+
return lines;
|
|
1458
|
+
}
|
|
1459
|
+
|
|
1093
1460
|
function isDoneResult(result: Details["results"][number]): boolean {
|
|
1094
1461
|
const status = result.progress?.status;
|
|
1095
1462
|
if (status === "completed") return true;
|
|
@@ -1108,32 +1475,72 @@ interface ChainRenderResultEntry {
|
|
|
1108
1475
|
rowNumber: number;
|
|
1109
1476
|
rowLabel?: string;
|
|
1110
1477
|
agentName: string;
|
|
1478
|
+
displayIndex: number;
|
|
1479
|
+
isParallel?: boolean;
|
|
1111
1480
|
}
|
|
1112
1481
|
|
|
1113
|
-
interface
|
|
1114
|
-
kind: "
|
|
1115
|
-
rowNumber: number;
|
|
1482
|
+
interface ChainRenderGroupEntry {
|
|
1483
|
+
kind: "group";
|
|
1116
1484
|
stepLabel: string;
|
|
1117
|
-
|
|
1485
|
+
groupLabel?: string;
|
|
1118
1486
|
status: WorkflowNodeStatus;
|
|
1119
1487
|
error?: string;
|
|
1120
1488
|
}
|
|
1121
1489
|
|
|
1122
|
-
type ChainRenderEntry = ChainRenderResultEntry |
|
|
1490
|
+
type ChainRenderEntry = ChainRenderResultEntry | ChainRenderGroupEntry;
|
|
1491
|
+
|
|
1492
|
+
function chainSpanStatus(details: Details, span: ChainStepSpan): WorkflowNodeStatus {
|
|
1493
|
+
if (span.status) return span.status;
|
|
1494
|
+
const results = details.results.slice(span.start, span.start + span.count);
|
|
1495
|
+
if (results.some((result) => isResultRunning(result))) return "running";
|
|
1496
|
+
if (results.some((result) => !hasTerminalResultFlag(result) && (result.progress?.status === "failed" || result.exitCode !== 0) && !isResultRunning(result))) return "failed";
|
|
1497
|
+
if (results.some((result) => result.stopped)) return "stopped";
|
|
1498
|
+
if (results.some((result) => result.interrupted)) return "paused";
|
|
1499
|
+
if (results.some((result) => result.detached || result.progress?.status === "detached")) return "detached";
|
|
1500
|
+
if (results.length < span.count) return "pending";
|
|
1501
|
+
if (results.length > 0 && results.every(isDoneResult)) return "completed";
|
|
1502
|
+
return "pending";
|
|
1503
|
+
}
|
|
1504
|
+
|
|
1505
|
+
function withDuplicateForegroundLabels(entries: ChainRenderResultEntry[], total: number): ChainRenderResultEntry[] {
|
|
1506
|
+
return withDuplicateLabelDiscriminators(
|
|
1507
|
+
entries.map((entry) => ({ ...entry, index: entry.displayIndex, displayName: entry.agentName })),
|
|
1508
|
+
total,
|
|
1509
|
+
).map(({ displayName, rowLabel, ...entry }) => ({
|
|
1510
|
+
...entry,
|
|
1511
|
+
rowLabel: rowLabel === displayName ? undefined : rowLabel.slice(0, -(displayName.length + 2)),
|
|
1512
|
+
}));
|
|
1513
|
+
}
|
|
1123
1514
|
|
|
1124
1515
|
function buildChainRenderEntries(details: Details, label: MultiProgressLabel): ChainRenderEntry[] | undefined {
|
|
1125
1516
|
if (details.mode !== "chain" || !label.hasParallelInChain || label.showActiveGroupOnly) return undefined;
|
|
1126
1517
|
const entries: ChainRenderEntry[] = [];
|
|
1127
1518
|
for (const span of buildChainStepSpans(details)) {
|
|
1128
|
-
if (span.isParallel
|
|
1519
|
+
if (span.isParallel) {
|
|
1129
1520
|
entries.push({
|
|
1130
|
-
kind: "
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
status: span.status ?? "pending",
|
|
1521
|
+
kind: "group",
|
|
1522
|
+
stepLabel: `Step ${span.stepIndex + 1}/${label.logicalStepCount}: parallel group`,
|
|
1523
|
+
groupLabel: span.label?.trim() || undefined,
|
|
1524
|
+
status: chainSpanStatus(details, span),
|
|
1135
1525
|
error: span.error,
|
|
1136
1526
|
});
|
|
1527
|
+
const groupEntries: ChainRenderResultEntry[] = [];
|
|
1528
|
+
for (let index = span.start; index < span.start + span.count; index++) {
|
|
1529
|
+
const result = details.results[index];
|
|
1530
|
+
const localIndex = foregroundResultIndex(details, index);
|
|
1531
|
+
const displayIndex = localIndex >= span.start && localIndex < span.start + span.count
|
|
1532
|
+
? localIndex - span.start
|
|
1533
|
+
: index - span.start;
|
|
1534
|
+
groupEntries.push({
|
|
1535
|
+
kind: "result",
|
|
1536
|
+
resultIndex: index,
|
|
1537
|
+
rowNumber: index - span.start + 1,
|
|
1538
|
+
agentName: foregroundResultDisplayName(details, index, result, `agent-${displayIndex + 1}`),
|
|
1539
|
+
displayIndex,
|
|
1540
|
+
isParallel: true,
|
|
1541
|
+
});
|
|
1542
|
+
}
|
|
1543
|
+
entries.push(...withDuplicateForegroundLabels(groupEntries, span.count));
|
|
1137
1544
|
continue;
|
|
1138
1545
|
}
|
|
1139
1546
|
for (let index = span.start; index < span.start + span.count; index++) {
|
|
@@ -1141,9 +1548,10 @@ function buildChainRenderEntries(details: Details, label: MultiProgressLabel): C
|
|
|
1141
1548
|
entries.push({
|
|
1142
1549
|
kind: "result",
|
|
1143
1550
|
resultIndex: index,
|
|
1144
|
-
rowNumber:
|
|
1145
|
-
rowLabel:
|
|
1146
|
-
agentName:
|
|
1551
|
+
rowNumber: span.stepIndex + 1,
|
|
1552
|
+
rowLabel: resultRowLabel(label, span.stepIndex + 1),
|
|
1553
|
+
agentName: foregroundResultDisplayName(details, index, result, details.chainAgents?.[span.stepIndex] ?? `step-${span.stepIndex + 1}`),
|
|
1554
|
+
displayIndex: foregroundResultIndex(details, index),
|
|
1147
1555
|
});
|
|
1148
1556
|
}
|
|
1149
1557
|
}
|
|
@@ -1159,6 +1567,7 @@ interface MultiProgressLabel {
|
|
|
1159
1567
|
groupStartIndex: number;
|
|
1160
1568
|
groupEndIndex: number;
|
|
1161
1569
|
showActiveGroupOnly: boolean;
|
|
1570
|
+
logicalStepCount: number;
|
|
1162
1571
|
}
|
|
1163
1572
|
|
|
1164
1573
|
function buildMultiProgressLabel(details: Pick<Details, "mode" | "results" | "progress" | "totalSteps" | "currentStepIndex" | "chainAgents" | "workflowGraph">, hasRunning: boolean): MultiProgressLabel {
|
|
@@ -1194,7 +1603,7 @@ function buildMultiProgressLabel(details: Pick<Details, "mode" | "results" | "pr
|
|
|
1194
1603
|
const headerLabel = hasRunning
|
|
1195
1604
|
? `${formatAgentRunningLabel(running)} · ${done}/${totalCount} done`
|
|
1196
1605
|
: `${done}/${totalCount} done`;
|
|
1197
|
-
return { headerLabel, itemTitle, totalCount, hasParallelInChain, activeParallelGroup, groupStartIndex: 0, groupEndIndex: totalCount, showActiveGroupOnly: false };
|
|
1606
|
+
return { headerLabel, itemTitle, totalCount, hasParallelInChain, activeParallelGroup, groupStartIndex: 0, groupEndIndex: totalCount, showActiveGroupOnly: false, logicalStepCount: totalCount };
|
|
1198
1607
|
}
|
|
1199
1608
|
|
|
1200
1609
|
if (activeParallelGroup) {
|
|
@@ -1222,7 +1631,7 @@ function buildMultiProgressLabel(details: Pick<Details, "mode" | "results" | "pr
|
|
|
1222
1631
|
const headerLabel = hasRunning
|
|
1223
1632
|
? `step ${currentStepIndex + 1}/${totalSteps} · parallel group: ${formatAgentRunningLabel(running)} · ${done}/${groupSize} done`
|
|
1224
1633
|
: `step ${currentStepIndex + 1}/${totalSteps} · parallel group: ${done}/${groupSize} done`;
|
|
1225
|
-
return { headerLabel, itemTitle, totalCount: groupSize, hasParallelInChain, activeParallelGroup, groupStartIndex: groupStart, groupEndIndex: groupEnd, showActiveGroupOnly: true };
|
|
1634
|
+
return { headerLabel, itemTitle, totalCount: groupSize, hasParallelInChain, activeParallelGroup, groupStartIndex: groupStart, groupEndIndex: groupEnd, showActiveGroupOnly: true, logicalStepCount: totalSteps };
|
|
1226
1635
|
}
|
|
1227
1636
|
|
|
1228
1637
|
if (details.mode === "chain" && details.chainAgents?.length) {
|
|
@@ -1240,30 +1649,66 @@ function buildMultiProgressLabel(details: Pick<Details, "mode" | "results" | "pr
|
|
|
1240
1649
|
}).length;
|
|
1241
1650
|
const currentStep = details.currentStepIndex !== undefined ? details.currentStepIndex + 1 : Math.min(totalCount, doneLogical + (hasRunning ? 1 : 0));
|
|
1242
1651
|
const headerLabel = hasRunning ? `step ${currentStep}/${totalCount}` : `step ${doneLogical}/${totalCount}`;
|
|
1243
|
-
return { headerLabel, itemTitle, totalCount, hasParallelInChain, activeParallelGroup, groupStartIndex: 0, groupEndIndex: details.results.length, showActiveGroupOnly: false };
|
|
1652
|
+
return { headerLabel, itemTitle, totalCount, hasParallelInChain, activeParallelGroup, groupStartIndex: 0, groupEndIndex: details.results.length, showActiveGroupOnly: false, logicalStepCount: totalCount };
|
|
1244
1653
|
}
|
|
1245
1654
|
|
|
1246
1655
|
const totalCount = details.totalSteps ?? details.results.length;
|
|
1247
1656
|
const currentStep = details.currentStepIndex !== undefined ? details.currentStepIndex + 1 : Math.min(totalCount, details.results.filter(isDoneResult).length + (hasRunning ? 1 : 0));
|
|
1248
1657
|
const done = details.results.filter(isDoneResult).length;
|
|
1249
1658
|
const headerLabel = hasRunning ? `step ${currentStep}/${totalCount}` : `step ${done}/${totalCount}`;
|
|
1250
|
-
return { headerLabel, itemTitle, totalCount, hasParallelInChain, activeParallelGroup, groupStartIndex: 0, groupEndIndex: details.results.length, showActiveGroupOnly: false };
|
|
1251
|
-
}
|
|
1252
|
-
|
|
1253
|
-
function resultRowLabel(label: MultiProgressLabel,
|
|
1254
|
-
if (label.itemTitle === "Agent")
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1659
|
+
return { headerLabel, itemTitle, totalCount, hasParallelInChain, activeParallelGroup, groupStartIndex: 0, groupEndIndex: details.results.length, showActiveGroupOnly: false, logicalStepCount: totalCount };
|
|
1660
|
+
}
|
|
1661
|
+
|
|
1662
|
+
function resultRowLabel(label: MultiProgressLabel, stepNumber: number): string | undefined {
|
|
1663
|
+
if (label.itemTitle === "Agent") return undefined;
|
|
1664
|
+
if (shouldSuppressSingleStep(label.logicalStepCount)) return undefined;
|
|
1665
|
+
return `Step ${stepNumber}/${label.logicalStepCount}`;
|
|
1666
|
+
}
|
|
1667
|
+
|
|
1668
|
+
function buildForegroundResultEntries(
|
|
1669
|
+
details: Details,
|
|
1670
|
+
label: MultiProgressLabel,
|
|
1671
|
+
displayStart: number,
|
|
1672
|
+
displayEnd: number,
|
|
1673
|
+
useResultsDirectly: boolean,
|
|
1674
|
+
): ChainRenderResultEntry[] {
|
|
1675
|
+
const fallbackLabel = label.itemTitle.toLowerCase();
|
|
1676
|
+
const entries = Array.from({ length: displayEnd - displayStart }, (_, offset): ChainRenderResultEntry => {
|
|
1677
|
+
const index = displayStart + offset;
|
|
1678
|
+
const result = details.results[index];
|
|
1679
|
+
const stableIndex = foregroundResultIndex(details, index);
|
|
1680
|
+
const rowNumber = label.showActiveGroupOnly ? index - label.groupStartIndex + 1 : stableIndex + 1;
|
|
1681
|
+
const fallbackAgent = useResultsDirectly
|
|
1682
|
+
? (result?.agent || `${fallbackLabel}-${rowNumber}`)
|
|
1683
|
+
: (details.chainAgents![index] || result?.agent || `${fallbackLabel}-${rowNumber}`);
|
|
1684
|
+
const displayIndex = label.activeParallelGroup && stableIndex >= label.groupStartIndex && stableIndex < label.groupEndIndex
|
|
1685
|
+
? stableIndex - label.groupStartIndex
|
|
1686
|
+
: label.activeParallelGroup ? index - label.groupStartIndex : stableIndex;
|
|
1687
|
+
return {
|
|
1688
|
+
kind: "result",
|
|
1689
|
+
resultIndex: index,
|
|
1690
|
+
rowNumber,
|
|
1691
|
+
rowLabel: resultRowLabel(label, rowNumber),
|
|
1692
|
+
agentName: foregroundResultDisplayName(details, index, result, fallbackAgent),
|
|
1693
|
+
displayIndex,
|
|
1694
|
+
};
|
|
1695
|
+
});
|
|
1696
|
+
return label.itemTitle === "Agent" ? withDuplicateForegroundLabels(entries, label.totalCount) : entries;
|
|
1261
1697
|
}
|
|
1262
1698
|
|
|
1263
1699
|
function widgetStats(job: AsyncJobState, theme: Theme): string {
|
|
1264
1700
|
const parts: string[] = [];
|
|
1265
|
-
const
|
|
1266
|
-
|
|
1701
|
+
const stageProgress = workflowStageProgress(job);
|
|
1702
|
+
const stepsTotal = stageProgress?.total ?? job.stepsTotal ?? (job.agents?.length ?? 1);
|
|
1703
|
+
const isSingleChild = isSingleChildAsyncJob(job);
|
|
1704
|
+
if (stageProgress) {
|
|
1705
|
+
const stages = workflowGraphStageNodes(job.workflowGraph);
|
|
1706
|
+
const currentStage = stageProgress.current !== undefined ? stages[stageProgress.current] : undefined;
|
|
1707
|
+
const focus = currentStage
|
|
1708
|
+
? [compactTaskText(undefined, currentStage.label) ?? boundedLaneValue(currentStage.id), currentStage.agent ? boundedLaneValue(currentStage.agent) : "", workflowNodeStatusLabel(currentStage.status)].filter(Boolean)
|
|
1709
|
+
: [];
|
|
1710
|
+
parts.push(["staged lane", stageProgress.current !== undefined ? `stage ${stageProgress.current + 1}/${stageProgress.total}` : `${stageProgress.total} stages`, ...focus].join(" · "));
|
|
1711
|
+
} else if (job.activeParallelGroup) {
|
|
1267
1712
|
const running = job.runningSteps ?? (job.status === "running" ? 1 : 0);
|
|
1268
1713
|
const done = job.completedSteps ?? (job.status === "complete" ? stepsTotal : 0);
|
|
1269
1714
|
if (job.mode === "parallel") {
|
|
@@ -1280,10 +1725,10 @@ function widgetStats(job: AsyncJobState, theme: Theme): string {
|
|
|
1280
1725
|
parts.push(`step ${logicalStep + 1}/${total} · parallel group: ${groupParts.join(" · ")}`);
|
|
1281
1726
|
}
|
|
1282
1727
|
} else if (job.currentStep !== undefined) {
|
|
1283
|
-
if (job.mode === "chain"
|
|
1728
|
+
if (job.mode === "chain") {
|
|
1284
1729
|
const total = job.chainStepCount ?? stepsTotal;
|
|
1285
|
-
parts.push(`step ${flatToLogicalStepIndex(job.currentStep, total, job.parallelGroups) + 1}/${total}`);
|
|
1286
|
-
} else {
|
|
1730
|
+
parts.push(`step ${flatToLogicalStepIndex(job.currentStep, total, job.parallelGroups ?? []) + 1}/${total}`);
|
|
1731
|
+
} else if (!isSingleChild) {
|
|
1287
1732
|
parts.push(`step ${job.currentStep + 1}/${stepsTotal}`);
|
|
1288
1733
|
}
|
|
1289
1734
|
} else if (stepsTotal > 1) {
|
|
@@ -1487,40 +1932,57 @@ function foregroundStyleWidgetStepLines(
|
|
|
1487
1932
|
job: AsyncJobState,
|
|
1488
1933
|
theme: Theme,
|
|
1489
1934
|
step: NonNullable<AsyncJobState["steps"]>[number],
|
|
1490
|
-
itemTitle: "Agent" | "Step",
|
|
1935
|
+
itemTitle: "Agent" | "Stage" | "Step",
|
|
1491
1936
|
index: number,
|
|
1492
1937
|
total: number,
|
|
1493
1938
|
expanded: boolean,
|
|
1494
1939
|
width: number,
|
|
1495
1940
|
frame?: number,
|
|
1941
|
+
options?: { rowLabel?: string; rowIndent?: string; detailIndent?: string; rowMarker?: string; includeCurrentPath?: boolean },
|
|
1496
1942
|
): string[] {
|
|
1943
|
+
const rowIndent = options?.rowIndent ?? " ";
|
|
1944
|
+
const detailIndent = options?.detailIndent ?? " ";
|
|
1497
1945
|
const status = widgetStepStatus(step.status, theme);
|
|
1498
1946
|
const stats = widgetStepStats(theme, step);
|
|
1499
1947
|
const modelDisplay = modelThinkingBadge(theme, step.model, step.thinking);
|
|
1500
|
-
const
|
|
1948
|
+
const collapseDetails = shouldCollapseSingleChildDetails(job, step);
|
|
1949
|
+
const displayName = collapseDetails ? singleChildAgentName(job, step) : childDisplayName(step);
|
|
1950
|
+
const stageName = itemTitle === "Stage"
|
|
1951
|
+
? compactTaskText(undefined, step.label) ?? boundedLaneValue(step.workflowKey) ?? displayName
|
|
1952
|
+
: undefined;
|
|
1953
|
+
const stageIdentity = stageName && stageName !== displayName ? `${stageName} (${displayName})` : stageName ?? displayName;
|
|
1954
|
+
const rowLabel = collapseDetails ? displayName : (options?.rowLabel ?? `${itemTitle} ${index}/${total}: ${itemTitle === "Stage" ? stageIdentity : displayName}`);
|
|
1955
|
+
const rowMarker = options?.rowMarker ? `${options.rowMarker} ` : "";
|
|
1956
|
+
const lines = [`${rowIndent}${rowMarker}${widgetStepGlyph(step.status, theme, widgetStepRunningSeed(step, index - 1), frame)} ${themeBold(theme, rowLabel)}${contextModeBadge(theme, step.context)} ${theme.fg("dim", "·")} ${status}${modelDisplay}${stats ? ` ${theme.fg("dim", "·")} ${stats}` : ""}`];
|
|
1501
1957
|
const lane = projectAsyncLane(job, step);
|
|
1502
|
-
if (lane) lines.push(...formatLaneProjectionLines(lane, theme,
|
|
1503
|
-
const task = compactTaskText(step.description, step.label);
|
|
1504
|
-
if (task) lines.push(
|
|
1958
|
+
if (lane) lines.push(...formatLaneProjectionLines(lane, theme, detailIndent));
|
|
1959
|
+
const task = collapseDetails ? singleChildTask(job, step) : compactTaskText(step.description, step.label);
|
|
1960
|
+
if (task) lines.push(`${detailIndent}${theme.fg("dim", `task: ${task}`)}`);
|
|
1505
1961
|
const activity = widgetStepActivityLine(step, width, expanded, job.updatedAt);
|
|
1506
|
-
|
|
1962
|
+
const currentPath = options?.includeCurrentPath && step.currentPath ? shortenPath(step.currentPath) : undefined;
|
|
1963
|
+
const activityWithPath = currentPath && activity && !activity.includes(currentPath)
|
|
1964
|
+
? `${activity} · ${currentPath}`
|
|
1965
|
+
: activity ?? currentPath;
|
|
1966
|
+
if (activityWithPath) lines.push(`${detailIndent}${theme.fg("dim", `⎿ ${activityWithPath}`)}`);
|
|
1507
1967
|
for (const nestedLine of formatNestedWidgetLines(step.children, theme, width, expanded, job.updatedAt, expanded ? 12 : 6)) {
|
|
1508
|
-
lines.push(
|
|
1968
|
+
lines.push(`${detailIndent}${nestedLine}`);
|
|
1509
1969
|
}
|
|
1970
|
+
const error = step.error?.trim() || step.execution?.error?.trim();
|
|
1971
|
+
if (error) lines.push(`${detailIndent}${theme.fg("error", `error: ${oneLine(error)}`)}`);
|
|
1510
1972
|
if (step.status === "running") {
|
|
1511
|
-
if (!expanded) lines.push(
|
|
1973
|
+
if (!expanded) lines.push(`${detailIndent}${theme.fg("accent", liveDetailHintText())}`);
|
|
1512
1974
|
const output = widgetOutputPath(job, step);
|
|
1513
|
-
if (output) lines.push(
|
|
1975
|
+
if (output) lines.push(`${detailIndent}${theme.fg("dim", `output: ${shortenPath(output)}`)}`);
|
|
1514
1976
|
if (expanded) {
|
|
1515
1977
|
const liveStatus = buildLiveStatusLine(step, job.updatedAt);
|
|
1516
|
-
if (liveStatus && liveStatus !== activity) lines.push(
|
|
1978
|
+
if (liveStatus && liveStatus !== activity) lines.push(`${detailIndent}${theme.fg("accent", liveStatus)}`);
|
|
1517
1979
|
for (const tool of step.recentTools?.slice(-3) ?? []) {
|
|
1518
1980
|
const maxArgsLen = Math.max(40, width - 30);
|
|
1519
1981
|
const argsPreview = renderToolArgsPreview(tool.args, maxArgsLen, expanded);
|
|
1520
|
-
lines.push(
|
|
1982
|
+
lines.push(`${detailIndent} ${theme.fg("dim", `${tool.tool}${argsPreview ? `: ${argsPreview}` : ""}`)}`);
|
|
1521
1983
|
}
|
|
1522
1984
|
for (const line of compactRecentOutputLines(step.recentOutput)) {
|
|
1523
|
-
lines.push(
|
|
1985
|
+
lines.push(`${detailIndent} ${theme.fg("dim", line)}`);
|
|
1524
1986
|
}
|
|
1525
1987
|
}
|
|
1526
1988
|
}
|
|
@@ -1553,7 +2015,8 @@ function hostStepWidgetLines(job: AsyncJobState, theme: Theme, indent: string):
|
|
|
1553
2015
|
}
|
|
1554
2016
|
|
|
1555
2017
|
function foregroundStyleWidgetDetails(job: AsyncJobState, theme: Theme, expanded: boolean, width: number, frame?: number): string[] {
|
|
1556
|
-
|
|
2018
|
+
const steps = workflowWidgetSteps(job);
|
|
2019
|
+
if (!steps.length) {
|
|
1557
2020
|
const lane = projectAsyncLane(job);
|
|
1558
2021
|
return [
|
|
1559
2022
|
...workflowPreflightLines(job, expanded),
|
|
@@ -1564,14 +2027,31 @@ function foregroundStyleWidgetDetails(job: AsyncJobState, theme: Theme, expanded
|
|
|
1564
2027
|
];
|
|
1565
2028
|
}
|
|
1566
2029
|
if (job.mode === "chain" && !job.activeParallelGroup && job.parallelGroups?.length) return widgetChainDetails(job, theme, expanded, width, frame);
|
|
1567
|
-
const total = job.stepsTotal ?? job.steps.length;
|
|
1568
|
-
const itemTitle = job.mode === "parallel" || job.activeParallelGroup ? "Agent" : "Step";
|
|
1569
2030
|
const lines: string[] = workflowPreflightLines(job, expanded);
|
|
1570
|
-
|
|
1571
|
-
|
|
2031
|
+
const group = activeParallelWidgetGroup(job);
|
|
2032
|
+
if (group) {
|
|
2033
|
+
lines.push(...parallelWidgetGroupDetails(job, theme, group, expanded, width, frame, Boolean(job.activeParallelGroup)));
|
|
2034
|
+
} else {
|
|
2035
|
+
const stageProgress = workflowStageProgress(job);
|
|
2036
|
+
const total = job.mode === "chain"
|
|
2037
|
+
? job.chainStepCount ?? job.stepsTotal ?? steps.length
|
|
2038
|
+
: stageProgress?.total ?? job.stepsTotal ?? steps.length;
|
|
2039
|
+
const stageTotal = stageProgress?.total ?? total;
|
|
2040
|
+
const plannedKeys = stageProgress ? new Set(workflowGraphStageNodes(job.workflowGraph).map((node) => node.id)) : undefined;
|
|
2041
|
+
const extraStepCount = plannedKeys
|
|
2042
|
+
? steps.filter((step) => step.workflowKey === undefined || !plannedKeys.has(step.workflowKey)).length
|
|
2043
|
+
: total;
|
|
2044
|
+
let extraStepIndex = 0;
|
|
2045
|
+
for (const [index, step] of steps.entries()) {
|
|
2046
|
+
const isPlannedStage = plannedKeys !== undefined && step.workflowKey !== undefined && plannedKeys.has(step.workflowKey);
|
|
2047
|
+
const itemTitle = isPlannedStage ? "Stage" : "Step";
|
|
2048
|
+
const displayIndex = isPlannedStage ? (step.index ?? index) + 1 : plannedKeys ? ++extraStepIndex : index + 1;
|
|
2049
|
+
const displayTotal = isPlannedStage ? stageTotal : extraStepCount;
|
|
2050
|
+
lines.push(...foregroundStyleWidgetStepLines(job, theme, step, itemTitle, displayIndex, displayTotal, expanded, width, frame));
|
|
2051
|
+
}
|
|
1572
2052
|
}
|
|
1573
2053
|
lines.push(...hostStepWidgetLines(job, theme, " "));
|
|
1574
|
-
const attached = new Set(
|
|
2054
|
+
const attached = new Set(steps.flatMap((step) => step.children?.map((child) => child.id) ?? []));
|
|
1575
2055
|
const unattached = job.nestedChildren?.filter((child) => !attached.has(child.id)) ?? [];
|
|
1576
2056
|
for (const nestedLine of formatNestedWidgetLines(unattached, theme, width, expanded, job.updatedAt, expanded ? 12 : 6)) {
|
|
1577
2057
|
lines.push(` ${nestedLine}`);
|
|
@@ -1581,12 +2061,16 @@ function foregroundStyleWidgetDetails(job: AsyncJobState, theme: Theme, expanded
|
|
|
1581
2061
|
|
|
1582
2062
|
function buildSingleWidgetLines(job: AsyncJobState, theme: Theme, width: number, expanded: boolean, frame?: number): string[] {
|
|
1583
2063
|
const stats = widgetStats(job, theme);
|
|
1584
|
-
const count = job.mode === "chain" ? job.chainStepCount : job.stepsTotal ?? job.agents?.length ?? job.steps?.length;
|
|
2064
|
+
const count = job.mode === "chain" ? job.chainStepCount : workflowStageProgress(job)?.total ?? job.stepsTotal ?? job.agents?.length ?? job.steps?.length;
|
|
1585
2065
|
const mode = widgetJobName(job);
|
|
1586
|
-
const title =
|
|
2066
|
+
const title = isSingleChildAsyncJob(job)
|
|
2067
|
+
? "async subagent"
|
|
2068
|
+
: `async subagent ${mode}${count && count > 1 ? ` (${count})` : ""}`;
|
|
2069
|
+
const collapseDetails = job.steps?.length === 1 && shouldCollapseSingleChildDetails(job, job.steps[0]!);
|
|
2070
|
+
const summary = `${widgetStatusGlyph(job, theme, frame)} ${themeBold(theme, mode)}${contextModeBadge(theme, job.context)}${stats ? ` ${theme.fg("dim", "·")} ${stats}` : ""}`;
|
|
1587
2071
|
return [
|
|
1588
2072
|
`${theme.fg("toolTitle", themeBold(theme, title))} ${theme.fg("dim", "· background")}`,
|
|
1589
|
-
|
|
2073
|
+
...(collapseDetails ? [] : [summary]),
|
|
1590
2074
|
...foregroundStyleWidgetDetails(job, theme, expanded, width, frame),
|
|
1591
2075
|
].map((line) => truncLine(line, width));
|
|
1592
2076
|
}
|
|
@@ -1595,10 +2079,13 @@ function compactSingleWidgetLines(job: AsyncJobState, theme: Theme, width: numbe
|
|
|
1595
2079
|
const fullLines = buildSingleWidgetLines(job, theme, width, false, frame);
|
|
1596
2080
|
if (fullLines.length <= 10 || !job.steps?.length || (job.mode !== "parallel" && !job.activeParallelGroup)) return fullLines;
|
|
1597
2081
|
|
|
1598
|
-
const
|
|
1599
|
-
|
|
2082
|
+
const group = activeParallelWidgetGroup(job);
|
|
2083
|
+
if (!group) return fullLines;
|
|
2084
|
+
const rows = parallelWidgetStepRows(group.steps, group.total, Boolean(job.activeParallelGroup));
|
|
1600
2085
|
const lines = fullLines.slice(0, 2);
|
|
1601
|
-
|
|
2086
|
+
lines.push(parallelWidgetGroupHeader(group, theme, frame));
|
|
2087
|
+
for (const [rowIndex, row] of rows.entries()) {
|
|
2088
|
+
const step = row.step;
|
|
1602
2089
|
const status = widgetStepStatus(step.status, theme);
|
|
1603
2090
|
const activity = widgetStepActivityLine(step, width, false, job.updatedAt);
|
|
1604
2091
|
const stepStats = widgetStepStats(theme, step);
|
|
@@ -1606,10 +2093,11 @@ function compactSingleWidgetLines(job: AsyncJobState, theme: Theme, width: numbe
|
|
|
1606
2093
|
const modelDisplay = modelThinkingBadge(theme, step.model, step.thinking);
|
|
1607
2094
|
const task = compactTaskText(step.description, step.label);
|
|
1608
2095
|
const taskSuffix = task ? ` ${theme.fg("dim", "·")} ${theme.fg("dim", `task: ${task}`)}` : "";
|
|
1609
|
-
|
|
2096
|
+
const marker = rowIndex === rows.length - 1 && rowIndex >= group.total - 1 ? "└─" : "├─";
|
|
2097
|
+
lines.push(` ${marker} ${widgetStepGlyph(step.status, theme, widgetStepRunningSeed(step, row.index), frame)} ${themeBold(theme, row.rowLabel)}${contextModeBadge(theme, step.context)} ${theme.fg("dim", "·")} ${status}${modelDisplay}${taskSuffix}${activitySuffix}${stepStats ? ` ${theme.fg("dim", "·")} ${stepStats}` : ""}`);
|
|
1610
2098
|
const lane = projectAsyncLane(job, step);
|
|
1611
|
-
if (lane) lines.push(...formatLaneProjectionLines(lane, theme, "
|
|
1612
|
-
for (const nestedLine of formatNestedWidgetLines(step.children, theme, width, false, job.updatedAt, 6)) lines.push(`
|
|
2099
|
+
if (lane) lines.push(...formatLaneProjectionLines(lane, theme, " "));
|
|
2100
|
+
for (const nestedLine of formatNestedWidgetLines(step.children, theme, width, false, job.updatedAt, 6)) lines.push(` ${nestedLine}`);
|
|
1613
2101
|
}
|
|
1614
2102
|
lines.push(...hostStepWidgetLines(job, theme, " "));
|
|
1615
2103
|
if (job.steps.some((step) => step.status === "running")) lines.push(theme.fg("accent", ` ${liveDetailHintText()}`));
|
|
@@ -1852,6 +2340,10 @@ function fitAdaptiveWidgetLines(jobs: AsyncJobState[], buildLines: () => string[
|
|
|
1852
2340
|
widgetLayoutSession = { expanded, rows, columns, tier: "full", visibleJobKeys: [] };
|
|
1853
2341
|
return fitWidgetLineBudget(lines, theme, width, false);
|
|
1854
2342
|
}
|
|
2343
|
+
if (availableRows > 2 && jobs.length === 1 && workflowStageProgress(jobs[0]!)) {
|
|
2344
|
+
widgetLayoutSession = { expanded, rows, columns, tier: "full", visibleJobKeys: [] };
|
|
2345
|
+
return fitWidgetLineBudget(lines, theme, width, false);
|
|
2346
|
+
}
|
|
1855
2347
|
|
|
1856
2348
|
if (availableRows <= 2) {
|
|
1857
2349
|
widgetLayoutSession = { expanded, rows, columns, tier: "single-line", visibleJobKeys: [] };
|
|
@@ -1898,9 +2390,7 @@ export function buildWidgetLines(jobs: AsyncJobState[], theme: Theme, width = ge
|
|
|
1898
2390
|
let hiddenFinished = 0;
|
|
1899
2391
|
let queuedSummaryShown = false;
|
|
1900
2392
|
let slots = MAX_WIDGET_JOBS;
|
|
1901
|
-
|
|
1902
|
-
for (const job of running) {
|
|
1903
|
-
if (slots <= 0) { hiddenRunning++; continue; }
|
|
2393
|
+
const appendJob = (job: AsyncJobState): void => {
|
|
1904
2394
|
const stats = widgetStats(job, theme);
|
|
1905
2395
|
items.push([
|
|
1906
2396
|
`${widgetStatusGlyph(job, theme, frame)} ${themeBold(theme, widgetJobName(job))}${contextModeBadge(theme, job.context)}${stats ? ` ${theme.fg("dim", "·")} ${stats}` : ""}`,
|
|
@@ -1908,6 +2398,11 @@ export function buildWidgetLines(jobs: AsyncJobState[], theme: Theme, width = ge
|
|
|
1908
2398
|
...widgetLaneDetailLines(job, theme),
|
|
1909
2399
|
...widgetParallelAgentDetails(job, theme, expanded, width, frame),
|
|
1910
2400
|
]);
|
|
2401
|
+
};
|
|
2402
|
+
|
|
2403
|
+
for (const job of running) {
|
|
2404
|
+
if (slots <= 0) { hiddenRunning++; continue; }
|
|
2405
|
+
appendJob(job);
|
|
1911
2406
|
slots--;
|
|
1912
2407
|
}
|
|
1913
2408
|
|
|
@@ -1919,13 +2414,7 @@ export function buildWidgetLines(jobs: AsyncJobState[], theme: Theme, width = ge
|
|
|
1919
2414
|
|
|
1920
2415
|
for (const job of finished) {
|
|
1921
2416
|
if (slots <= 0) { hiddenFinished++; continue; }
|
|
1922
|
-
|
|
1923
|
-
items.push([
|
|
1924
|
-
`${widgetStatusGlyph(job, theme, frame)} ${themeBold(theme, widgetJobName(job))}${contextModeBadge(theme, job.context)}${stats ? ` ${theme.fg("dim", "·")} ${stats}` : ""}`,
|
|
1925
|
-
` ${theme.fg("dim", `⎿ ${widgetActivity(job)}`)}`,
|
|
1926
|
-
...widgetLaneDetailLines(job, theme),
|
|
1927
|
-
...widgetParallelAgentDetails(job, theme, expanded, width, frame),
|
|
1928
|
-
]);
|
|
2417
|
+
appendJob(job);
|
|
1929
2418
|
slots--;
|
|
1930
2419
|
}
|
|
1931
2420
|
|
|
@@ -1991,7 +2480,7 @@ function renderSingleCompact(
|
|
|
1991
2480
|
const detailIndent = mainWindowIndent(layout, 1);
|
|
1992
2481
|
const continuationIndent = mainWindowIndent(layout, 2) + (layout.horizontalSpacing > 0 ? " " : "");
|
|
1993
2482
|
const modelDisplay = modelThinkingBadge(theme, r.model ?? r.progress?.model, r.thinking ?? r.progress?.thinking);
|
|
1994
|
-
c.addChild(new Text(truncLine(`${resultGlyph(r, output, theme, isRunning, undefined, frame)} ${theme.fg("toolTitle", theme.bold(
|
|
2483
|
+
c.addChild(new Text(truncLine(`${resultGlyph(r, output, theme, isRunning, undefined, frame)} ${theme.fg("toolTitle", theme.bold(foregroundSingleDisplayName(r)))}${modelDisplay}${contextBadge}${stats ? ` ${theme.fg("dim", "·")} ${stats}` : ""}`, width), 0, 0));
|
|
1995
2484
|
|
|
1996
2485
|
if (isRunning && r.progress) {
|
|
1997
2486
|
const task = compactTaskText(r.task);
|
|
@@ -2144,19 +2633,13 @@ function renderMultiCompact(d: Details, theme: Theme, layout: MainWindowRenderLa
|
|
|
2144
2633
|
const displayStart = multiLabel.showActiveGroupOnly ? multiLabel.groupStartIndex : 0;
|
|
2145
2634
|
const displayEnd = multiLabel.showActiveGroupOnly ? multiLabel.groupEndIndex : (useResultsDirectly ? d.results.length : d.chainAgents!.length);
|
|
2146
2635
|
const chainEntries = buildChainRenderEntries(d, multiLabel);
|
|
2147
|
-
const renderEntries = chainEntries ??
|
|
2148
|
-
const i = displayStart + offset;
|
|
2149
|
-
const r = d.results[i];
|
|
2150
|
-
const fallbackLabel = itemTitle.toLowerCase();
|
|
2151
|
-
const rowNumber = multiLabel.showActiveGroupOnly ? (i - multiLabel.groupStartIndex + 1) : (i + 1);
|
|
2152
|
-
const fallbackAgent = useResultsDirectly ? (r?.agent || `${fallbackLabel}-${rowNumber}`) : (d.chainAgents![i] || r?.agent || `${fallbackLabel}-${rowNumber}`);
|
|
2153
|
-
return { kind: "result", resultIndex: i, rowNumber, agentName: r?.sessionName?.trim() || fallbackAgent };
|
|
2154
|
-
});
|
|
2636
|
+
const renderEntries = chainEntries ?? buildForegroundResultEntries(d, multiLabel, displayStart, displayEnd, useResultsDirectly);
|
|
2155
2637
|
for (const entry of renderEntries) {
|
|
2156
|
-
if (entry.kind === "
|
|
2638
|
+
if (entry.kind === "group") {
|
|
2157
2639
|
const glyph = widgetStepGlyph(entry.status as AsyncJobStep["status"], theme);
|
|
2158
2640
|
const statusLabel = widgetStepStatus(entry.status as AsyncJobStep["status"], theme);
|
|
2159
|
-
|
|
2641
|
+
const groupLabel = entry.groupLabel ? ` (${compactTaskText(undefined, entry.groupLabel) ?? entry.groupLabel})` : "";
|
|
2642
|
+
c.addChild(new Text(truncLine(`${rowIndent}${glyph} ${entry.stepLabel}${groupLabel} ${theme.fg("dim", "·")} ${statusLabel}`, width), 0, 0));
|
|
2160
2643
|
if (entry.error) c.addChild(new Text(truncLine(theme.fg("error", `${detailIndent}⎿ Error: ${entry.error}`), width), 0, 0));
|
|
2161
2644
|
continue;
|
|
2162
2645
|
}
|
|
@@ -2165,23 +2648,24 @@ function renderMultiCompact(d: Details, theme: Theme, layout: MainWindowRenderLa
|
|
|
2165
2648
|
const rowNumber = entry.rowNumber;
|
|
2166
2649
|
const agentName = entry.agentName;
|
|
2167
2650
|
if (!r) {
|
|
2168
|
-
const pendingLabel = entry.rowLabel ?? `${itemTitle} ${rowNumber}
|
|
2169
|
-
|
|
2651
|
+
const pendingLabel = entry.rowLabel ?? (entry.isParallel ? "" : `${itemTitle} ${rowNumber}`);
|
|
2652
|
+
const labelPrefix = pendingLabel ? `${pendingLabel}: ` : "";
|
|
2653
|
+
c.addChild(new Text(truncLine(theme.fg("dim", `${rowIndent}◦ ${labelPrefix}${agentName} · pending`), width), 0, 0));
|
|
2170
2654
|
continue;
|
|
2171
2655
|
}
|
|
2172
2656
|
const output = getSingleResultOutput(r);
|
|
2173
|
-
const progressFromArray = d
|
|
2657
|
+
const progressFromArray = foregroundProgressForResult(d, i);
|
|
2174
2658
|
const rProg = r.progress || progressFromArray || r.progressSummary;
|
|
2175
2659
|
const rRunning = rProg && "status" in rProg && isResultRunning(r, rProg.status);
|
|
2176
2660
|
const rPending = rProg && "status" in rProg && rProg.status === "pending";
|
|
2177
|
-
const stepNumber = r.progress?.index !== undefined ? r.progress.index + 1 : progressFromArray?.index !== undefined ? progressFromArray.index + 1 : i + 1;
|
|
2178
2661
|
const stepStats = formatProgressStats(theme, rProg);
|
|
2179
2662
|
const glyph = rPending ? theme.fg("dim", "◦") : resultGlyph(r, output, theme, rRunning, progressRunningSeed(rProg), frame);
|
|
2180
2663
|
const pendingLabel = rPending ? ` ${theme.fg("dim", "· pending")}` : "";
|
|
2181
|
-
const stepLabel = entry.rowLabel
|
|
2664
|
+
const stepLabel = entry.rowLabel;
|
|
2182
2665
|
const rowProgressModel = rProg && "status" in rProg ? rProg : undefined;
|
|
2183
2666
|
const rowModelDisplay = modelThinkingBadge(theme, r.model ?? rowProgressModel?.model, r.thinking ?? rowProgressModel?.thinking);
|
|
2184
|
-
const
|
|
2667
|
+
const labelPrefix = stepLabel ? `${stepLabel}: ` : "";
|
|
2668
|
+
const line = `${glyph} ${labelPrefix}${themeBold(theme, agentName)}${contextModeBadge(theme, r.context)}${rowModelDisplay}${stepStats ? ` ${theme.fg("dim", "·")} ${stepStats}` : ""}${pendingLabel}`;
|
|
2185
2669
|
c.addChild(new Text(truncLine(`${rowIndent}${line}`, width), 0, 0));
|
|
2186
2670
|
if (rRunning || rPending) {
|
|
2187
2671
|
const task = compactTaskText(r.task, workflowLabelForResult(d, i));
|
|
@@ -2242,7 +2726,7 @@ export function renderSubagentSummary(
|
|
|
2242
2726
|
? theme.fg("error", "✗")
|
|
2243
2727
|
: theme.fg("warning", "■");
|
|
2244
2728
|
const label = details?.mode === "single" && results.length === 1
|
|
2245
|
-
?
|
|
2729
|
+
? foregroundSingleDisplayName(results[0])
|
|
2246
2730
|
: details?.mode || "subagent";
|
|
2247
2731
|
return new Text(
|
|
2248
2732
|
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),
|
|
@@ -2315,7 +2799,7 @@ export function renderSubagentResult(
|
|
|
2315
2799
|
const fit = (text: string) => expanded ? text : truncLine(text, w);
|
|
2316
2800
|
const toolCallLines = getToolCallLines(r, expanded);
|
|
2317
2801
|
const c = new Container();
|
|
2318
|
-
c.addChild(new Text(fit(`${presentation.glyph} ${theme.fg("toolTitle", theme.bold(
|
|
2802
|
+
c.addChild(new Text(fit(`${presentation.glyph} ${theme.fg("toolTitle", theme.bold(foregroundSingleDisplayName(r)))}${contextBadge}${progressInfo} ${theme.fg("dim", "·")} ${presentation.label}`), 0, 0));
|
|
2319
2803
|
c.addChild(new Spacer(1));
|
|
2320
2804
|
const taskMaxLen = Math.max(20, w - 8);
|
|
2321
2805
|
const taskPreview = expanded || r.task.length <= taskMaxLen
|
|
@@ -2325,6 +2809,9 @@ export function renderSubagentResult(
|
|
|
2325
2809
|
new Text(fit(theme.fg("dim", `Task: ${taskPreview}`)), 0, 0),
|
|
2326
2810
|
);
|
|
2327
2811
|
c.addChild(new Spacer(1));
|
|
2812
|
+
if (!isRunning && (r.exitCode !== 0 || r.interrupted || r.detached || r.stopped)) {
|
|
2813
|
+
c.addChild(new Text(fit(theme.fg(r.exitCode !== 0 ? "error" : "dim", ` ⎿ ${resultStatusLine(r, output)}`)), 0, 0));
|
|
2814
|
+
}
|
|
2328
2815
|
|
|
2329
2816
|
if (isRunning && r.progress) {
|
|
2330
2817
|
const progressSnapshotNow = snapshotNowForProgress(r.progress);
|
|
@@ -2456,7 +2943,7 @@ export function renderSubagentResult(
|
|
|
2456
2943
|
? d.chainAgents
|
|
2457
2944
|
.map((agent, i) => {
|
|
2458
2945
|
const result = d.results[i];
|
|
2459
|
-
const displayName =
|
|
2946
|
+
const displayName = foregroundResultDisplayName(d, i, result, agent);
|
|
2460
2947
|
const isCurrent = i === (d.currentStepIndex ?? d.results.length);
|
|
2461
2948
|
const stepPresentation = result
|
|
2462
2949
|
? styledResultPresentation(resultPresentation(result, getSingleResultOutput(result), isCurrent && hasRunning && !hasTerminalResultFlag(result)), theme)
|
|
@@ -2487,20 +2974,15 @@ export function renderSubagentResult(
|
|
|
2487
2974
|
const displayStart = multiLabel.showActiveGroupOnly ? multiLabel.groupStartIndex : 0;
|
|
2488
2975
|
const displayEnd = multiLabel.showActiveGroupOnly ? multiLabel.groupEndIndex : (useResultsDirectly ? d.results.length : d.chainAgents!.length);
|
|
2489
2976
|
const chainEntries = buildChainRenderEntries(d, multiLabel);
|
|
2490
|
-
const renderEntries = chainEntries ??
|
|
2491
|
-
const i = displayStart + offset;
|
|
2492
|
-
const r = d.results[i];
|
|
2493
|
-
const rowNumber = multiLabel.showActiveGroupOnly ? (i - multiLabel.groupStartIndex + 1) : (i + 1);
|
|
2494
|
-
const fallbackAgent = useResultsDirectly ? (r?.agent || `step-${rowNumber}`) : (d.chainAgents![i] || r?.agent || `step-${rowNumber}`);
|
|
2495
|
-
return { kind: "result", resultIndex: i, rowNumber, agentName: r?.sessionName?.trim() || fallbackAgent };
|
|
2496
|
-
});
|
|
2977
|
+
const renderEntries = chainEntries ?? buildForegroundResultEntries(d, multiLabel, displayStart, displayEnd, useResultsDirectly);
|
|
2497
2978
|
|
|
2498
2979
|
c.addChild(new Spacer(1));
|
|
2499
2980
|
|
|
2500
2981
|
for (const entry of renderEntries) {
|
|
2501
|
-
if (entry.kind === "
|
|
2982
|
+
if (entry.kind === "group") {
|
|
2502
2983
|
const statusLabel = widgetStepStatus(entry.status as AsyncJobStep["status"], theme);
|
|
2503
|
-
|
|
2984
|
+
const groupLabel = entry.groupLabel ? ` (${compactTaskText(undefined, entry.groupLabel) ?? entry.groupLabel})` : "";
|
|
2985
|
+
c.addChild(new Text(fit(` ${statusLabel} ${entry.stepLabel}${groupLabel}`), 0, 0));
|
|
2504
2986
|
c.addChild(new Text(theme.fg(entry.status === "failed" ? "error" : "dim", ` status: ${entry.status}`), 0, 0));
|
|
2505
2987
|
if (entry.error) c.addChild(new Text(theme.fg("error", ` error: ${entry.error}`), 0, 0));
|
|
2506
2988
|
c.addChild(new Spacer(1));
|
|
@@ -2512,28 +2994,28 @@ export function renderSubagentResult(
|
|
|
2512
2994
|
const agentName = entry.agentName;
|
|
2513
2995
|
|
|
2514
2996
|
if (!r) {
|
|
2515
|
-
const pendingLabel = entry.rowLabel ?? `${itemTitle} ${rowNumber}
|
|
2516
|
-
|
|
2997
|
+
const pendingLabel = entry.rowLabel ?? (entry.isParallel ? "" : `${itemTitle} ${rowNumber}`);
|
|
2998
|
+
const labelPrefix = pendingLabel ? `${pendingLabel}: ` : "";
|
|
2999
|
+
c.addChild(new Text(fit(theme.fg("dim", ` ${labelPrefix}${agentName}`)), 0, 0));
|
|
2517
3000
|
c.addChild(new Text(theme.fg("dim", ` status: pending`), 0, 0));
|
|
2518
3001
|
c.addChild(new Spacer(1));
|
|
2519
3002
|
continue;
|
|
2520
3003
|
}
|
|
2521
3004
|
|
|
2522
|
-
const progressFromArray = d
|
|
2523
|
-
|| d.progress?.find((p) => p.agent === r.agent && p.status === "running");
|
|
3005
|
+
const progressFromArray = foregroundProgressForResult(d, i);
|
|
2524
3006
|
const rProg = r.progress || progressFromArray || r.progressSummary;
|
|
2525
3007
|
const rRunning = isResultRunning(r, rProg?.status);
|
|
2526
|
-
const stepNumber = typeof rProg?.index === "number" ? rProg.index + 1 : i + 1;
|
|
2527
3008
|
|
|
2528
3009
|
const resultOutput = getSingleResultOutput(r);
|
|
2529
3010
|
const rowPresentation = styledResultPresentation(resultPresentation(r, resultOutput, rRunning, progressRunningSeed(rProg), frame), theme);
|
|
2530
3011
|
const stats = rProg ? ` | ${rProg.toolCount} tools, ${formatDuration(rProg.durationMs)}` : "";
|
|
2531
3012
|
const modelDisplay = modelThinkingBadge(theme, r.model ?? rProg?.model, r.thinking ?? rProg?.thinking);
|
|
2532
|
-
const stepLabel = entry.rowLabel
|
|
3013
|
+
const stepLabel = entry.rowLabel;
|
|
2533
3014
|
const contextBadge = contextModeBadge(theme, r.context);
|
|
3015
|
+
const labelPrefix = stepLabel ? `${stepLabel}: ` : "";
|
|
2534
3016
|
const stepHeader = rRunning
|
|
2535
|
-
? `${rowPresentation.glyph} ${
|
|
2536
|
-
: `${rowPresentation.glyph} ${
|
|
3017
|
+
? `${rowPresentation.glyph} ${labelPrefix}${theme.bold(theme.fg("warning", agentName))}${contextBadge}${modelDisplay}${stats} ${theme.fg("dim", "·")} ${rowPresentation.label}`
|
|
3018
|
+
: `${rowPresentation.glyph} ${labelPrefix}${theme.bold(agentName)}${contextBadge}${modelDisplay}${stats} ${theme.fg("dim", "·")} ${rowPresentation.label}`;
|
|
2537
3019
|
const toolCallLines = getToolCallLines(r, expanded);
|
|
2538
3020
|
c.addChild(new Text(fit(stepHeader), 0, 0));
|
|
2539
3021
|
|
|
@@ -2547,6 +3029,9 @@ export function renderSubagentResult(
|
|
|
2547
3029
|
if (outputTarget) {
|
|
2548
3030
|
c.addChild(new Text(fit(theme.fg("dim", ` output: ${outputTarget}`)), 0, 0));
|
|
2549
3031
|
}
|
|
3032
|
+
if (!rRunning && (r.exitCode !== 0 || r.interrupted || r.detached || r.stopped)) {
|
|
3033
|
+
c.addChild(new Text(fit(theme.fg(r.exitCode !== 0 ? "error" : "dim", ` ⎿ ${resultStatusLine(r, resultOutput)}`)), 0, 0));
|
|
3034
|
+
}
|
|
2550
3035
|
|
|
2551
3036
|
if (r.skills?.length) {
|
|
2552
3037
|
c.addChild(new Text(fit(theme.fg("dim", ` skills: ${r.skills.join(", ")}`)), 0, 0));
|