pi-subagents 0.58.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 +102 -0
- package/docs/agents.md +5 -3
- package/docs/configuration.md +4 -4
- package/docs/extension-api.md +1 -1
- package/docs/models.md +24 -1
- package/docs/observability.md +1 -1
- package/docs/tool-reference.md +93 -5
- package/docs/workflows.md +94 -3
- package/package.json +3 -1
- package/prompts/review-loop.md +2 -2
- 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 +52 -7
- package/skills/pi-subagents/references/management-authoring-rpc.md +3 -4
- package/skills/pi-subagents/references/multi-lane-orchestration.md +13 -1
- package/skills/pi-subagents/references/prompting-and-roles.md +54 -26
- package/skills/pi-subagents/references/review-and-validation.md +73 -0
- package/src/agents/agent-management.ts +159 -42
- package/src/agents/agent-serializer.ts +4 -2
- package/src/agents/agents.ts +67 -26
- package/src/agents/runtime-agent-registry.ts +9 -16
- package/src/api/background-work.ts +5 -1
- package/src/api/delegation.ts +0 -7
- package/src/api/preflight.ts +6 -9
- package/src/api/shared-types.ts +2 -0
- package/src/extension/fanout-child.ts +5 -3
- package/src/extension/index.ts +51 -23
- package/src/extension/public-execution.ts +16 -2
- package/src/extension/schemas.ts +36 -10
- package/src/extension/tool-description.ts +24 -5
- package/src/intercom/result-intercom.ts +2 -0
- package/src/profiles/profiles.ts +5 -6
- package/src/runs/background/active-async-capacity.ts +2 -2
- package/src/runs/background/async-execution.ts +52 -37
- package/src/runs/background/async-job-tracker.ts +17 -12
- package/src/runs/background/async-resume.ts +23 -25
- package/src/runs/background/async-status-snapshot.ts +23 -261
- package/src/runs/background/async-status.ts +110 -8
- package/src/runs/background/chain-append.ts +6 -3
- package/src/runs/background/chain-root-attachment.ts +60 -8
- package/src/runs/background/control-channel.ts +3 -2
- package/src/runs/background/fleet-view.ts +21 -11
- package/src/runs/background/notify.ts +158 -6
- package/src/runs/background/result-files.ts +2 -1
- package/src/runs/background/result-watcher.ts +2 -0
- package/src/runs/background/resume-guidance.ts +1 -1
- package/src/runs/background/retained-children.ts +1 -1
- package/src/runs/background/run-status.ts +30 -9
- package/src/runs/background/scheduled-runs.ts +86 -7
- package/src/runs/background/stale-run-reconciler.ts +10 -4
- package/src/runs/background/steering.ts +4 -14
- package/src/runs/background/subagent-runner.ts +351 -358
- package/src/runs/background/subagent-wait.ts +68 -12
- package/src/runs/background/terminal-run-index.ts +1 -1
- package/src/runs/background/wait-completions.ts +25 -1
- package/src/runs/background/wait-config.ts +23 -9
- package/src/runs/background/wait-tool.ts +9 -2
- package/src/runs/foreground/async-steering-action.ts +2 -2
- package/src/runs/foreground/execution.ts +125 -122
- package/src/runs/foreground/foreground-control.ts +3 -0
- package/src/runs/foreground/foreground-history.ts +1 -0
- package/src/runs/foreground/subagent-executor.ts +576 -240
- package/src/runs/foreground/workflow-detach-reconcile.ts +99 -200
- package/src/runs/shared/abort-recovery.ts +119 -0
- package/src/runs/shared/async-status-projection.ts +597 -0
- package/src/runs/shared/background-process-options.ts +9 -0
- package/src/runs/shared/child-identity.ts +19 -4
- package/src/runs/shared/child-launch-plan.ts +151 -0
- package/src/runs/shared/completion-evidence.ts +89 -0
- package/src/runs/shared/completion-guard.ts +1 -1
- package/src/runs/shared/dynamic-fanout.ts +2 -2
- package/src/runs/shared/host-step-status.ts +230 -0
- package/src/runs/shared/lane-metadata.ts +105 -0
- package/src/runs/shared/mcp-config-sources.ts +42 -6
- package/src/runs/shared/mcp-direct-tool-allowlist.ts +93 -143
- package/src/runs/shared/mcp-direct-tool-grant.ts +194 -0
- package/src/runs/shared/model-fallback.ts +9 -2
- package/src/runs/shared/mutation-evidence.ts +52 -3
- package/src/runs/shared/nested-events.ts +6 -2
- package/src/runs/shared/nested-render.ts +7 -3
- package/src/runs/shared/parallel-handoff.ts +419 -7
- package/src/runs/shared/parallel-utils.ts +7 -0
- package/src/runs/shared/pi-args.ts +67 -3
- package/src/runs/shared/single-output.ts +72 -22
- package/src/runs/shared/subagent-prompt-runtime.ts +33 -6
- package/src/runs/shared/workflow-graph.ts +15 -0
- package/src/runs/shared/worktree-cleanup-plan.ts +847 -0
- package/src/runs/shared/worktree.ts +18 -0
- package/src/shared/child-session-name.ts +46 -0
- package/src/shared/extension-context.ts +24 -0
- package/src/shared/formatters.ts +5 -2
- package/src/shared/launch-contract.ts +1 -1
- package/src/shared/settings.ts +9 -103
- package/src/shared/types.ts +232 -30
- package/src/shared/utils.ts +35 -55
- package/src/slash/delegation-adapters.ts +1 -8
- package/src/slash/delegation-request.ts +0 -4
- package/src/slash/slash-bridge.ts +1 -2
- package/src/slash/slash-commands.ts +369 -90
- package/src/slash/slash-live-state.ts +22 -11
- package/src/tui/fleet-status.ts +134 -75
- package/src/tui/fleet.ts +11 -5
- package/src/tui/render-helpers.ts +31 -0
- package/src/tui/render.ts +895 -121
- package/src/watchdog/change-signature.ts +40 -1
- package/src/watchdog/turn-delta.ts +1 -1
- package/src/workflows/chat-progress.ts +6 -3
- package/src/workflows/host-command.ts +235 -0
- package/src/workflows/scripted-workflow.ts +503 -38
- package/src/workflows/workflow-child-summary.ts +9 -5
- package/src/workflows/workflow-preflight.ts +270 -0
- package/src/workflows/workflow-receipt.ts +43 -4
- package/src/workflows/workflow-settlement.ts +246 -0
- package/src/runs/shared/turn-budget.ts +0 -98
package/src/tui/render.ts
CHANGED
|
@@ -13,6 +13,8 @@ import {
|
|
|
13
13
|
type AsyncJobStep,
|
|
14
14
|
type AsyncParallelGroupStatus,
|
|
15
15
|
type Details,
|
|
16
|
+
type HostStepState,
|
|
17
|
+
type HostStepVerdict,
|
|
16
18
|
type NestedRunSummary,
|
|
17
19
|
type NestedStepSummary,
|
|
18
20
|
type WorkflowNodeStatus,
|
|
@@ -29,8 +31,13 @@ import { flatToLogicalStepIndex } from "../runs/background/parallel-groups.ts";
|
|
|
29
31
|
import { formatNestedAggregate } from "../runs/shared/nested-render.ts";
|
|
30
32
|
import { aggregateStepStatus, formatActivityLabel, formatAgentRunningLabel, formatParallelOutcome } from "../shared/status-format.ts";
|
|
31
33
|
import { contextModeBadge, contextModePrefix } from "../runs/shared/context-mode.ts";
|
|
34
|
+
import { shouldSuppressSingleStep, stripRepeatedAgentPrefix, withDuplicateLabelDiscriminators } from "./render-helpers.ts";
|
|
32
35
|
import { buildWorkflowChatProgressRows, type WorkflowChatProgressRow } from "../workflows/chat-progress.ts";
|
|
36
|
+
import { formatWorkflowPreflight, formatWorkflowPreflightPlanSummary, formatWorkflowPreflightWarningSummary, formatWorkflowPreflightWarnings } from "../workflows/workflow-preflight.ts";
|
|
33
37
|
import { encodeAsyncStatusSnapshotWidget } from "../runs/background/async-status-snapshot.ts";
|
|
38
|
+
import { projectAsyncWorkflowRows, type AsyncStatusWorkflowRow } from "../runs/shared/async-status-projection.ts";
|
|
39
|
+
import { hostStepReportName, hostStepVerdictLabel } from "../runs/shared/host-step-status.ts";
|
|
40
|
+
import { workflowGraphStageNodes } from "../runs/shared/workflow-graph.ts";
|
|
34
41
|
|
|
35
42
|
type Theme = ExtensionContext["ui"]["theme"];
|
|
36
43
|
|
|
@@ -187,7 +194,10 @@ function runningSeed(...values: Array<number | undefined>): number | undefined {
|
|
|
187
194
|
|
|
188
195
|
function runningGlyph(seed?: number): string {
|
|
189
196
|
if (seed === undefined) return STATIC_RUNNING_GLYPH;
|
|
190
|
-
|
|
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]!;
|
|
191
201
|
}
|
|
192
202
|
|
|
193
203
|
function animatedSeed(seed: number | undefined, frame: number | undefined): number | undefined {
|
|
@@ -254,6 +264,7 @@ const noisyStatusPatterns = [
|
|
|
254
264
|
/^(?:checking|fetching|reading|inspecting|verifying|collecting|confirming|polling)\b/i,
|
|
255
265
|
/^(?:async\s+subagent\s+)?[\w.-]+\s*·\s*(?:step|agent)\s+\d+\/\d+\s*·/i,
|
|
256
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,
|
|
257
268
|
/^Press\s+\S+\s+for\s+live\s+detail$/i,
|
|
258
269
|
/^output:\s+.+\/async-subagent-runs\//i,
|
|
259
270
|
];
|
|
@@ -266,6 +277,12 @@ function oneLine(text: string): string {
|
|
|
266
277
|
|
|
267
278
|
const COMPACT_TASK_MAX_CHARS = 96;
|
|
268
279
|
|
|
280
|
+
/** Display label for a child run: the derived session name (agent + task
|
|
281
|
+
* excerpt) when the launcher provided one, else the bare agent name. */
|
|
282
|
+
function childDisplayName(result: { agent?: string; sessionName?: string } | undefined, fallback = "subagent"): string {
|
|
283
|
+
return result?.sessionName?.trim() || result?.agent || fallback;
|
|
284
|
+
}
|
|
285
|
+
|
|
269
286
|
export function compactTaskText(task: string | undefined, label?: string): string | undefined {
|
|
270
287
|
const taskText = task?.trim();
|
|
271
288
|
const labelText = label?.trim();
|
|
@@ -278,8 +295,292 @@ export function compactTaskText(task: string | undefined, label?: string): strin
|
|
|
278
295
|
return previewDisplayText(normalized, COMPACT_TASK_MAX_CHARS);
|
|
279
296
|
}
|
|
280
297
|
|
|
298
|
+
export interface AsyncLaneProjection {
|
|
299
|
+
label?: string;
|
|
300
|
+
role: string;
|
|
301
|
+
phase?: string;
|
|
302
|
+
state: AsyncJobState["status"] | AsyncJobStep["status"];
|
|
303
|
+
gate?: string;
|
|
304
|
+
next?: string;
|
|
305
|
+
output?: string;
|
|
306
|
+
workspace?: string;
|
|
307
|
+
ref: string;
|
|
308
|
+
chips: string[];
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
const LANE_VALUE_MAX_CHARS = 48;
|
|
312
|
+
|
|
313
|
+
function boundedLaneValue(value: string | undefined, maxChars = LANE_VALUE_MAX_CHARS): string | undefined {
|
|
314
|
+
if (!value?.trim() || value.trim() === PROMPT_REDACTED) return undefined;
|
|
315
|
+
return previewDisplayText(oneLine(value), maxChars);
|
|
316
|
+
}
|
|
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
|
+
|
|
433
|
+
function laneStepForJob(job: AsyncJobState): AsyncJobStep | undefined {
|
|
434
|
+
const steps = workflowWidgetSteps(job);
|
|
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
|
+
}
|
|
441
|
+
if (job.currentStep !== undefined) {
|
|
442
|
+
const current = steps[job.currentStep];
|
|
443
|
+
if (current && (current.index === undefined || current.index === job.currentStep)) return current;
|
|
444
|
+
// Active parallel groups retain flat indices while exposing only the group slice.
|
|
445
|
+
const indexedCurrent = steps.find((step) => step.index === job.currentStep);
|
|
446
|
+
if (indexedCurrent) return indexedCurrent;
|
|
447
|
+
return steps[0];
|
|
448
|
+
}
|
|
449
|
+
return steps.find((step) => step.status === "running")
|
|
450
|
+
?? steps.find((step) => step.status === "pending")
|
|
451
|
+
?? steps.at(-1);
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
function laneTraceForJob(job: AsyncJobState): NonNullable<AsyncJobState["workflow"]>["trace"][number] | undefined {
|
|
455
|
+
return Array.isArray(job.workflow?.trace) ? job.workflow.trace.at(-1) : undefined;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
function laneGate(step: AsyncJobStep | undefined): string | undefined {
|
|
459
|
+
const review = step?.review?.status ?? step?.acceptance?.reviewResult?.status;
|
|
460
|
+
if (review === "blockers") return "review blockers";
|
|
461
|
+
if (review === "review-required") return "review required";
|
|
462
|
+
if (review === "reviewed") return "reviewed";
|
|
463
|
+
const acceptance = step?.acceptance?.status;
|
|
464
|
+
if (acceptance === "review-required") return "acceptance review";
|
|
465
|
+
if (acceptance === "checked" || acceptance === "verified" || acceptance === "accepted") return "acceptance";
|
|
466
|
+
return undefined;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
function laneNextAction(state: AsyncLaneProjection["state"], step: AsyncJobStep | undefined, output: string | undefined, gate: string | undefined): string | undefined {
|
|
470
|
+
if (step?.watchdog?.phase === "stale") return "inspect stale state";
|
|
471
|
+
if (step?.toolBudgetBlocked === true || step?.turnBudgetExceeded === true) return "inspect blocked state";
|
|
472
|
+
if (gate === "review blockers") return "resolve review blockers";
|
|
473
|
+
if (gate === "review required" || gate === "acceptance review") return "review output";
|
|
474
|
+
if (step?.activityState === "needs_attention") return "inspect attention";
|
|
475
|
+
if (state === "queued" || state === "pending") return "await launch";
|
|
476
|
+
if (state === "failed" || state === "rejected") return "inspect failure";
|
|
477
|
+
if (state === "partial") return "inspect partial state";
|
|
478
|
+
if (state === "paused") return "inspect paused state";
|
|
479
|
+
if (state === "stopped") return "inspect stopped state";
|
|
480
|
+
if ((state === "complete" || state === "completed") && gate === "reviewed") return "ready";
|
|
481
|
+
if ((state === "complete" || state === "completed") && output) return "inspect output";
|
|
482
|
+
return undefined;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
function isTerminalLaneState(state: AsyncJobState["status"]): boolean {
|
|
486
|
+
return state !== "queued" && state !== "running";
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
/** Project already-loaded async status facts into one bounded, render-only lane row. */
|
|
490
|
+
export function projectAsyncLane(job: AsyncJobState, selectedStep = laneStepForJob(job)): AsyncLaneProjection | undefined {
|
|
491
|
+
const trace = laneTraceForJob(job);
|
|
492
|
+
const workspace = job.cwd ? boundedLaneValue(shortenPath(job.cwd)) : undefined;
|
|
493
|
+
const label = compactTaskText(selectedStep?.description, selectedStep?.label)
|
|
494
|
+
?? boundedLaneValue(trace?.label)
|
|
495
|
+
?? (workspace ? undefined : boundedLaneValue(selectedStep?.workflowKey ?? job.workflowKey));
|
|
496
|
+
const role = boundedLaneValue(selectedStep?.agent ?? trace?.agent ?? job.agents?.[0] ?? widgetJobName(job), 32) ?? "subagent";
|
|
497
|
+
const phase = boundedLaneValue(selectedStep?.phase ?? trace?.phase);
|
|
498
|
+
const gate = laneGate(selectedStep);
|
|
499
|
+
const output = boundedLaneValue(selectedStep?.outputName);
|
|
500
|
+
const ref = boundedLaneValue(selectedStep?.workflowKey ?? job.workflowKey ?? job.asyncId.slice(0, 8), 24) ?? job.asyncId.slice(0, 8);
|
|
501
|
+
const chips = [
|
|
502
|
+
selectedStep?.context,
|
|
503
|
+
selectedStep?.structured ? "structured" : undefined,
|
|
504
|
+
selectedStep?.activityState === "active_long_running" ? "long-running" : undefined,
|
|
505
|
+
selectedStep?.activityState === "needs_attention" ? "attention" : undefined,
|
|
506
|
+
selectedStep?.watchdog?.phase === "stale" ? "stale" : undefined,
|
|
507
|
+
selectedStep?.toolBudgetBlocked === true || selectedStep?.turnBudgetExceeded === true ? "blocked" : undefined,
|
|
508
|
+
].filter((chip): chip is string => Boolean(chip));
|
|
509
|
+
const state = isTerminalLaneState(job.status) ? job.status : selectedStep?.status ?? job.status;
|
|
510
|
+
const next = laneNextAction(state, selectedStep, output, gate);
|
|
511
|
+
if (!label && !phase && !gate && !output && !selectedStep?.workflowKey && !job.workflowKey && !trace?.label && !trace?.phase) return undefined;
|
|
512
|
+
return { ...(label ? { label } : {}), role, ...(phase ? { phase } : {}), state, ...(gate ? { gate } : {}), ...(next ? { next } : {}), ...(output ? { output } : {}), ...(workspace ? { workspace } : {}), ref, chips };
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
function laneStateLabel(state: AsyncLaneProjection["state"], theme: Theme): string {
|
|
516
|
+
if (state === "running") return theme.fg("accent", "running");
|
|
517
|
+
if (state === "queued" || state === "pending") return theme.fg("muted", state);
|
|
518
|
+
if (state === "complete" || state === "completed") return theme.fg("success", "complete");
|
|
519
|
+
if (state === "failed" || state === "rejected") return theme.fg("error", state === "rejected" ? "rejected" : "failed");
|
|
520
|
+
return theme.fg("warning", state);
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
function formatLaneProjection(lane: AsyncLaneProjection, theme: Theme): string {
|
|
524
|
+
const label = boundedLaneValue(lane.label, 56);
|
|
525
|
+
const identity = [label, lane.role ? `role:${lane.role}` : undefined].filter(Boolean).join(" · ");
|
|
526
|
+
return `${identity ? theme.bold(identity) : theme.bold(lane.role)} · ${laneStateLabel(lane.state, theme)}`;
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
function formatLaneChip(chip: string, theme: Theme): string {
|
|
530
|
+
const text = `[${chip}]`;
|
|
531
|
+
if (chip === "blocked") return theme.fg("error", text);
|
|
532
|
+
if (chip === "stale") return theme.fg("warning", text);
|
|
533
|
+
return text;
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
function formatLaneProjectionDetails(lane: AsyncLaneProjection, theme: Theme): string | undefined {
|
|
537
|
+
const details = [
|
|
538
|
+
lane.phase ? `phase:${lane.phase}` : undefined,
|
|
539
|
+
lane.gate ? `gate:${lane.gate}` : undefined,
|
|
540
|
+
lane.next ? `next:${lane.next}` : undefined,
|
|
541
|
+
lane.output ? `out:${lane.output}` : undefined,
|
|
542
|
+
lane.workspace ? `workspace:${lane.workspace}` : lane.ref ? `ref:${lane.ref}` : undefined,
|
|
543
|
+
].filter(Boolean);
|
|
544
|
+
const chips = lane.chips.map((chip) => formatLaneChip(chip, theme));
|
|
545
|
+
return [...(details.length ? [theme.fg("dim", details.join(" · "))] : []), ...chips].join(" · ") || undefined;
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
function formatLaneProjectionLines(lane: AsyncLaneProjection, theme: Theme, indent: string): string[] {
|
|
549
|
+
const details = formatLaneProjectionDetails(lane, theme);
|
|
550
|
+
return [
|
|
551
|
+
`${indent}${formatLaneProjection(lane, theme)}`,
|
|
552
|
+
...(details ? [`${indent} ${details}`] : []),
|
|
553
|
+
];
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
function laneRenderKey(job: AsyncJobState): unknown {
|
|
557
|
+
const lane = projectAsyncLane(job);
|
|
558
|
+
return lane ? [lane.label, lane.role, lane.phase, lane.state, lane.gate, lane.next, lane.output, lane.workspace, lane.ref, lane.chips] : undefined;
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
function widgetLaneDetailLines(job: AsyncJobState, theme: Theme): string[] {
|
|
562
|
+
if (job.steps?.length && (job.mode === "parallel" || job.mode === "chain")) return [];
|
|
563
|
+
const lane = projectAsyncLane(job);
|
|
564
|
+
return lane ? formatLaneProjectionLines(lane, theme, " ") : [];
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
function workflowPreflightLines(job: AsyncJobState, expanded = false): string[] {
|
|
568
|
+
if (job.mode !== "workflow" || !job.preflight) return [];
|
|
569
|
+
if (!expanded) {
|
|
570
|
+
const warning = formatWorkflowPreflightWarningSummary(job.workflow?.preflightWarnings, { indent: " ", hint: "expand for debug" });
|
|
571
|
+
return [
|
|
572
|
+
formatWorkflowPreflightPlanSummary(job.preflight, { indent: " " }),
|
|
573
|
+
...(warning ? [warning] : []),
|
|
574
|
+
];
|
|
575
|
+
}
|
|
576
|
+
return [
|
|
577
|
+
...formatWorkflowPreflight(job.preflight, { indent: " " }).split("\n"),
|
|
578
|
+
...(job.workflow?.preflightWarnings ? formatWorkflowPreflightWarnings(job.workflow.preflightWarnings, { indent: " " }).split("\n") : []),
|
|
579
|
+
];
|
|
580
|
+
}
|
|
581
|
+
|
|
281
582
|
function workflowLabelForResult(details: Details, resultIndex: number): string | undefined {
|
|
282
|
-
const flatIndex = details
|
|
583
|
+
const flatIndex = foregroundResultIndex(details, resultIndex);
|
|
283
584
|
const visit = (nodes: NonNullable<Details["workflowGraph"]>["nodes"]): string | undefined => {
|
|
284
585
|
for (const node of nodes) {
|
|
285
586
|
if (node.flatIndex === flatIndex && node.label.trim()) return node.label;
|
|
@@ -291,6 +592,52 @@ function workflowLabelForResult(details: Details, resultIndex: number): string |
|
|
|
291
592
|
return details.workflowGraph ? visit(details.workflowGraph.nodes) : undefined;
|
|
292
593
|
}
|
|
293
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
|
+
|
|
294
641
|
function hasLiveOutputSignal(line: string): boolean {
|
|
295
642
|
const clean = oneLine(line);
|
|
296
643
|
return liveOutputWordSignalPattern.test(clean) || liveOutputCodeSignalPattern.test(clean);
|
|
@@ -474,7 +821,7 @@ function resultStatusLine(result: Details["results"][number], output: string): s
|
|
|
474
821
|
|
|
475
822
|
type ResultPresentation = {
|
|
476
823
|
glyph: string;
|
|
477
|
-
label: "running" | "detached" | "stopped" | "paused" | "failed" | "completed";
|
|
824
|
+
label: "running" | "detached" | "stopped" | "paused" | "failed" | "partial" | "completed";
|
|
478
825
|
tone: "accent" | "warning" | "error" | "success";
|
|
479
826
|
};
|
|
480
827
|
|
|
@@ -484,6 +831,7 @@ function semanticResultPresentation(input: {
|
|
|
484
831
|
stopped?: boolean;
|
|
485
832
|
interrupted?: boolean;
|
|
486
833
|
failed?: boolean;
|
|
834
|
+
partial?: boolean;
|
|
487
835
|
completedWithoutOutput?: boolean;
|
|
488
836
|
seed?: number;
|
|
489
837
|
frame?: number;
|
|
@@ -496,6 +844,7 @@ function semanticResultPresentation(input: {
|
|
|
496
844
|
if (input.stopped) return { glyph: "■", label: "stopped", tone: "warning" };
|
|
497
845
|
if (input.interrupted) return { glyph: "■", label: "paused", tone: "warning" };
|
|
498
846
|
if (input.failed) return { glyph: "✗", label: "failed", tone: "error" };
|
|
847
|
+
if (input.partial) return { glyph: "■", label: "partial", tone: "warning" };
|
|
499
848
|
return { glyph: "✓", label: "completed", tone: input.completedWithoutOutput ? "warning" : "success" };
|
|
500
849
|
}
|
|
501
850
|
|
|
@@ -574,6 +923,7 @@ function widgetStepRenderKey(step: AsyncJobStep, index: number, expanded = false
|
|
|
574
923
|
return [
|
|
575
924
|
step.index ?? index,
|
|
576
925
|
step.agent,
|
|
926
|
+
step.sessionName,
|
|
577
927
|
step.workflowKey,
|
|
578
928
|
step.phase,
|
|
579
929
|
step.label,
|
|
@@ -593,6 +943,23 @@ function widgetStepRenderKey(step: AsyncJobStep, index: number, expanded = false
|
|
|
593
943
|
step.model,
|
|
594
944
|
step.thinking,
|
|
595
945
|
step.context,
|
|
946
|
+
step.description,
|
|
947
|
+
step.outputName,
|
|
948
|
+
step.structured,
|
|
949
|
+
step.acceptance?.status,
|
|
950
|
+
step.acceptance?.reviewResult?.status,
|
|
951
|
+
step.review?.status,
|
|
952
|
+
step.toolBudgetBlocked,
|
|
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,
|
|
962
|
+
step.watchdog?.phase,
|
|
596
963
|
step.error,
|
|
597
964
|
expanded ? expandedStepActivityRenderKey(step) : undefined,
|
|
598
965
|
nestedRenderKey(step.children, expanded),
|
|
@@ -604,6 +971,7 @@ function nestedRenderKey(children: NestedRunSummary[] | undefined, expanded = fa
|
|
|
604
971
|
child.id,
|
|
605
972
|
child.state,
|
|
606
973
|
child.agent,
|
|
974
|
+
child.sessionName,
|
|
607
975
|
child.model,
|
|
608
976
|
child.thinking,
|
|
609
977
|
child.activityState,
|
|
@@ -623,6 +991,22 @@ function nestedRenderKey(children: NestedRunSummary[] | undefined, expanded = fa
|
|
|
623
991
|
]);
|
|
624
992
|
}
|
|
625
993
|
|
|
994
|
+
function hostStepRenderKey(row: AsyncStatusWorkflowRow): unknown[] {
|
|
995
|
+
return [
|
|
996
|
+
row.kind,
|
|
997
|
+
row.name,
|
|
998
|
+
row.state,
|
|
999
|
+
row.provider,
|
|
1000
|
+
row.role,
|
|
1001
|
+
row.verdict,
|
|
1002
|
+
row.reasonCode,
|
|
1003
|
+
row.detail,
|
|
1004
|
+
row.target,
|
|
1005
|
+
row.freshness,
|
|
1006
|
+
row.reportPath,
|
|
1007
|
+
];
|
|
1008
|
+
}
|
|
1009
|
+
|
|
626
1010
|
export function widgetRenderKey(job: AsyncJobState, expanded = false): string {
|
|
627
1011
|
return JSON.stringify({
|
|
628
1012
|
asyncDir: job.asyncDir,
|
|
@@ -639,14 +1023,24 @@ export function widgetRenderKey(job: AsyncJobState, expanded = false): string {
|
|
|
639
1023
|
currentStep: job.currentStep,
|
|
640
1024
|
chainStepCount: job.chainStepCount,
|
|
641
1025
|
parallelGroups: job.parallelGroups,
|
|
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,
|
|
1031
|
+
preflight: expanded ? job.preflight : job.preflight ? formatWorkflowPreflightPlanSummary(job.preflight) : undefined,
|
|
1032
|
+
preflightWarnings: expanded ? job.workflow?.preflightWarnings : job.workflow?.preflightWarnings?.length || undefined,
|
|
642
1033
|
steps: job.steps?.map((step, index) => widgetStepRenderKey(step, index, expanded)),
|
|
643
1034
|
nestedChildren: nestedRenderKey(job.nestedChildren, expanded),
|
|
1035
|
+
lane: laneRenderKey(job),
|
|
644
1036
|
stepsTotal: job.stepsTotal,
|
|
645
1037
|
runningSteps: job.runningSteps,
|
|
646
1038
|
completedSteps: job.completedSteps,
|
|
647
1039
|
activeParallelGroup: job.activeParallelGroup,
|
|
648
1040
|
startedAt: job.startedAt,
|
|
649
1041
|
updatedAt: job.updatedAt,
|
|
1042
|
+
timedOut: job.timedOut,
|
|
1043
|
+
stopped: job.stopped,
|
|
650
1044
|
totalTokens: job.totalTokens,
|
|
651
1045
|
});
|
|
652
1046
|
}
|
|
@@ -666,6 +1060,65 @@ function widgetJobName(job: AsyncJobState): string {
|
|
|
666
1060
|
return job.mode ?? "subagent";
|
|
667
1061
|
}
|
|
668
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
|
+
|
|
669
1122
|
function widgetActivity(job: AsyncJobState): string {
|
|
670
1123
|
const facts: string[] = [];
|
|
671
1124
|
if (job.currentTool && job.currentToolStartedAt !== undefined && job.updatedAt !== undefined) facts.push(`${job.currentTool} ${formatDuration(Math.max(0, job.updatedAt - job.currentToolStartedAt))}`);
|
|
@@ -681,6 +1134,7 @@ function widgetActivity(job: AsyncJobState): string {
|
|
|
681
1134
|
if (job.status === "queued") return "queued…";
|
|
682
1135
|
if (job.status === "paused") return "Paused";
|
|
683
1136
|
if (job.status === "stopped") return "Stopped";
|
|
1137
|
+
if (job.status === "partial") return "Partial";
|
|
684
1138
|
if (job.status === "failed") return "Failed";
|
|
685
1139
|
return "Done";
|
|
686
1140
|
}
|
|
@@ -773,8 +1227,7 @@ function widgetChainDetails(job: AsyncJobState, theme: Theme, expanded = false,
|
|
|
773
1227
|
for (const span of buildAsyncChainStepSpans(total, job.steps.length, job.parallelGroups)) {
|
|
774
1228
|
const steps = job.steps.slice(span.start, span.start + span.count);
|
|
775
1229
|
if (span.isParallel) {
|
|
776
|
-
|
|
777
|
-
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));
|
|
778
1231
|
continue;
|
|
779
1232
|
}
|
|
780
1233
|
const step = steps[0];
|
|
@@ -791,6 +1244,8 @@ function widgetParallelAgentDetails(job: AsyncJobState, theme: Theme, expanded =
|
|
|
791
1244
|
if (!job.steps?.length) return [];
|
|
792
1245
|
if (job.mode !== "parallel" && job.mode !== "chain") return [];
|
|
793
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));
|
|
794
1249
|
const total = job.stepsTotal ?? job.steps.length;
|
|
795
1250
|
const lines: string[] = [];
|
|
796
1251
|
for (const [index, step] of job.steps.entries()) {
|
|
@@ -799,8 +1254,10 @@ function widgetParallelAgentDetails(job: AsyncJobState, theme: Theme, expanded =
|
|
|
799
1254
|
const itemTitle = job.mode === "parallel" || job.activeParallelGroup ? "Agent" : "Step";
|
|
800
1255
|
const modelDisplay = modelThinkingBadge(theme, step.model, step.thinking);
|
|
801
1256
|
const label = compactTaskText(step.description, step.label);
|
|
802
|
-
const display = label ? `${label} (${step.agent})` : step.agent;
|
|
1257
|
+
const display = step.sessionName?.trim() || (label ? `${label} (${step.agent})` : step.agent);
|
|
803
1258
|
lines.push(` ${theme.fg("dim", `${marker} ${widgetStepGlyph(step.status, theme, widgetStepRunningSeed(step, index), frame)} ${itemTitle} ${index + 1}/${total}: ${display} · ${widgetStepStatus(step.status, theme)}${modelDisplay}${activity ? ` · ${activity}` : ""}`)}`);
|
|
1259
|
+
const lane = projectAsyncLane(job, step);
|
|
1260
|
+
if (lane) lines.push(...formatLaneProjectionLines(lane, theme, " "));
|
|
804
1261
|
for (const nestedLine of formatNestedWidgetLines(step.children, theme, width, expanded, job.updatedAt, expanded ? 8 : 6)) lines.push(` ${nestedLine}`);
|
|
805
1262
|
}
|
|
806
1263
|
return lines;
|
|
@@ -879,6 +1336,127 @@ function buildAsyncChainStepSpans(total: number, stepCount: number, parallelGrou
|
|
|
879
1336
|
return spans;
|
|
880
1337
|
}
|
|
881
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
|
+
|
|
882
1460
|
function isDoneResult(result: Details["results"][number]): boolean {
|
|
883
1461
|
const status = result.progress?.status;
|
|
884
1462
|
if (status === "completed") return true;
|
|
@@ -897,41 +1475,83 @@ interface ChainRenderResultEntry {
|
|
|
897
1475
|
rowNumber: number;
|
|
898
1476
|
rowLabel?: string;
|
|
899
1477
|
agentName: string;
|
|
1478
|
+
displayIndex: number;
|
|
1479
|
+
isParallel?: boolean;
|
|
900
1480
|
}
|
|
901
1481
|
|
|
902
|
-
interface
|
|
903
|
-
kind: "
|
|
904
|
-
rowNumber: number;
|
|
1482
|
+
interface ChainRenderGroupEntry {
|
|
1483
|
+
kind: "group";
|
|
905
1484
|
stepLabel: string;
|
|
906
|
-
|
|
1485
|
+
groupLabel?: string;
|
|
907
1486
|
status: WorkflowNodeStatus;
|
|
908
1487
|
error?: string;
|
|
909
1488
|
}
|
|
910
1489
|
|
|
911
|
-
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
|
+
}
|
|
912
1514
|
|
|
913
1515
|
function buildChainRenderEntries(details: Details, label: MultiProgressLabel): ChainRenderEntry[] | undefined {
|
|
914
1516
|
if (details.mode !== "chain" || !label.hasParallelInChain || label.showActiveGroupOnly) return undefined;
|
|
915
1517
|
const entries: ChainRenderEntry[] = [];
|
|
916
1518
|
for (const span of buildChainStepSpans(details)) {
|
|
917
|
-
if (span.isParallel
|
|
1519
|
+
if (span.isParallel) {
|
|
918
1520
|
entries.push({
|
|
919
|
-
kind: "
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
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),
|
|
924
1525
|
error: span.error,
|
|
925
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));
|
|
926
1544
|
continue;
|
|
927
1545
|
}
|
|
928
1546
|
for (let index = span.start; index < span.start + span.count; index++) {
|
|
1547
|
+
const result = details.results[index];
|
|
929
1548
|
entries.push({
|
|
930
1549
|
kind: "result",
|
|
931
1550
|
resultIndex: index,
|
|
932
|
-
rowNumber:
|
|
933
|
-
rowLabel:
|
|
934
|
-
agentName: details
|
|
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),
|
|
935
1555
|
});
|
|
936
1556
|
}
|
|
937
1557
|
}
|
|
@@ -947,6 +1567,7 @@ interface MultiProgressLabel {
|
|
|
947
1567
|
groupStartIndex: number;
|
|
948
1568
|
groupEndIndex: number;
|
|
949
1569
|
showActiveGroupOnly: boolean;
|
|
1570
|
+
logicalStepCount: number;
|
|
950
1571
|
}
|
|
951
1572
|
|
|
952
1573
|
function buildMultiProgressLabel(details: Pick<Details, "mode" | "results" | "progress" | "totalSteps" | "currentStepIndex" | "chainAgents" | "workflowGraph">, hasRunning: boolean): MultiProgressLabel {
|
|
@@ -982,7 +1603,7 @@ function buildMultiProgressLabel(details: Pick<Details, "mode" | "results" | "pr
|
|
|
982
1603
|
const headerLabel = hasRunning
|
|
983
1604
|
? `${formatAgentRunningLabel(running)} · ${done}/${totalCount} done`
|
|
984
1605
|
: `${done}/${totalCount} done`;
|
|
985
|
-
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 };
|
|
986
1607
|
}
|
|
987
1608
|
|
|
988
1609
|
if (activeParallelGroup) {
|
|
@@ -1010,7 +1631,7 @@ function buildMultiProgressLabel(details: Pick<Details, "mode" | "results" | "pr
|
|
|
1010
1631
|
const headerLabel = hasRunning
|
|
1011
1632
|
? `step ${currentStepIndex + 1}/${totalSteps} · parallel group: ${formatAgentRunningLabel(running)} · ${done}/${groupSize} done`
|
|
1012
1633
|
: `step ${currentStepIndex + 1}/${totalSteps} · parallel group: ${done}/${groupSize} done`;
|
|
1013
|
-
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 };
|
|
1014
1635
|
}
|
|
1015
1636
|
|
|
1016
1637
|
if (details.mode === "chain" && details.chainAgents?.length) {
|
|
@@ -1028,30 +1649,66 @@ function buildMultiProgressLabel(details: Pick<Details, "mode" | "results" | "pr
|
|
|
1028
1649
|
}).length;
|
|
1029
1650
|
const currentStep = details.currentStepIndex !== undefined ? details.currentStepIndex + 1 : Math.min(totalCount, doneLogical + (hasRunning ? 1 : 0));
|
|
1030
1651
|
const headerLabel = hasRunning ? `step ${currentStep}/${totalCount}` : `step ${doneLogical}/${totalCount}`;
|
|
1031
|
-
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 };
|
|
1032
1653
|
}
|
|
1033
1654
|
|
|
1034
1655
|
const totalCount = details.totalSteps ?? details.results.length;
|
|
1035
1656
|
const currentStep = details.currentStepIndex !== undefined ? details.currentStepIndex + 1 : Math.min(totalCount, details.results.filter(isDoneResult).length + (hasRunning ? 1 : 0));
|
|
1036
1657
|
const done = details.results.filter(isDoneResult).length;
|
|
1037
1658
|
const headerLabel = hasRunning ? `step ${currentStep}/${totalCount}` : `step ${done}/${totalCount}`;
|
|
1038
|
-
return { headerLabel, itemTitle, totalCount, hasParallelInChain, activeParallelGroup, groupStartIndex: 0, groupEndIndex: details.results.length, showActiveGroupOnly: false };
|
|
1039
|
-
}
|
|
1040
|
-
|
|
1041
|
-
function resultRowLabel(label: MultiProgressLabel,
|
|
1042
|
-
if (label.itemTitle === "Agent")
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
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;
|
|
1049
1697
|
}
|
|
1050
1698
|
|
|
1051
1699
|
function widgetStats(job: AsyncJobState, theme: Theme): string {
|
|
1052
1700
|
const parts: string[] = [];
|
|
1053
|
-
const
|
|
1054
|
-
|
|
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) {
|
|
1055
1712
|
const running = job.runningSteps ?? (job.status === "running" ? 1 : 0);
|
|
1056
1713
|
const done = job.completedSteps ?? (job.status === "complete" ? stepsTotal : 0);
|
|
1057
1714
|
if (job.mode === "parallel") {
|
|
@@ -1068,10 +1725,10 @@ function widgetStats(job: AsyncJobState, theme: Theme): string {
|
|
|
1068
1725
|
parts.push(`step ${logicalStep + 1}/${total} · parallel group: ${groupParts.join(" · ")}`);
|
|
1069
1726
|
}
|
|
1070
1727
|
} else if (job.currentStep !== undefined) {
|
|
1071
|
-
if (job.mode === "chain"
|
|
1728
|
+
if (job.mode === "chain") {
|
|
1072
1729
|
const total = job.chainStepCount ?? stepsTotal;
|
|
1073
|
-
parts.push(`step ${flatToLogicalStepIndex(job.currentStep, total, job.parallelGroups) + 1}/${total}`);
|
|
1074
|
-
} else {
|
|
1730
|
+
parts.push(`step ${flatToLogicalStepIndex(job.currentStep, total, job.parallelGroups ?? []) + 1}/${total}`);
|
|
1731
|
+
} else if (!isSingleChild) {
|
|
1075
1732
|
parts.push(`step ${job.currentStep + 1}/${stepsTotal}`);
|
|
1076
1733
|
}
|
|
1077
1734
|
} else if (stepsTotal > 1) {
|
|
@@ -1116,6 +1773,7 @@ function widgetOutputPath(job: AsyncJobState, step: NonNullable<AsyncJobState["s
|
|
|
1116
1773
|
}
|
|
1117
1774
|
|
|
1118
1775
|
function nestedRunName(run: NestedRunSummary): string {
|
|
1776
|
+
if (run.sessionName?.trim()) return run.sessionName.trim();
|
|
1119
1777
|
if (run.agent) return run.agent;
|
|
1120
1778
|
if (run.agents?.length) return formatWidgetAgents(run.agents);
|
|
1121
1779
|
return run.id;
|
|
@@ -1125,6 +1783,7 @@ function nestedStatusGlyph(state: NestedRunSummary["state"] | NestedStepSummary[
|
|
|
1125
1783
|
if (state === "running") return theme.fg("accent", runningGlyph(seed));
|
|
1126
1784
|
if (state === "complete" || state === "completed") return theme.fg("success", "✓");
|
|
1127
1785
|
if (state === "failed") return theme.fg("error", "✗");
|
|
1786
|
+
if (state === "partial") return theme.fg("warning", "■");
|
|
1128
1787
|
if (state === "paused") return theme.fg("warning", "■");
|
|
1129
1788
|
if (state === "stopped") return theme.fg("warning", "■");
|
|
1130
1789
|
return theme.fg("muted", "◦");
|
|
@@ -1172,6 +1831,7 @@ function nestedActivity(input: Pick<NestedRunSummary | NestedStepSummary, "activ
|
|
|
1172
1831
|
if (state === "queued" || state === "pending") return "queued…";
|
|
1173
1832
|
if (state === "paused") return "Paused";
|
|
1174
1833
|
if (state === "stopped") return "Stopped";
|
|
1834
|
+
if (state === "partial") return "Partial";
|
|
1175
1835
|
if (state === "failed") return "Failed";
|
|
1176
1836
|
return "Done";
|
|
1177
1837
|
}
|
|
@@ -1196,7 +1856,7 @@ function formatNestedWidgetLines(children: NestedRunSummary[] | undefined, theme
|
|
|
1196
1856
|
const activity = nestedActivity(step, state, snapshotNow ?? fallback);
|
|
1197
1857
|
const timestamp = "status" in step ? nestedStepTimestamp(step, fallback) : formatClockTime(nestedRunEventTime(step));
|
|
1198
1858
|
const error = step.error ? ` · ${step.error}` : "";
|
|
1199
|
-
const name = "status" in step ? step
|
|
1859
|
+
const name = "status" in step ? childDisplayName(step) : nestedRunName(step);
|
|
1200
1860
|
rows.push({
|
|
1201
1861
|
prefix,
|
|
1202
1862
|
text: `${nestedTimestampPrefix(timestamp)}${nestedStatusGlyph(state, theme)} ${name} · ${state}${modelThinking ? ` · ${modelThinking}` : ""}${activity ? ` · ${activity}` : ""}${error}`,
|
|
@@ -1258,7 +1918,7 @@ function formatNestedWidgetLines(children: NestedRunSummary[] | undefined, theme
|
|
|
1258
1918
|
for (const step of child.steps ?? []) {
|
|
1259
1919
|
if (lines.length >= lineBudget) return;
|
|
1260
1920
|
const modelThinking = formatModelThinking(step.model, step.thinking);
|
|
1261
|
-
lines.push(theme.fg("dim", `${prefix} ↳ ${nestedTimestampPrefix(nestedStepTimestamp(step, child.lastUpdate))}${nestedStatusGlyph(step.status, theme)} ${step
|
|
1921
|
+
lines.push(theme.fg("dim", `${prefix} ↳ ${nestedTimestampPrefix(nestedStepTimestamp(step, child.lastUpdate))}${nestedStatusGlyph(step.status, theme)} ${childDisplayName(step)} · ${step.status}${modelThinking ? ` · ${modelThinking}` : ""} · ${nestedActivity(step, step.status, snapshotNow ?? child.lastUpdate)}`));
|
|
1262
1922
|
append(step.children, depth + 1, `${prefix} `);
|
|
1263
1923
|
}
|
|
1264
1924
|
append(child.children, depth + 1, `${prefix} `);
|
|
@@ -1272,57 +1932,126 @@ function foregroundStyleWidgetStepLines(
|
|
|
1272
1932
|
job: AsyncJobState,
|
|
1273
1933
|
theme: Theme,
|
|
1274
1934
|
step: NonNullable<AsyncJobState["steps"]>[number],
|
|
1275
|
-
itemTitle: "Agent" | "Step",
|
|
1935
|
+
itemTitle: "Agent" | "Stage" | "Step",
|
|
1276
1936
|
index: number,
|
|
1277
1937
|
total: number,
|
|
1278
1938
|
expanded: boolean,
|
|
1279
1939
|
width: number,
|
|
1280
1940
|
frame?: number,
|
|
1941
|
+
options?: { rowLabel?: string; rowIndent?: string; detailIndent?: string; rowMarker?: string; includeCurrentPath?: boolean },
|
|
1281
1942
|
): string[] {
|
|
1943
|
+
const rowIndent = options?.rowIndent ?? " ";
|
|
1944
|
+
const detailIndent = options?.detailIndent ?? " ";
|
|
1282
1945
|
const status = widgetStepStatus(step.status, theme);
|
|
1283
1946
|
const stats = widgetStepStats(theme, step);
|
|
1284
1947
|
const modelDisplay = modelThinkingBadge(theme, step.model, step.thinking);
|
|
1285
|
-
const
|
|
1286
|
-
const
|
|
1287
|
-
|
|
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}` : ""}`];
|
|
1957
|
+
const lane = projectAsyncLane(job, step);
|
|
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}`)}`);
|
|
1288
1961
|
const activity = widgetStepActivityLine(step, width, expanded, job.updatedAt);
|
|
1289
|
-
|
|
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}`)}`);
|
|
1290
1967
|
for (const nestedLine of formatNestedWidgetLines(step.children, theme, width, expanded, job.updatedAt, expanded ? 12 : 6)) {
|
|
1291
|
-
lines.push(
|
|
1968
|
+
lines.push(`${detailIndent}${nestedLine}`);
|
|
1292
1969
|
}
|
|
1970
|
+
const error = step.error?.trim() || step.execution?.error?.trim();
|
|
1971
|
+
if (error) lines.push(`${detailIndent}${theme.fg("error", `error: ${oneLine(error)}`)}`);
|
|
1293
1972
|
if (step.status === "running") {
|
|
1294
|
-
if (!expanded) lines.push(
|
|
1973
|
+
if (!expanded) lines.push(`${detailIndent}${theme.fg("accent", liveDetailHintText())}`);
|
|
1295
1974
|
const output = widgetOutputPath(job, step);
|
|
1296
|
-
if (output) lines.push(
|
|
1975
|
+
if (output) lines.push(`${detailIndent}${theme.fg("dim", `output: ${shortenPath(output)}`)}`);
|
|
1297
1976
|
if (expanded) {
|
|
1298
1977
|
const liveStatus = buildLiveStatusLine(step, job.updatedAt);
|
|
1299
|
-
if (liveStatus && liveStatus !== activity) lines.push(
|
|
1978
|
+
if (liveStatus && liveStatus !== activity) lines.push(`${detailIndent}${theme.fg("accent", liveStatus)}`);
|
|
1300
1979
|
for (const tool of step.recentTools?.slice(-3) ?? []) {
|
|
1301
1980
|
const maxArgsLen = Math.max(40, width - 30);
|
|
1302
1981
|
const argsPreview = renderToolArgsPreview(tool.args, maxArgsLen, expanded);
|
|
1303
|
-
lines.push(
|
|
1982
|
+
lines.push(`${detailIndent} ${theme.fg("dim", `${tool.tool}${argsPreview ? `: ${argsPreview}` : ""}`)}`);
|
|
1304
1983
|
}
|
|
1305
1984
|
for (const line of compactRecentOutputLines(step.recentOutput)) {
|
|
1306
|
-
lines.push(
|
|
1985
|
+
lines.push(`${detailIndent} ${theme.fg("dim", line)}`);
|
|
1307
1986
|
}
|
|
1308
1987
|
}
|
|
1309
1988
|
}
|
|
1310
1989
|
return lines;
|
|
1311
1990
|
}
|
|
1312
1991
|
|
|
1992
|
+
function hostStepWidgetLines(job: AsyncJobState, theme: Theme, indent: string): string[] {
|
|
1993
|
+
const rows = projectAsyncWorkflowRows([], job.hostSteps).filter((row) => row.kind);
|
|
1994
|
+
const visible = rows.slice(0, 8);
|
|
1995
|
+
const lines = visible.map((row) => {
|
|
1996
|
+
const state = hostStepVerdictLabel(row.state as HostStepState, row.verdict as HostStepVerdict | undefined);
|
|
1997
|
+
const glyph = state === "running" ? theme.fg("accent", "●")
|
|
1998
|
+
: state === "pending" ? theme.fg("muted", "◦")
|
|
1999
|
+
: state === "pass" ? theme.fg("success", "✓")
|
|
2000
|
+
: state === "fail" || state === "error" ? theme.fg("error", "✗")
|
|
2001
|
+
: theme.fg("warning", "■");
|
|
2002
|
+
const details = [
|
|
2003
|
+
row.provider ? `provider:${row.provider}` : undefined,
|
|
2004
|
+
row.role ? `role:${row.role}` : undefined,
|
|
2005
|
+
row.target,
|
|
2006
|
+
row.detail,
|
|
2007
|
+
row.reasonCode ? `reason:${row.reasonCode}` : undefined,
|
|
2008
|
+
row.freshness?.stale ? "stale" : row.freshness?.observedRef ? `ref:${row.freshness.observedRef}` : undefined,
|
|
2009
|
+
row.reportPath ? `out:${hostStepReportName(row.reportPath)}` : undefined,
|
|
2010
|
+
].filter(Boolean).join(" · ");
|
|
2011
|
+
return `${indent}${glyph} ${row.kind}: ${row.name} · ${state}${details ? ` · ${details}` : ""}`;
|
|
2012
|
+
});
|
|
2013
|
+
if (rows.length > visible.length) lines.push(`${indent}${theme.fg("dim", `… +${rows.length - visible.length} host steps hidden`)}`);
|
|
2014
|
+
return lines;
|
|
2015
|
+
}
|
|
2016
|
+
|
|
1313
2017
|
function foregroundStyleWidgetDetails(job: AsyncJobState, theme: Theme, expanded: boolean, width: number, frame?: number): string[] {
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
2018
|
+
const steps = workflowWidgetSteps(job);
|
|
2019
|
+
if (!steps.length) {
|
|
2020
|
+
const lane = projectAsyncLane(job);
|
|
2021
|
+
return [
|
|
2022
|
+
...workflowPreflightLines(job, expanded),
|
|
2023
|
+
...(lane ? formatLaneProjectionLines(lane, theme, " ") : []),
|
|
2024
|
+
...hostStepWidgetLines(job, theme, " "),
|
|
2025
|
+
` ${theme.fg("dim", `⎿ ${widgetActivity(job)}`)}`,
|
|
2026
|
+
...formatNestedWidgetLines(job.nestedChildren, theme, width, expanded, job.updatedAt, expanded ? 12 : 6).map((line) => ` ${line}`),
|
|
2027
|
+
];
|
|
2028
|
+
}
|
|
1318
2029
|
if (job.mode === "chain" && !job.activeParallelGroup && job.parallelGroups?.length) return widgetChainDetails(job, theme, expanded, width, frame);
|
|
1319
|
-
const
|
|
1320
|
-
const
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
2030
|
+
const lines: string[] = workflowPreflightLines(job, expanded);
|
|
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
|
+
}
|
|
1324
2052
|
}
|
|
1325
|
-
|
|
2053
|
+
lines.push(...hostStepWidgetLines(job, theme, " "));
|
|
2054
|
+
const attached = new Set(steps.flatMap((step) => step.children?.map((child) => child.id) ?? []));
|
|
1326
2055
|
const unattached = job.nestedChildren?.filter((child) => !attached.has(child.id)) ?? [];
|
|
1327
2056
|
for (const nestedLine of formatNestedWidgetLines(unattached, theme, width, expanded, job.updatedAt, expanded ? 12 : 6)) {
|
|
1328
2057
|
lines.push(` ${nestedLine}`);
|
|
@@ -1332,12 +2061,16 @@ function foregroundStyleWidgetDetails(job: AsyncJobState, theme: Theme, expanded
|
|
|
1332
2061
|
|
|
1333
2062
|
function buildSingleWidgetLines(job: AsyncJobState, theme: Theme, width: number, expanded: boolean, frame?: number): string[] {
|
|
1334
2063
|
const stats = widgetStats(job, theme);
|
|
1335
|
-
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;
|
|
1336
2065
|
const mode = widgetJobName(job);
|
|
1337
|
-
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}` : ""}`;
|
|
1338
2071
|
return [
|
|
1339
2072
|
`${theme.fg("toolTitle", themeBold(theme, title))} ${theme.fg("dim", "· background")}`,
|
|
1340
|
-
|
|
2073
|
+
...(collapseDetails ? [] : [summary]),
|
|
1341
2074
|
...foregroundStyleWidgetDetails(job, theme, expanded, width, frame),
|
|
1342
2075
|
].map((line) => truncLine(line, width));
|
|
1343
2076
|
}
|
|
@@ -1346,10 +2079,13 @@ function compactSingleWidgetLines(job: AsyncJobState, theme: Theme, width: numbe
|
|
|
1346
2079
|
const fullLines = buildSingleWidgetLines(job, theme, width, false, frame);
|
|
1347
2080
|
if (fullLines.length <= 10 || !job.steps?.length || (job.mode !== "parallel" && !job.activeParallelGroup)) return fullLines;
|
|
1348
2081
|
|
|
1349
|
-
const
|
|
1350
|
-
|
|
2082
|
+
const group = activeParallelWidgetGroup(job);
|
|
2083
|
+
if (!group) return fullLines;
|
|
2084
|
+
const rows = parallelWidgetStepRows(group.steps, group.total, Boolean(job.activeParallelGroup));
|
|
1351
2085
|
const lines = fullLines.slice(0, 2);
|
|
1352
|
-
|
|
2086
|
+
lines.push(parallelWidgetGroupHeader(group, theme, frame));
|
|
2087
|
+
for (const [rowIndex, row] of rows.entries()) {
|
|
2088
|
+
const step = row.step;
|
|
1353
2089
|
const status = widgetStepStatus(step.status, theme);
|
|
1354
2090
|
const activity = widgetStepActivityLine(step, width, false, job.updatedAt);
|
|
1355
2091
|
const stepStats = widgetStepStats(theme, step);
|
|
@@ -1357,9 +2093,13 @@ function compactSingleWidgetLines(job: AsyncJobState, theme: Theme, width: numbe
|
|
|
1357
2093
|
const modelDisplay = modelThinkingBadge(theme, step.model, step.thinking);
|
|
1358
2094
|
const task = compactTaskText(step.description, step.label);
|
|
1359
2095
|
const taskSuffix = task ? ` ${theme.fg("dim", "·")} ${theme.fg("dim", `task: ${task}`)}` : "";
|
|
1360
|
-
|
|
1361
|
-
|
|
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}` : ""}`);
|
|
2098
|
+
const lane = projectAsyncLane(job, step);
|
|
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}`);
|
|
1362
2101
|
}
|
|
2102
|
+
lines.push(...hostStepWidgetLines(job, theme, " "));
|
|
1363
2103
|
if (job.steps.some((step) => step.status === "running")) lines.push(theme.fg("accent", ` ${liveDetailHintText()}`));
|
|
1364
2104
|
return lines.map((line) => truncLine(line, width));
|
|
1365
2105
|
}
|
|
@@ -1496,9 +2236,18 @@ function progressiveJobLine(job: AsyncJobState, theme: Theme, width: number, fra
|
|
|
1496
2236
|
const stats = widgetStats(job, theme);
|
|
1497
2237
|
const activity = widgetActivity(job);
|
|
1498
2238
|
const status = job.status === "complete" ? "done" : job.status;
|
|
2239
|
+
const lane = projectAsyncLane(job);
|
|
2240
|
+
const laneSummary = lane
|
|
2241
|
+
? [lane.label ?? lane.role, lane.phase ? `phase:${lane.phase}` : undefined, lane.output ? `out:${lane.output}` : undefined, lane.workspace ? `workspace:${lane.workspace}` : `ref:${lane.ref}`].filter(Boolean).join(" · ")
|
|
2242
|
+
: "";
|
|
2243
|
+
const laneSignals = lane
|
|
2244
|
+
? [lane.next ? `next:${lane.next}` : undefined, ...lane.chips.map((chip) => formatLaneChip(chip, theme))].filter(Boolean).join(" · ")
|
|
2245
|
+
: "";
|
|
1499
2246
|
const parts = [
|
|
1500
2247
|
`${themeBold(theme, widgetJobName(job))}${contextModeBadge(theme, job.context)}`,
|
|
1501
2248
|
theme.fg("dim", status),
|
|
2249
|
+
laneSignals ? laneSignals : "",
|
|
2250
|
+
laneSummary ? theme.fg("dim", laneSummary) : "",
|
|
1502
2251
|
stats,
|
|
1503
2252
|
activity && activity.toLowerCase() !== status ? theme.fg("dim", activity) : "",
|
|
1504
2253
|
].filter(Boolean);
|
|
@@ -1591,6 +2340,10 @@ function fitAdaptiveWidgetLines(jobs: AsyncJobState[], buildLines: () => string[
|
|
|
1591
2340
|
widgetLayoutSession = { expanded, rows, columns, tier: "full", visibleJobKeys: [] };
|
|
1592
2341
|
return fitWidgetLineBudget(lines, theme, width, false);
|
|
1593
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
|
+
}
|
|
1594
2347
|
|
|
1595
2348
|
if (availableRows <= 2) {
|
|
1596
2349
|
widgetLayoutSession = { expanded, rows, columns, tier: "single-line", visibleJobKeys: [] };
|
|
@@ -1637,15 +2390,19 @@ export function buildWidgetLines(jobs: AsyncJobState[], theme: Theme, width = ge
|
|
|
1637
2390
|
let hiddenFinished = 0;
|
|
1638
2391
|
let queuedSummaryShown = false;
|
|
1639
2392
|
let slots = MAX_WIDGET_JOBS;
|
|
1640
|
-
|
|
1641
|
-
for (const job of running) {
|
|
1642
|
-
if (slots <= 0) { hiddenRunning++; continue; }
|
|
2393
|
+
const appendJob = (job: AsyncJobState): void => {
|
|
1643
2394
|
const stats = widgetStats(job, theme);
|
|
1644
2395
|
items.push([
|
|
1645
2396
|
`${widgetStatusGlyph(job, theme, frame)} ${themeBold(theme, widgetJobName(job))}${contextModeBadge(theme, job.context)}${stats ? ` ${theme.fg("dim", "·")} ${stats}` : ""}`,
|
|
1646
2397
|
` ${theme.fg("dim", `⎿ ${widgetActivity(job)}`)}`,
|
|
2398
|
+
...widgetLaneDetailLines(job, theme),
|
|
1647
2399
|
...widgetParallelAgentDetails(job, theme, expanded, width, frame),
|
|
1648
2400
|
]);
|
|
2401
|
+
};
|
|
2402
|
+
|
|
2403
|
+
for (const job of running) {
|
|
2404
|
+
if (slots <= 0) { hiddenRunning++; continue; }
|
|
2405
|
+
appendJob(job);
|
|
1649
2406
|
slots--;
|
|
1650
2407
|
}
|
|
1651
2408
|
|
|
@@ -1657,12 +2414,7 @@ export function buildWidgetLines(jobs: AsyncJobState[], theme: Theme, width = ge
|
|
|
1657
2414
|
|
|
1658
2415
|
for (const job of finished) {
|
|
1659
2416
|
if (slots <= 0) { hiddenFinished++; continue; }
|
|
1660
|
-
|
|
1661
|
-
items.push([
|
|
1662
|
-
`${widgetStatusGlyph(job, theme, frame)} ${themeBold(theme, widgetJobName(job))}${contextModeBadge(theme, job.context)}${stats ? ` ${theme.fg("dim", "·")} ${stats}` : ""}`,
|
|
1663
|
-
` ${theme.fg("dim", `⎿ ${widgetActivity(job)}`)}`,
|
|
1664
|
-
...widgetParallelAgentDetails(job, theme, expanded, width, frame),
|
|
1665
|
-
]);
|
|
2417
|
+
appendJob(job);
|
|
1666
2418
|
slots--;
|
|
1667
2419
|
}
|
|
1668
2420
|
|
|
@@ -1728,7 +2480,7 @@ function renderSingleCompact(
|
|
|
1728
2480
|
const detailIndent = mainWindowIndent(layout, 1);
|
|
1729
2481
|
const continuationIndent = mainWindowIndent(layout, 2) + (layout.horizontalSpacing > 0 ? " " : "");
|
|
1730
2482
|
const modelDisplay = modelThinkingBadge(theme, r.model ?? r.progress?.model, r.thinking ?? r.progress?.thinking);
|
|
1731
|
-
c.addChild(new Text(truncLine(`${resultGlyph(r, output, theme, isRunning, undefined, frame)} ${theme.fg("toolTitle", theme.bold(r
|
|
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));
|
|
1732
2484
|
|
|
1733
2485
|
if (isRunning && r.progress) {
|
|
1734
2486
|
const task = compactTaskText(r.task);
|
|
@@ -1761,6 +2513,7 @@ function renderSingleCompact(
|
|
|
1761
2513
|
}
|
|
1762
2514
|
|
|
1763
2515
|
function workflowRowGlyph(row: WorkflowChatProgressRow, theme: Theme, frame?: number): string {
|
|
2516
|
+
if (row.state === "planned") return theme.fg("muted", "◦");
|
|
1764
2517
|
if (row.state === "running") return theme.fg("accent", runningGlyph(frame));
|
|
1765
2518
|
if (row.state === "complete") return theme.fg("success", "✓");
|
|
1766
2519
|
if (row.state === "detached" || row.state === "stopped") return theme.fg("warning", "■");
|
|
@@ -1769,6 +2522,7 @@ function workflowRowGlyph(row: WorkflowChatProgressRow, theme: Theme, frame?: nu
|
|
|
1769
2522
|
|
|
1770
2523
|
function workflowRowStateLabel(row: WorkflowChatProgressRow, theme: Theme): string {
|
|
1771
2524
|
const label = (row.state === "complete" ? "complete" : row.state).padEnd(8);
|
|
2525
|
+
if (row.state === "planned") return theme.fg("dim", label);
|
|
1772
2526
|
if (row.state === "running") return theme.fg("accent", label);
|
|
1773
2527
|
if (row.state === "complete") return theme.fg("success", label);
|
|
1774
2528
|
if (row.state === "detached" || row.state === "stopped") return theme.fg("warning", label);
|
|
@@ -1783,9 +2537,9 @@ function workflowOverallState(rows: WorkflowChatProgressRow[], hasTerminalValue:
|
|
|
1783
2537
|
return "running";
|
|
1784
2538
|
}
|
|
1785
2539
|
|
|
1786
|
-
function renderWorkflowChatProgress(d: Details, result: AgentToolResult<Details>, theme: Theme, layout: MainWindowRenderLayout, frame?: number): Component {
|
|
2540
|
+
function renderWorkflowChatProgress(d: Details, result: AgentToolResult<Details>, theme: Theme, layout: MainWindowRenderLayout, frame?: number, expanded = false): Component {
|
|
1787
2541
|
const workflow = d.workflow;
|
|
1788
|
-
const rows = workflow ? buildWorkflowChatProgressRows(workflow.trace) : [];
|
|
2542
|
+
const rows = workflow ? buildWorkflowChatProgressRows(workflow.trace, d.preflight) : d.preflight ? buildWorkflowChatProgressRows([], d.preflight) : [];
|
|
1789
2543
|
const state = workflowOverallState(rows, workflow?.value !== undefined, result.isError);
|
|
1790
2544
|
const glyph = state === "running" ? theme.fg("accent", runningGlyph(frame)) : state === "complete" ? theme.fg("success", "✓") : state === "paused" ? theme.fg("warning", "■") : theme.fg("error", "✗");
|
|
1791
2545
|
const width = getTermWidth() - 4;
|
|
@@ -1796,6 +2550,7 @@ function renderWorkflowChatProgress(d: Details, result: AgentToolResult<Details>
|
|
|
1796
2550
|
const rowIndent = mainWindowIndent(layout, 1);
|
|
1797
2551
|
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));
|
|
1798
2552
|
c.addChild(new Text(truncLine(theme.fg("dim", `${rowIndent}Repo ${repoLabel}`), width), 0, 0));
|
|
2553
|
+
if (d.preflight) c.addChild(new Text(truncLine(theme.fg("dim", formatWorkflowPreflightPlanSummary(d.preflight, { indent: rowIndent })), width), 0, 0));
|
|
1799
2554
|
if (phase) c.addChild(new Text(truncLine(theme.fg("dim", `${rowIndent}Phase ${phase}`), width), 0, 0));
|
|
1800
2555
|
if (rows.length === 0) {
|
|
1801
2556
|
c.addChild(new Text(truncLine(theme.fg("dim", `${rowIndent}◦ waiting for workflow child launches`), width), 0, 0));
|
|
@@ -1809,7 +2564,20 @@ function renderWorkflowChatProgress(d: Details, result: AgentToolResult<Details>
|
|
|
1809
2564
|
const duration = row.durationMs !== undefined ? ` ${theme.fg("dim", `· ${formatDuration(row.durationMs)}`)}` : "";
|
|
1810
2565
|
const run = row.runId ? ` ${theme.fg("dim", `[${row.runId.slice(0, 8)}]`)}` : "";
|
|
1811
2566
|
const error = row.error ? ` ${theme.fg(row.state === "detached" ? "warning" : "error", `· ${compactWorkflowError(row.error)}`)}` : "";
|
|
1812
|
-
|
|
2567
|
+
const hints = expanded && row.preflight ? [
|
|
2568
|
+
row.preflight.mode ? `mode:${row.preflight.mode}` : undefined,
|
|
2569
|
+
row.preflight.decision ? `decision:${row.preflight.decision}` : undefined,
|
|
2570
|
+
row.preflight.claims?.length ? `claims:${row.preflight.claims.join(",")}` : undefined,
|
|
2571
|
+
row.preflight.expectedOutput ? `expected:${row.preflight.expectedOutput}` : undefined,
|
|
2572
|
+
row.preflight.independence ? `independence:${row.preflight.independence}` : undefined,
|
|
2573
|
+
].filter((value): value is string => Boolean(value)).join(" · ") : "";
|
|
2574
|
+
c.addChild(new Text(truncLine(`${rowIndent}${workflowRowGlyph(row, theme, frame)} ${status} ${theme.bold(row.key)}${label}${run}${duration}${error}${hints ? ` ${theme.fg("dim", `· ${hints}`)}` : ""}`, width), 0, 0));
|
|
2575
|
+
}
|
|
2576
|
+
if (workflow?.preflightWarnings?.length) {
|
|
2577
|
+
const warningLines = expanded
|
|
2578
|
+
? formatWorkflowPreflightWarnings(workflow.preflightWarnings, { indent: rowIndent }).split("\n")
|
|
2579
|
+
: [formatWorkflowPreflightWarningSummary(workflow.preflightWarnings, { indent: rowIndent, hint: "expand for debug" })];
|
|
2580
|
+
for (const warningLine of warningLines) c.addChild(new Text(truncLine(theme.fg("warning", warningLine), width), 0, 0));
|
|
1813
2581
|
}
|
|
1814
2582
|
if (workflow?.emits.length) c.addChild(new Text(truncLine(theme.fg("dim", `${rowIndent}Emits ${workflow.emits.length}`), width), 0, 0));
|
|
1815
2583
|
return c;
|
|
@@ -1825,6 +2593,7 @@ function renderMultiCompact(d: Details, theme: Theme, layout: MainWindowRenderLa
|
|
|
1825
2593
|
|| workflowGraphHasStatus(d, ["failed"]);
|
|
1826
2594
|
const paused = d.results.some((r) => r.interrupted)
|
|
1827
2595
|
|| workflowGraphHasStatus(d, ["paused"]);
|
|
2596
|
+
const partial = workflowGraphHasStatus(d, ["partial"]);
|
|
1828
2597
|
let totalSummary = d.progressSummary;
|
|
1829
2598
|
if (!totalSummary) {
|
|
1830
2599
|
let sawProgress = false;
|
|
@@ -1848,6 +2617,7 @@ function renderMultiCompact(d: Details, theme: Theme, layout: MainWindowRenderLa
|
|
|
1848
2617
|
stopped,
|
|
1849
2618
|
interrupted: paused,
|
|
1850
2619
|
failed,
|
|
2620
|
+
partial,
|
|
1851
2621
|
seed: runningSeed(progressRunningSeed(totalSummary), d.currentStepIndex),
|
|
1852
2622
|
frame,
|
|
1853
2623
|
});
|
|
@@ -1857,25 +2627,19 @@ function renderMultiCompact(d: Details, theme: Theme, layout: MainWindowRenderLa
|
|
|
1857
2627
|
const width = getTermWidth() - 4;
|
|
1858
2628
|
const rowIndent = mainWindowIndent(layout, 1);
|
|
1859
2629
|
const detailIndent = mainWindowIndent(layout, 2);
|
|
1860
|
-
const continuationIndent = mainWindowIndent(layout, 3) + (layout.horizontalSpacing > 0 ? " " : "");
|
|
1861
2630
|
c.addChild(new Text(truncLine(`${glyph} ${theme.fg("toolTitle", theme.bold(d.mode))}${contextBadge}${stats ? ` ${theme.fg("dim", "·")} ${stats}` : ""}`, width), 0, 0));
|
|
1862
2631
|
|
|
1863
2632
|
const useResultsDirectly = multiLabel.hasParallelInChain || !d.chainAgents?.length;
|
|
1864
2633
|
const displayStart = multiLabel.showActiveGroupOnly ? multiLabel.groupStartIndex : 0;
|
|
1865
2634
|
const displayEnd = multiLabel.showActiveGroupOnly ? multiLabel.groupEndIndex : (useResultsDirectly ? d.results.length : d.chainAgents!.length);
|
|
1866
2635
|
const chainEntries = buildChainRenderEntries(d, multiLabel);
|
|
1867
|
-
const renderEntries = chainEntries ??
|
|
1868
|
-
const i = displayStart + offset;
|
|
1869
|
-
const r = d.results[i];
|
|
1870
|
-
const fallbackLabel = itemTitle.toLowerCase();
|
|
1871
|
-
const rowNumber = multiLabel.showActiveGroupOnly ? (i - multiLabel.groupStartIndex + 1) : (i + 1);
|
|
1872
|
-
return { kind: "result", resultIndex: i, rowNumber, agentName: useResultsDirectly ? (r?.agent || `${fallbackLabel}-${rowNumber}`) : (d.chainAgents![i] || r?.agent || `${fallbackLabel}-${rowNumber}`) };
|
|
1873
|
-
});
|
|
2636
|
+
const renderEntries = chainEntries ?? buildForegroundResultEntries(d, multiLabel, displayStart, displayEnd, useResultsDirectly);
|
|
1874
2637
|
for (const entry of renderEntries) {
|
|
1875
|
-
if (entry.kind === "
|
|
2638
|
+
if (entry.kind === "group") {
|
|
1876
2639
|
const glyph = widgetStepGlyph(entry.status as AsyncJobStep["status"], theme);
|
|
1877
2640
|
const statusLabel = widgetStepStatus(entry.status as AsyncJobStep["status"], theme);
|
|
1878
|
-
|
|
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));
|
|
1879
2643
|
if (entry.error) c.addChild(new Text(truncLine(theme.fg("error", `${detailIndent}⎿ Error: ${entry.error}`), width), 0, 0));
|
|
1880
2644
|
continue;
|
|
1881
2645
|
}
|
|
@@ -1884,23 +2648,24 @@ function renderMultiCompact(d: Details, theme: Theme, layout: MainWindowRenderLa
|
|
|
1884
2648
|
const rowNumber = entry.rowNumber;
|
|
1885
2649
|
const agentName = entry.agentName;
|
|
1886
2650
|
if (!r) {
|
|
1887
|
-
const pendingLabel = entry.rowLabel ?? `${itemTitle} ${rowNumber}
|
|
1888
|
-
|
|
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));
|
|
1889
2654
|
continue;
|
|
1890
2655
|
}
|
|
1891
2656
|
const output = getSingleResultOutput(r);
|
|
1892
|
-
const progressFromArray = d
|
|
2657
|
+
const progressFromArray = foregroundProgressForResult(d, i);
|
|
1893
2658
|
const rProg = r.progress || progressFromArray || r.progressSummary;
|
|
1894
2659
|
const rRunning = rProg && "status" in rProg && isResultRunning(r, rProg.status);
|
|
1895
2660
|
const rPending = rProg && "status" in rProg && rProg.status === "pending";
|
|
1896
|
-
const stepNumber = r.progress?.index !== undefined ? r.progress.index + 1 : progressFromArray?.index !== undefined ? progressFromArray.index + 1 : i + 1;
|
|
1897
2661
|
const stepStats = formatProgressStats(theme, rProg);
|
|
1898
2662
|
const glyph = rPending ? theme.fg("dim", "◦") : resultGlyph(r, output, theme, rRunning, progressRunningSeed(rProg), frame);
|
|
1899
2663
|
const pendingLabel = rPending ? ` ${theme.fg("dim", "· pending")}` : "";
|
|
1900
|
-
const stepLabel = entry.rowLabel
|
|
2664
|
+
const stepLabel = entry.rowLabel;
|
|
1901
2665
|
const rowProgressModel = rProg && "status" in rProg ? rProg : undefined;
|
|
1902
2666
|
const rowModelDisplay = modelThinkingBadge(theme, r.model ?? rowProgressModel?.model, r.thinking ?? rowProgressModel?.thinking);
|
|
1903
|
-
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}`;
|
|
1904
2669
|
c.addChild(new Text(truncLine(`${rowIndent}${line}`, width), 0, 0));
|
|
1905
2670
|
if (rRunning || rPending) {
|
|
1906
2671
|
const task = compactTaskText(r.task, workflowLabelForResult(d, i));
|
|
@@ -1951,7 +2716,8 @@ export function renderSubagentSummary(
|
|
|
1951
2716
|
const failed = result.isError === true
|
|
1952
2717
|
|| results.some((entry) => !hasTerminalResultFlag(entry) && entry.exitCode !== 0 && !isResultRunning(entry))
|
|
1953
2718
|
|| Boolean(details && workflowGraphHasStatus(details, ["failed"]));
|
|
1954
|
-
const
|
|
2719
|
+
const partial = Boolean(details && workflowGraphHasStatus(details, ["partial"]));
|
|
2720
|
+
const state = running ? "running" : failed ? "failed" : stopped ? "stopped" : paused ? "paused" : partial ? "partial" : "completed";
|
|
1955
2721
|
const glyph = state === "running"
|
|
1956
2722
|
? theme.fg("accent", STATIC_RUNNING_GLYPH)
|
|
1957
2723
|
: state === "completed"
|
|
@@ -1960,7 +2726,7 @@ export function renderSubagentSummary(
|
|
|
1960
2726
|
? theme.fg("error", "✗")
|
|
1961
2727
|
: theme.fg("warning", "■");
|
|
1962
2728
|
const label = details?.mode === "single" && results.length === 1
|
|
1963
|
-
? results[0]
|
|
2729
|
+
? foregroundSingleDisplayName(results[0])
|
|
1964
2730
|
: details?.mode || "subagent";
|
|
1965
2731
|
return new Text(
|
|
1966
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),
|
|
@@ -1984,7 +2750,7 @@ export function renderSubagentResult(
|
|
|
1984
2750
|
const compact = (component: Component): Component => capCompactMainWindowResult(component, layout, theme, !options.expanded);
|
|
1985
2751
|
const d = result.details;
|
|
1986
2752
|
if (d?.mode === "workflow" && d.chatProgress?.mode === "live-card" && d.workflow?.value === undefined && (!result.isError || (d.workflow?.trace.length ?? 0) > 0)) {
|
|
1987
|
-
return compact(renderWorkflowChatProgress(d, result, theme, options.expanded ? resolveMainWindowRenderLayout() : layout, frame));
|
|
2753
|
+
return compact(renderWorkflowChatProgress(d, result, theme, options.expanded ? resolveMainWindowRenderLayout() : layout, frame, options.expanded));
|
|
1988
2754
|
}
|
|
1989
2755
|
if (!d || !d.results.length) {
|
|
1990
2756
|
const t = result.content[0];
|
|
@@ -1995,9 +2761,12 @@ export function renderSubagentResult(
|
|
|
1995
2761
|
if (d && !options.expanded && !result.isError) {
|
|
1996
2762
|
const lines = text.split(/\r?\n/);
|
|
1997
2763
|
const firstNonEmptyLine = lines.find((line) => line.trim())?.trim() || "(no output)";
|
|
2764
|
+
const compactLine = d.mode === "workflow" && d.preflight
|
|
2765
|
+
? formatWorkflowPreflightPlanSummary(d.preflight)
|
|
2766
|
+
: firstNonEmptyLine;
|
|
1998
2767
|
const c = new Container();
|
|
1999
2768
|
const detailIndent = mainWindowIndent(layout, 1);
|
|
2000
|
-
c.addChild(new Text(truncLine(`${contextPrefix}${
|
|
2769
|
+
c.addChild(new Text(truncLine(`${contextPrefix}${compactLine} · ${lines.length} lines`, width), 0, 0));
|
|
2001
2770
|
c.addChild(new Text(truncLine(theme.fg("accent", `${detailIndent}Press ${liveDetailKeyText()} for full output`), width), 0, 0));
|
|
2002
2771
|
return compact(c);
|
|
2003
2772
|
}
|
|
@@ -2030,7 +2799,7 @@ export function renderSubagentResult(
|
|
|
2030
2799
|
const fit = (text: string) => expanded ? text : truncLine(text, w);
|
|
2031
2800
|
const toolCallLines = getToolCallLines(r, expanded);
|
|
2032
2801
|
const c = new Container();
|
|
2033
|
-
c.addChild(new Text(fit(`${presentation.glyph} ${theme.fg("toolTitle", theme.bold(r
|
|
2802
|
+
c.addChild(new Text(fit(`${presentation.glyph} ${theme.fg("toolTitle", theme.bold(foregroundSingleDisplayName(r)))}${contextBadge}${progressInfo} ${theme.fg("dim", "·")} ${presentation.label}`), 0, 0));
|
|
2034
2803
|
c.addChild(new Spacer(1));
|
|
2035
2804
|
const taskMaxLen = Math.max(20, w - 8);
|
|
2036
2805
|
const taskPreview = expanded || r.task.length <= taskMaxLen
|
|
@@ -2040,6 +2809,9 @@ export function renderSubagentResult(
|
|
|
2040
2809
|
new Text(fit(theme.fg("dim", `Task: ${taskPreview}`)), 0, 0),
|
|
2041
2810
|
);
|
|
2042
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
|
+
}
|
|
2043
2815
|
|
|
2044
2816
|
if (isRunning && r.progress) {
|
|
2045
2817
|
const progressSnapshotNow = snapshotNowForProgress(r.progress);
|
|
@@ -2118,6 +2890,7 @@ export function renderSubagentResult(
|
|
|
2118
2890
|
|| workflowGraphHasStatus(d, ["failed"]);
|
|
2119
2891
|
const paused = d.results.some((r) => r.interrupted)
|
|
2120
2892
|
|| workflowGraphHasStatus(d, ["paused"]);
|
|
2893
|
+
const partial = workflowGraphHasStatus(d, ["partial"]);
|
|
2121
2894
|
const completedWithoutOutput = d.results.some((r) =>
|
|
2122
2895
|
!hasTerminalResultFlag(r)
|
|
2123
2896
|
&& r.exitCode === 0
|
|
@@ -2130,6 +2903,7 @@ export function renderSubagentResult(
|
|
|
2130
2903
|
stopped,
|
|
2131
2904
|
interrupted: paused,
|
|
2132
2905
|
failed,
|
|
2906
|
+
partial,
|
|
2133
2907
|
completedWithoutOutput,
|
|
2134
2908
|
frame,
|
|
2135
2909
|
}), theme);
|
|
@@ -2169,6 +2943,7 @@ export function renderSubagentResult(
|
|
|
2169
2943
|
? d.chainAgents
|
|
2170
2944
|
.map((agent, i) => {
|
|
2171
2945
|
const result = d.results[i];
|
|
2946
|
+
const displayName = foregroundResultDisplayName(d, i, result, agent);
|
|
2172
2947
|
const isCurrent = i === (d.currentStepIndex ?? d.results.length);
|
|
2173
2948
|
const stepPresentation = result
|
|
2174
2949
|
? styledResultPresentation(resultPresentation(result, getSingleResultOutput(result), isCurrent && hasRunning && !hasTerminalResultFlag(result)), theme)
|
|
@@ -2176,7 +2951,7 @@ export function renderSubagentResult(
|
|
|
2176
2951
|
const stepStatus = stepPresentation
|
|
2177
2952
|
? `${stepPresentation.glyph} ${stepPresentation.label}`
|
|
2178
2953
|
: theme.fg("dim", "◦ pending");
|
|
2179
|
-
return `${stepStatus} ${
|
|
2954
|
+
return `${stepStatus} ${displayName}${contextModeBadge(theme, result?.context)}`;
|
|
2180
2955
|
})
|
|
2181
2956
|
.join(theme.fg("dim", " → "))
|
|
2182
2957
|
: null;
|
|
@@ -2199,19 +2974,15 @@ export function renderSubagentResult(
|
|
|
2199
2974
|
const displayStart = multiLabel.showActiveGroupOnly ? multiLabel.groupStartIndex : 0;
|
|
2200
2975
|
const displayEnd = multiLabel.showActiveGroupOnly ? multiLabel.groupEndIndex : (useResultsDirectly ? d.results.length : d.chainAgents!.length);
|
|
2201
2976
|
const chainEntries = buildChainRenderEntries(d, multiLabel);
|
|
2202
|
-
const renderEntries = chainEntries ??
|
|
2203
|
-
const i = displayStart + offset;
|
|
2204
|
-
const r = d.results[i];
|
|
2205
|
-
const rowNumber = multiLabel.showActiveGroupOnly ? (i - multiLabel.groupStartIndex + 1) : (i + 1);
|
|
2206
|
-
return { kind: "result", resultIndex: i, rowNumber, agentName: useResultsDirectly ? (r?.agent || `step-${rowNumber}`) : (d.chainAgents![i] || r?.agent || `step-${rowNumber}`) };
|
|
2207
|
-
});
|
|
2977
|
+
const renderEntries = chainEntries ?? buildForegroundResultEntries(d, multiLabel, displayStart, displayEnd, useResultsDirectly);
|
|
2208
2978
|
|
|
2209
2979
|
c.addChild(new Spacer(1));
|
|
2210
2980
|
|
|
2211
2981
|
for (const entry of renderEntries) {
|
|
2212
|
-
if (entry.kind === "
|
|
2982
|
+
if (entry.kind === "group") {
|
|
2213
2983
|
const statusLabel = widgetStepStatus(entry.status as AsyncJobStep["status"], theme);
|
|
2214
|
-
|
|
2984
|
+
const groupLabel = entry.groupLabel ? ` (${compactTaskText(undefined, entry.groupLabel) ?? entry.groupLabel})` : "";
|
|
2985
|
+
c.addChild(new Text(fit(` ${statusLabel} ${entry.stepLabel}${groupLabel}`), 0, 0));
|
|
2215
2986
|
c.addChild(new Text(theme.fg(entry.status === "failed" ? "error" : "dim", ` status: ${entry.status}`), 0, 0));
|
|
2216
2987
|
if (entry.error) c.addChild(new Text(theme.fg("error", ` error: ${entry.error}`), 0, 0));
|
|
2217
2988
|
c.addChild(new Spacer(1));
|
|
@@ -2223,28 +2994,28 @@ export function renderSubagentResult(
|
|
|
2223
2994
|
const agentName = entry.agentName;
|
|
2224
2995
|
|
|
2225
2996
|
if (!r) {
|
|
2226
|
-
const pendingLabel = entry.rowLabel ?? `${itemTitle} ${rowNumber}
|
|
2227
|
-
|
|
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));
|
|
2228
3000
|
c.addChild(new Text(theme.fg("dim", ` status: pending`), 0, 0));
|
|
2229
3001
|
c.addChild(new Spacer(1));
|
|
2230
3002
|
continue;
|
|
2231
3003
|
}
|
|
2232
3004
|
|
|
2233
|
-
const progressFromArray = d
|
|
2234
|
-
|| d.progress?.find((p) => p.agent === r.agent && p.status === "running");
|
|
3005
|
+
const progressFromArray = foregroundProgressForResult(d, i);
|
|
2235
3006
|
const rProg = r.progress || progressFromArray || r.progressSummary;
|
|
2236
3007
|
const rRunning = isResultRunning(r, rProg?.status);
|
|
2237
|
-
const stepNumber = typeof rProg?.index === "number" ? rProg.index + 1 : i + 1;
|
|
2238
3008
|
|
|
2239
3009
|
const resultOutput = getSingleResultOutput(r);
|
|
2240
3010
|
const rowPresentation = styledResultPresentation(resultPresentation(r, resultOutput, rRunning, progressRunningSeed(rProg), frame), theme);
|
|
2241
3011
|
const stats = rProg ? ` | ${rProg.toolCount} tools, ${formatDuration(rProg.durationMs)}` : "";
|
|
2242
3012
|
const modelDisplay = modelThinkingBadge(theme, r.model ?? rProg?.model, r.thinking ?? rProg?.thinking);
|
|
2243
|
-
const stepLabel = entry.rowLabel
|
|
3013
|
+
const stepLabel = entry.rowLabel;
|
|
2244
3014
|
const contextBadge = contextModeBadge(theme, r.context);
|
|
3015
|
+
const labelPrefix = stepLabel ? `${stepLabel}: ` : "";
|
|
2245
3016
|
const stepHeader = rRunning
|
|
2246
|
-
? `${rowPresentation.glyph} ${
|
|
2247
|
-
: `${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}`;
|
|
2248
3019
|
const toolCallLines = getToolCallLines(r, expanded);
|
|
2249
3020
|
c.addChild(new Text(fit(stepHeader), 0, 0));
|
|
2250
3021
|
|
|
@@ -2258,6 +3029,9 @@ export function renderSubagentResult(
|
|
|
2258
3029
|
if (outputTarget) {
|
|
2259
3030
|
c.addChild(new Text(fit(theme.fg("dim", ` output: ${outputTarget}`)), 0, 0));
|
|
2260
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
|
+
}
|
|
2261
3035
|
|
|
2262
3036
|
if (r.skills?.length) {
|
|
2263
3037
|
c.addChild(new Text(fit(theme.fg("dim", ` skills: ${r.skills.join(", ")}`)), 0, 0));
|