pi-subagents 0.58.0 → 0.59.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.
Files changed (105) hide show
  1. package/CHANGELOG.md +65 -0
  2. package/docs/agents.md +5 -3
  3. package/docs/configuration.md +4 -4
  4. package/docs/extension-api.md +1 -1
  5. package/docs/models.md +24 -1
  6. package/docs/observability.md +1 -1
  7. package/docs/tool-reference.md +89 -4
  8. package/docs/workflows.md +93 -2
  9. package/package.json +3 -1
  10. package/prompts/review-loop.md +2 -2
  11. package/skills/council-mode/SKILL.md +1 -1
  12. package/skills/pi-subagents/SKILL.md +1 -1
  13. package/skills/pi-subagents/references/execution-controls.md +5 -1
  14. package/skills/pi-subagents/references/management-authoring-rpc.md +1 -2
  15. package/skills/pi-subagents/references/prompting-and-roles.md +24 -3
  16. package/src/agents/agent-management.ts +5 -17
  17. package/src/agents/agent-serializer.ts +4 -2
  18. package/src/agents/agents.ts +67 -26
  19. package/src/agents/runtime-agent-registry.ts +9 -16
  20. package/src/api/background-work.ts +5 -1
  21. package/src/api/delegation.ts +0 -7
  22. package/src/api/preflight.ts +6 -9
  23. package/src/extension/fanout-child.ts +5 -3
  24. package/src/extension/index.ts +51 -23
  25. package/src/extension/public-execution.ts +15 -2
  26. package/src/extension/schemas.ts +35 -10
  27. package/src/extension/tool-description.ts +18 -3
  28. package/src/intercom/result-intercom.ts +2 -0
  29. package/src/profiles/profiles.ts +5 -6
  30. package/src/runs/background/active-async-capacity.ts +2 -2
  31. package/src/runs/background/async-execution.ts +50 -35
  32. package/src/runs/background/async-job-tracker.ts +14 -12
  33. package/src/runs/background/async-resume.ts +23 -25
  34. package/src/runs/background/async-status-snapshot.ts +23 -261
  35. package/src/runs/background/async-status.ts +66 -7
  36. package/src/runs/background/chain-append.ts +6 -3
  37. package/src/runs/background/chain-root-attachment.ts +60 -8
  38. package/src/runs/background/fleet-view.ts +21 -11
  39. package/src/runs/background/notify.ts +158 -6
  40. package/src/runs/background/result-files.ts +2 -1
  41. package/src/runs/background/result-watcher.ts +2 -0
  42. package/src/runs/background/resume-guidance.ts +1 -1
  43. package/src/runs/background/retained-children.ts +1 -1
  44. package/src/runs/background/run-status.ts +18 -8
  45. package/src/runs/background/scheduled-runs.ts +86 -7
  46. package/src/runs/background/stale-run-reconciler.ts +10 -4
  47. package/src/runs/background/steering.ts +4 -14
  48. package/src/runs/background/subagent-runner.ts +346 -357
  49. package/src/runs/background/subagent-wait.ts +58 -10
  50. package/src/runs/background/terminal-run-index.ts +1 -1
  51. package/src/runs/background/wait-completions.ts +22 -1
  52. package/src/runs/background/wait-config.ts +23 -9
  53. package/src/runs/background/wait-tool.ts +9 -2
  54. package/src/runs/foreground/async-steering-action.ts +2 -2
  55. package/src/runs/foreground/execution.ts +114 -120
  56. package/src/runs/foreground/foreground-control.ts +3 -0
  57. package/src/runs/foreground/foreground-history.ts +1 -0
  58. package/src/runs/foreground/subagent-executor.ts +478 -239
  59. package/src/runs/foreground/workflow-detach-reconcile.ts +99 -200
  60. package/src/runs/shared/abort-recovery.ts +119 -0
  61. package/src/runs/shared/async-status-projection.ts +463 -0
  62. package/src/runs/shared/child-identity.ts +19 -4
  63. package/src/runs/shared/child-launch-plan.ts +151 -0
  64. package/src/runs/shared/completion-evidence.ts +89 -0
  65. package/src/runs/shared/completion-guard.ts +1 -1
  66. package/src/runs/shared/dynamic-fanout.ts +2 -2
  67. package/src/runs/shared/host-step-status.ts +230 -0
  68. package/src/runs/shared/lane-metadata.ts +105 -0
  69. package/src/runs/shared/mcp-config-sources.ts +42 -6
  70. package/src/runs/shared/mcp-direct-tool-allowlist.ts +93 -143
  71. package/src/runs/shared/mcp-direct-tool-grant.ts +197 -0
  72. package/src/runs/shared/model-fallback.ts +9 -2
  73. package/src/runs/shared/nested-events.ts +6 -2
  74. package/src/runs/shared/nested-render.ts +7 -3
  75. package/src/runs/shared/parallel-handoff.ts +419 -7
  76. package/src/runs/shared/parallel-utils.ts +7 -0
  77. package/src/runs/shared/pi-args.ts +20 -2
  78. package/src/runs/shared/single-output.ts +27 -4
  79. package/src/runs/shared/subagent-prompt-runtime.ts +13 -4
  80. package/src/runs/shared/worktree-cleanup-plan.ts +847 -0
  81. package/src/runs/shared/worktree.ts +18 -0
  82. package/src/shared/child-session-name.ts +46 -0
  83. package/src/shared/extension-context.ts +24 -0
  84. package/src/shared/formatters.ts +5 -2
  85. package/src/shared/launch-contract.ts +1 -1
  86. package/src/shared/settings.ts +9 -103
  87. package/src/shared/types.ts +198 -29
  88. package/src/shared/utils.ts +35 -55
  89. package/src/slash/delegation-adapters.ts +1 -8
  90. package/src/slash/delegation-request.ts +0 -4
  91. package/src/slash/slash-bridge.ts +1 -2
  92. package/src/slash/slash-commands.ts +369 -90
  93. package/src/slash/slash-live-state.ts +22 -11
  94. package/src/tui/fleet-status.ts +125 -74
  95. package/src/tui/fleet.ts +11 -5
  96. package/src/tui/render.ts +316 -27
  97. package/src/watchdog/turn-delta.ts +1 -1
  98. package/src/workflows/chat-progress.ts +6 -3
  99. package/src/workflows/host-command.ts +230 -0
  100. package/src/workflows/scripted-workflow.ts +452 -38
  101. package/src/workflows/workflow-child-summary.ts +9 -5
  102. package/src/workflows/workflow-preflight.ts +270 -0
  103. package/src/workflows/workflow-receipt.ts +43 -4
  104. package/src/workflows/workflow-settlement.ts +246 -0
  105. 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,
@@ -30,7 +32,10 @@ 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";
32
34
  import { buildWorkflowChatProgressRows, type WorkflowChatProgressRow } from "../workflows/chat-progress.ts";
35
+ import { formatWorkflowPreflight, formatWorkflowPreflightPlanSummary, formatWorkflowPreflightWarningSummary, formatWorkflowPreflightWarnings } from "../workflows/workflow-preflight.ts";
33
36
  import { encodeAsyncStatusSnapshotWidget } from "../runs/background/async-status-snapshot.ts";
37
+ import { projectAsyncWorkflowRows, type AsyncStatusWorkflowRow } from "../runs/shared/async-status-projection.ts";
38
+ import { hostStepReportName, hostStepVerdictLabel } from "../runs/shared/host-step-status.ts";
34
39
 
35
40
  type Theme = ExtensionContext["ui"]["theme"];
36
41
 
@@ -266,6 +271,12 @@ function oneLine(text: string): string {
266
271
 
267
272
  const COMPACT_TASK_MAX_CHARS = 96;
268
273
 
274
+ /** Display label for a child run: the derived session name (agent + task
275
+ * excerpt) when the launcher provided one, else the bare agent name. */
276
+ function childDisplayName(result: { agent?: string; sessionName?: string } | undefined, fallback = "subagent"): string {
277
+ return result?.sessionName?.trim() || result?.agent || fallback;
278
+ }
279
+
269
280
  export function compactTaskText(task: string | undefined, label?: string): string | undefined {
270
281
  const taskText = task?.trim();
271
282
  const labelText = label?.trim();
@@ -278,6 +289,170 @@ export function compactTaskText(task: string | undefined, label?: string): strin
278
289
  return previewDisplayText(normalized, COMPACT_TASK_MAX_CHARS);
279
290
  }
280
291
 
292
+ export interface AsyncLaneProjection {
293
+ label?: string;
294
+ role: string;
295
+ phase?: string;
296
+ state: AsyncJobState["status"] | AsyncJobStep["status"];
297
+ gate?: string;
298
+ next?: string;
299
+ output?: string;
300
+ workspace?: string;
301
+ ref: string;
302
+ chips: string[];
303
+ }
304
+
305
+ const LANE_VALUE_MAX_CHARS = 48;
306
+
307
+ function boundedLaneValue(value: string | undefined, maxChars = LANE_VALUE_MAX_CHARS): string | undefined {
308
+ if (!value?.trim() || value.trim() === PROMPT_REDACTED) return undefined;
309
+ return previewDisplayText(oneLine(value), maxChars);
310
+ }
311
+
312
+ function laneStepForJob(job: AsyncJobState): AsyncJobStep | undefined {
313
+ const steps = job.steps ?? [];
314
+ if (steps.length === 0) return undefined;
315
+ if (job.currentStep !== undefined) {
316
+ const current = steps[job.currentStep];
317
+ if (current && (current.index === undefined || current.index === job.currentStep)) return current;
318
+ // Active parallel groups retain flat indices while exposing only the group slice.
319
+ const indexedCurrent = steps.find((step) => step.index === job.currentStep);
320
+ if (indexedCurrent) return indexedCurrent;
321
+ return steps[0];
322
+ }
323
+ return steps.find((step) => step.status === "running")
324
+ ?? steps.find((step) => step.status === "pending")
325
+ ?? steps.at(-1);
326
+ }
327
+
328
+ function laneTraceForJob(job: AsyncJobState): NonNullable<AsyncJobState["workflow"]>["trace"][number] | undefined {
329
+ return Array.isArray(job.workflow?.trace) ? job.workflow.trace.at(-1) : undefined;
330
+ }
331
+
332
+ function laneGate(step: AsyncJobStep | undefined): string | undefined {
333
+ const review = step?.review?.status ?? step?.acceptance?.reviewResult?.status;
334
+ if (review === "blockers") return "review blockers";
335
+ if (review === "review-required") return "review required";
336
+ if (review === "reviewed") return "reviewed";
337
+ const acceptance = step?.acceptance?.status;
338
+ if (acceptance === "review-required") return "acceptance review";
339
+ if (acceptance === "checked" || acceptance === "verified" || acceptance === "accepted") return "acceptance";
340
+ return undefined;
341
+ }
342
+
343
+ function laneNextAction(state: AsyncLaneProjection["state"], step: AsyncJobStep | undefined, output: string | undefined, gate: string | undefined): string | undefined {
344
+ if (step?.watchdog?.phase === "stale") return "inspect stale state";
345
+ if (step?.toolBudgetBlocked === true || step?.turnBudgetExceeded === true) return "inspect blocked state";
346
+ if (gate === "review blockers") return "resolve review blockers";
347
+ if (gate === "review required" || gate === "acceptance review") return "review output";
348
+ if (step?.activityState === "needs_attention") return "inspect attention";
349
+ if (state === "queued" || state === "pending") return "await launch";
350
+ if (state === "failed" || state === "rejected") return "inspect failure";
351
+ if (state === "partial") return "inspect partial state";
352
+ if (state === "paused") return "inspect paused state";
353
+ if (state === "stopped") return "inspect stopped state";
354
+ if ((state === "complete" || state === "completed") && gate === "reviewed") return "ready";
355
+ if ((state === "complete" || state === "completed") && output) return "inspect output";
356
+ return undefined;
357
+ }
358
+
359
+ function isTerminalLaneState(state: AsyncJobState["status"]): boolean {
360
+ return state !== "queued" && state !== "running";
361
+ }
362
+
363
+ /** Project already-loaded async status facts into one bounded, render-only lane row. */
364
+ export function projectAsyncLane(job: AsyncJobState, selectedStep = laneStepForJob(job)): AsyncLaneProjection | undefined {
365
+ const trace = laneTraceForJob(job);
366
+ const workspace = job.cwd ? boundedLaneValue(shortenPath(job.cwd)) : undefined;
367
+ const label = compactTaskText(selectedStep?.description, selectedStep?.label)
368
+ ?? boundedLaneValue(trace?.label)
369
+ ?? (workspace ? undefined : boundedLaneValue(selectedStep?.workflowKey ?? job.workflowKey));
370
+ const role = boundedLaneValue(selectedStep?.agent ?? trace?.agent ?? job.agents?.[0] ?? widgetJobName(job), 32) ?? "subagent";
371
+ const phase = boundedLaneValue(selectedStep?.phase ?? trace?.phase);
372
+ const gate = laneGate(selectedStep);
373
+ const output = boundedLaneValue(selectedStep?.outputName);
374
+ const ref = boundedLaneValue(selectedStep?.workflowKey ?? job.workflowKey ?? job.asyncId.slice(0, 8), 24) ?? job.asyncId.slice(0, 8);
375
+ const chips = [
376
+ selectedStep?.context,
377
+ selectedStep?.structured ? "structured" : undefined,
378
+ selectedStep?.activityState === "active_long_running" ? "long-running" : undefined,
379
+ selectedStep?.activityState === "needs_attention" ? "attention" : undefined,
380
+ selectedStep?.watchdog?.phase === "stale" ? "stale" : undefined,
381
+ selectedStep?.toolBudgetBlocked === true || selectedStep?.turnBudgetExceeded === true ? "blocked" : undefined,
382
+ ].filter((chip): chip is string => Boolean(chip));
383
+ const state = isTerminalLaneState(job.status) ? job.status : selectedStep?.status ?? job.status;
384
+ const next = laneNextAction(state, selectedStep, output, gate);
385
+ if (!label && !phase && !gate && !output && !selectedStep?.workflowKey && !job.workflowKey && !trace?.label && !trace?.phase) return undefined;
386
+ return { ...(label ? { label } : {}), role, ...(phase ? { phase } : {}), state, ...(gate ? { gate } : {}), ...(next ? { next } : {}), ...(output ? { output } : {}), ...(workspace ? { workspace } : {}), ref, chips };
387
+ }
388
+
389
+ function laneStateLabel(state: AsyncLaneProjection["state"], theme: Theme): string {
390
+ if (state === "running") return theme.fg("accent", "running");
391
+ if (state === "queued" || state === "pending") return theme.fg("muted", state);
392
+ if (state === "complete" || state === "completed") return theme.fg("success", "complete");
393
+ if (state === "failed" || state === "rejected") return theme.fg("error", state === "rejected" ? "rejected" : "failed");
394
+ return theme.fg("warning", state);
395
+ }
396
+
397
+ function formatLaneProjection(lane: AsyncLaneProjection, theme: Theme): string {
398
+ const label = boundedLaneValue(lane.label, 56);
399
+ const identity = [label, lane.role ? `role:${lane.role}` : undefined].filter(Boolean).join(" · ");
400
+ return `${identity ? theme.bold(identity) : theme.bold(lane.role)} · ${laneStateLabel(lane.state, theme)}`;
401
+ }
402
+
403
+ function formatLaneChip(chip: string, theme: Theme): string {
404
+ const text = `[${chip}]`;
405
+ if (chip === "blocked") return theme.fg("error", text);
406
+ if (chip === "stale") return theme.fg("warning", text);
407
+ return text;
408
+ }
409
+
410
+ function formatLaneProjectionDetails(lane: AsyncLaneProjection, theme: Theme): string | undefined {
411
+ const details = [
412
+ lane.phase ? `phase:${lane.phase}` : undefined,
413
+ lane.gate ? `gate:${lane.gate}` : undefined,
414
+ lane.next ? `next:${lane.next}` : undefined,
415
+ lane.output ? `out:${lane.output}` : undefined,
416
+ lane.workspace ? `workspace:${lane.workspace}` : lane.ref ? `ref:${lane.ref}` : undefined,
417
+ ].filter(Boolean);
418
+ const chips = lane.chips.map((chip) => formatLaneChip(chip, theme));
419
+ return [...(details.length ? [theme.fg("dim", details.join(" · "))] : []), ...chips].join(" · ") || undefined;
420
+ }
421
+
422
+ function formatLaneProjectionLines(lane: AsyncLaneProjection, theme: Theme, indent: string): string[] {
423
+ const details = formatLaneProjectionDetails(lane, theme);
424
+ return [
425
+ `${indent}${formatLaneProjection(lane, theme)}`,
426
+ ...(details ? [`${indent} ${details}`] : []),
427
+ ];
428
+ }
429
+
430
+ function laneRenderKey(job: AsyncJobState): unknown {
431
+ const lane = projectAsyncLane(job);
432
+ return lane ? [lane.label, lane.role, lane.phase, lane.state, lane.gate, lane.next, lane.output, lane.workspace, lane.ref, lane.chips] : undefined;
433
+ }
434
+
435
+ function widgetLaneDetailLines(job: AsyncJobState, theme: Theme): string[] {
436
+ if (job.steps?.length && (job.mode === "parallel" || job.mode === "chain")) return [];
437
+ const lane = projectAsyncLane(job);
438
+ return lane ? formatLaneProjectionLines(lane, theme, " ") : [];
439
+ }
440
+
441
+ function workflowPreflightLines(job: AsyncJobState, expanded = false): string[] {
442
+ if (job.mode !== "workflow" || !job.preflight) return [];
443
+ if (!expanded) {
444
+ const warning = formatWorkflowPreflightWarningSummary(job.workflow?.preflightWarnings, { indent: " ", hint: "expand for debug" });
445
+ return [
446
+ formatWorkflowPreflightPlanSummary(job.preflight, { indent: " " }),
447
+ ...(warning ? [warning] : []),
448
+ ];
449
+ }
450
+ return [
451
+ ...formatWorkflowPreflight(job.preflight, { indent: " " }).split("\n"),
452
+ ...(job.workflow?.preflightWarnings ? formatWorkflowPreflightWarnings(job.workflow.preflightWarnings, { indent: " " }).split("\n") : []),
453
+ ];
454
+ }
455
+
281
456
  function workflowLabelForResult(details: Details, resultIndex: number): string | undefined {
282
457
  const flatIndex = details.results[resultIndex]?.progress?.index ?? resultIndex;
283
458
  const visit = (nodes: NonNullable<Details["workflowGraph"]>["nodes"]): string | undefined => {
@@ -474,7 +649,7 @@ function resultStatusLine(result: Details["results"][number], output: string): s
474
649
 
475
650
  type ResultPresentation = {
476
651
  glyph: string;
477
- label: "running" | "detached" | "stopped" | "paused" | "failed" | "completed";
652
+ label: "running" | "detached" | "stopped" | "paused" | "failed" | "partial" | "completed";
478
653
  tone: "accent" | "warning" | "error" | "success";
479
654
  };
480
655
 
@@ -484,6 +659,7 @@ function semanticResultPresentation(input: {
484
659
  stopped?: boolean;
485
660
  interrupted?: boolean;
486
661
  failed?: boolean;
662
+ partial?: boolean;
487
663
  completedWithoutOutput?: boolean;
488
664
  seed?: number;
489
665
  frame?: number;
@@ -496,6 +672,7 @@ function semanticResultPresentation(input: {
496
672
  if (input.stopped) return { glyph: "■", label: "stopped", tone: "warning" };
497
673
  if (input.interrupted) return { glyph: "■", label: "paused", tone: "warning" };
498
674
  if (input.failed) return { glyph: "✗", label: "failed", tone: "error" };
675
+ if (input.partial) return { glyph: "■", label: "partial", tone: "warning" };
499
676
  return { glyph: "✓", label: "completed", tone: input.completedWithoutOutput ? "warning" : "success" };
500
677
  }
501
678
 
@@ -574,6 +751,7 @@ function widgetStepRenderKey(step: AsyncJobStep, index: number, expanded = false
574
751
  return [
575
752
  step.index ?? index,
576
753
  step.agent,
754
+ step.sessionName,
577
755
  step.workflowKey,
578
756
  step.phase,
579
757
  step.label,
@@ -593,6 +771,15 @@ function widgetStepRenderKey(step: AsyncJobStep, index: number, expanded = false
593
771
  step.model,
594
772
  step.thinking,
595
773
  step.context,
774
+ step.description,
775
+ step.outputName,
776
+ step.structured,
777
+ step.acceptance?.status,
778
+ step.acceptance?.reviewResult?.status,
779
+ step.review?.status,
780
+ step.toolBudgetBlocked,
781
+ step.turnBudgetExceeded,
782
+ step.watchdog?.phase,
596
783
  step.error,
597
784
  expanded ? expandedStepActivityRenderKey(step) : undefined,
598
785
  nestedRenderKey(step.children, expanded),
@@ -604,6 +791,7 @@ function nestedRenderKey(children: NestedRunSummary[] | undefined, expanded = fa
604
791
  child.id,
605
792
  child.state,
606
793
  child.agent,
794
+ child.sessionName,
607
795
  child.model,
608
796
  child.thinking,
609
797
  child.activityState,
@@ -623,6 +811,22 @@ function nestedRenderKey(children: NestedRunSummary[] | undefined, expanded = fa
623
811
  ]);
624
812
  }
625
813
 
814
+ function hostStepRenderKey(row: AsyncStatusWorkflowRow): unknown[] {
815
+ return [
816
+ row.kind,
817
+ row.name,
818
+ row.state,
819
+ row.provider,
820
+ row.role,
821
+ row.verdict,
822
+ row.reasonCode,
823
+ row.detail,
824
+ row.target,
825
+ row.freshness,
826
+ row.reportPath,
827
+ ];
828
+ }
829
+
626
830
  export function widgetRenderKey(job: AsyncJobState, expanded = false): string {
627
831
  return JSON.stringify({
628
832
  asyncDir: job.asyncDir,
@@ -639,8 +843,12 @@ export function widgetRenderKey(job: AsyncJobState, expanded = false): string {
639
843
  currentStep: job.currentStep,
640
844
  chainStepCount: job.chainStepCount,
641
845
  parallelGroups: job.parallelGroups,
846
+ workflowHostSteps: projectAsyncWorkflowRows([], job.hostSteps).map(hostStepRenderKey),
847
+ preflight: expanded ? job.preflight : job.preflight ? formatWorkflowPreflightPlanSummary(job.preflight) : undefined,
848
+ preflightWarnings: expanded ? job.workflow?.preflightWarnings : job.workflow?.preflightWarnings?.length || undefined,
642
849
  steps: job.steps?.map((step, index) => widgetStepRenderKey(step, index, expanded)),
643
850
  nestedChildren: nestedRenderKey(job.nestedChildren, expanded),
851
+ lane: laneRenderKey(job),
644
852
  stepsTotal: job.stepsTotal,
645
853
  runningSteps: job.runningSteps,
646
854
  completedSteps: job.completedSteps,
@@ -681,6 +889,7 @@ function widgetActivity(job: AsyncJobState): string {
681
889
  if (job.status === "queued") return "queued…";
682
890
  if (job.status === "paused") return "Paused";
683
891
  if (job.status === "stopped") return "Stopped";
892
+ if (job.status === "partial") return "Partial";
684
893
  if (job.status === "failed") return "Failed";
685
894
  return "Done";
686
895
  }
@@ -799,8 +1008,10 @@ function widgetParallelAgentDetails(job: AsyncJobState, theme: Theme, expanded =
799
1008
  const itemTitle = job.mode === "parallel" || job.activeParallelGroup ? "Agent" : "Step";
800
1009
  const modelDisplay = modelThinkingBadge(theme, step.model, step.thinking);
801
1010
  const label = compactTaskText(step.description, step.label);
802
- const display = label ? `${label} (${step.agent})` : step.agent;
1011
+ const display = step.sessionName?.trim() || (label ? `${label} (${step.agent})` : step.agent);
803
1012
  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}` : ""}`)}`);
1013
+ const lane = projectAsyncLane(job, step);
1014
+ if (lane) lines.push(...formatLaneProjectionLines(lane, theme, " "));
804
1015
  for (const nestedLine of formatNestedWidgetLines(step.children, theme, width, expanded, job.updatedAt, expanded ? 8 : 6)) lines.push(` ${nestedLine}`);
805
1016
  }
806
1017
  return lines;
@@ -926,12 +1137,13 @@ function buildChainRenderEntries(details: Details, label: MultiProgressLabel): C
926
1137
  continue;
927
1138
  }
928
1139
  for (let index = span.start; index < span.start + span.count; index++) {
1140
+ const result = details.results[index];
929
1141
  entries.push({
930
1142
  kind: "result",
931
1143
  resultIndex: index,
932
1144
  rowNumber: index + 1,
933
1145
  rowLabel: span.isParallel ? `Agent ${index - span.start + 1}/${span.count}` : `Step ${span.stepIndex + 1}`,
934
- agentName: details.results[index]?.agent ?? details.chainAgents?.[span.stepIndex] ?? `step-${span.stepIndex + 1}`,
1146
+ agentName: result?.sessionName?.trim() || result?.agent || details.chainAgents?.[span.stepIndex] || `step-${span.stepIndex + 1}`,
935
1147
  });
936
1148
  }
937
1149
  }
@@ -1116,6 +1328,7 @@ function widgetOutputPath(job: AsyncJobState, step: NonNullable<AsyncJobState["s
1116
1328
  }
1117
1329
 
1118
1330
  function nestedRunName(run: NestedRunSummary): string {
1331
+ if (run.sessionName?.trim()) return run.sessionName.trim();
1119
1332
  if (run.agent) return run.agent;
1120
1333
  if (run.agents?.length) return formatWidgetAgents(run.agents);
1121
1334
  return run.id;
@@ -1125,6 +1338,7 @@ function nestedStatusGlyph(state: NestedRunSummary["state"] | NestedStepSummary[
1125
1338
  if (state === "running") return theme.fg("accent", runningGlyph(seed));
1126
1339
  if (state === "complete" || state === "completed") return theme.fg("success", "✓");
1127
1340
  if (state === "failed") return theme.fg("error", "✗");
1341
+ if (state === "partial") return theme.fg("warning", "■");
1128
1342
  if (state === "paused") return theme.fg("warning", "■");
1129
1343
  if (state === "stopped") return theme.fg("warning", "■");
1130
1344
  return theme.fg("muted", "◦");
@@ -1172,6 +1386,7 @@ function nestedActivity(input: Pick<NestedRunSummary | NestedStepSummary, "activ
1172
1386
  if (state === "queued" || state === "pending") return "queued…";
1173
1387
  if (state === "paused") return "Paused";
1174
1388
  if (state === "stopped") return "Stopped";
1389
+ if (state === "partial") return "Partial";
1175
1390
  if (state === "failed") return "Failed";
1176
1391
  return "Done";
1177
1392
  }
@@ -1196,7 +1411,7 @@ function formatNestedWidgetLines(children: NestedRunSummary[] | undefined, theme
1196
1411
  const activity = nestedActivity(step, state, snapshotNow ?? fallback);
1197
1412
  const timestamp = "status" in step ? nestedStepTimestamp(step, fallback) : formatClockTime(nestedRunEventTime(step));
1198
1413
  const error = step.error ? ` · ${step.error}` : "";
1199
- const name = "status" in step ? step.agent : nestedRunName(step);
1414
+ const name = "status" in step ? childDisplayName(step) : nestedRunName(step);
1200
1415
  rows.push({
1201
1416
  prefix,
1202
1417
  text: `${nestedTimestampPrefix(timestamp)}${nestedStatusGlyph(state, theme)} ${name} · ${state}${modelThinking ? ` · ${modelThinking}` : ""}${activity ? ` · ${activity}` : ""}${error}`,
@@ -1258,7 +1473,7 @@ function formatNestedWidgetLines(children: NestedRunSummary[] | undefined, theme
1258
1473
  for (const step of child.steps ?? []) {
1259
1474
  if (lines.length >= lineBudget) return;
1260
1475
  const modelThinking = formatModelThinking(step.model, step.thinking);
1261
- lines.push(theme.fg("dim", `${prefix} ↳ ${nestedTimestampPrefix(nestedStepTimestamp(step, child.lastUpdate))}${nestedStatusGlyph(step.status, theme)} ${step.agent} · ${step.status}${modelThinking ? ` · ${modelThinking}` : ""} · ${nestedActivity(step, step.status, snapshotNow ?? child.lastUpdate)}`));
1476
+ 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
1477
  append(step.children, depth + 1, `${prefix} `);
1263
1478
  }
1264
1479
  append(child.children, depth + 1, `${prefix} `);
@@ -1282,7 +1497,9 @@ function foregroundStyleWidgetStepLines(
1282
1497
  const status = widgetStepStatus(step.status, theme);
1283
1498
  const stats = widgetStepStats(theme, step);
1284
1499
  const modelDisplay = modelThinkingBadge(theme, step.model, step.thinking);
1285
- const lines = [` ${widgetStepGlyph(step.status, theme, widgetStepRunningSeed(step, index - 1), frame)} ${itemTitle} ${index}/${total}: ${themeBold(theme, step.agent)}${contextModeBadge(theme, step.context)} ${theme.fg("dim", "·")} ${status}${modelDisplay}${stats ? ` ${theme.fg("dim", "·")} ${stats}` : ""}`];
1500
+ const lines = [` ${widgetStepGlyph(step.status, theme, widgetStepRunningSeed(step, index - 1), frame)} ${itemTitle} ${index}/${total}: ${themeBold(theme, childDisplayName(step))}${contextModeBadge(theme, step.context)} ${theme.fg("dim", "·")} ${status}${modelDisplay}${stats ? ` ${theme.fg("dim", "·")} ${stats}` : ""}`];
1501
+ const lane = projectAsyncLane(job, step);
1502
+ if (lane) lines.push(...formatLaneProjectionLines(lane, theme, " "));
1286
1503
  const task = compactTaskText(step.description, step.label);
1287
1504
  if (task) lines.push(` ${theme.fg("dim", `task: ${task}`)}`);
1288
1505
  const activity = widgetStepActivityLine(step, width, expanded, job.updatedAt);
@@ -1310,18 +1527,50 @@ function foregroundStyleWidgetStepLines(
1310
1527
  return lines;
1311
1528
  }
1312
1529
 
1530
+ function hostStepWidgetLines(job: AsyncJobState, theme: Theme, indent: string): string[] {
1531
+ const rows = projectAsyncWorkflowRows([], job.hostSteps).filter((row) => row.kind);
1532
+ const visible = rows.slice(0, 8);
1533
+ const lines = visible.map((row) => {
1534
+ const state = hostStepVerdictLabel(row.state as HostStepState, row.verdict as HostStepVerdict | undefined);
1535
+ const glyph = state === "running" ? theme.fg("accent", "●")
1536
+ : state === "pending" ? theme.fg("muted", "◦")
1537
+ : state === "pass" ? theme.fg("success", "✓")
1538
+ : state === "fail" || state === "error" ? theme.fg("error", "✗")
1539
+ : theme.fg("warning", "■");
1540
+ const details = [
1541
+ row.provider ? `provider:${row.provider}` : undefined,
1542
+ row.role ? `role:${row.role}` : undefined,
1543
+ row.target,
1544
+ row.detail,
1545
+ row.reasonCode ? `reason:${row.reasonCode}` : undefined,
1546
+ row.freshness?.stale ? "stale" : row.freshness?.observedRef ? `ref:${row.freshness.observedRef}` : undefined,
1547
+ row.reportPath ? `out:${hostStepReportName(row.reportPath)}` : undefined,
1548
+ ].filter(Boolean).join(" · ");
1549
+ return `${indent}${glyph} ${row.kind}: ${row.name} · ${state}${details ? ` · ${details}` : ""}`;
1550
+ });
1551
+ if (rows.length > visible.length) lines.push(`${indent}${theme.fg("dim", `… +${rows.length - visible.length} host steps hidden`)}`);
1552
+ return lines;
1553
+ }
1554
+
1313
1555
  function foregroundStyleWidgetDetails(job: AsyncJobState, theme: Theme, expanded: boolean, width: number, frame?: number): string[] {
1314
- if (!job.steps?.length) return [
1315
- ` ${theme.fg("dim", `⎿ ${widgetActivity(job)}`)}`,
1316
- ...formatNestedWidgetLines(job.nestedChildren, theme, width, expanded, job.updatedAt, expanded ? 12 : 6).map((line) => ` ${line}`),
1317
- ];
1556
+ if (!job.steps?.length) {
1557
+ const lane = projectAsyncLane(job);
1558
+ return [
1559
+ ...workflowPreflightLines(job, expanded),
1560
+ ...(lane ? formatLaneProjectionLines(lane, theme, " ") : []),
1561
+ ...hostStepWidgetLines(job, theme, " "),
1562
+ ` ${theme.fg("dim", `⎿ ${widgetActivity(job)}`)}`,
1563
+ ...formatNestedWidgetLines(job.nestedChildren, theme, width, expanded, job.updatedAt, expanded ? 12 : 6).map((line) => ` ${line}`),
1564
+ ];
1565
+ }
1318
1566
  if (job.mode === "chain" && !job.activeParallelGroup && job.parallelGroups?.length) return widgetChainDetails(job, theme, expanded, width, frame);
1319
1567
  const total = job.stepsTotal ?? job.steps.length;
1320
1568
  const itemTitle = job.mode === "parallel" || job.activeParallelGroup ? "Agent" : "Step";
1321
- const lines: string[] = [];
1569
+ const lines: string[] = workflowPreflightLines(job, expanded);
1322
1570
  for (const [index, step] of job.steps.entries()) {
1323
1571
  lines.push(...foregroundStyleWidgetStepLines(job, theme, step, itemTitle, index + 1, total, expanded, width, frame));
1324
1572
  }
1573
+ lines.push(...hostStepWidgetLines(job, theme, " "));
1325
1574
  const attached = new Set(job.steps.flatMap((step) => step.children?.map((child) => child.id) ?? []));
1326
1575
  const unattached = job.nestedChildren?.filter((child) => !attached.has(child.id)) ?? [];
1327
1576
  for (const nestedLine of formatNestedWidgetLines(unattached, theme, width, expanded, job.updatedAt, expanded ? 12 : 6)) {
@@ -1357,9 +1606,12 @@ function compactSingleWidgetLines(job: AsyncJobState, theme: Theme, width: numbe
1357
1606
  const modelDisplay = modelThinkingBadge(theme, step.model, step.thinking);
1358
1607
  const task = compactTaskText(step.description, step.label);
1359
1608
  const taskSuffix = task ? ` ${theme.fg("dim", "·")} ${theme.fg("dim", `task: ${task}`)}` : "";
1360
- lines.push(` ${widgetStepGlyph(step.status, theme, widgetStepRunningSeed(step, index), frame)} ${itemTitle} ${index + 1}/${total}: ${themeBold(theme, step.agent)}${contextModeBadge(theme, step.context)} ${theme.fg("dim", "·")} ${status}${modelDisplay}${taskSuffix}${activitySuffix}${stepStats ? ` ${theme.fg("dim", "·")} ${stepStats}` : ""}`);
1609
+ lines.push(` ${widgetStepGlyph(step.status, theme, widgetStepRunningSeed(step, index), frame)} ${itemTitle} ${index + 1}/${total}: ${themeBold(theme, childDisplayName(step))}${contextModeBadge(theme, step.context)} ${theme.fg("dim", "·")} ${status}${modelDisplay}${taskSuffix}${activitySuffix}${stepStats ? ` ${theme.fg("dim", "·")} ${stepStats}` : ""}`);
1610
+ const lane = projectAsyncLane(job, step);
1611
+ if (lane) lines.push(...formatLaneProjectionLines(lane, theme, " "));
1361
1612
  for (const nestedLine of formatNestedWidgetLines(step.children, theme, width, false, job.updatedAt, 6)) lines.push(` ${nestedLine}`);
1362
1613
  }
1614
+ lines.push(...hostStepWidgetLines(job, theme, " "));
1363
1615
  if (job.steps.some((step) => step.status === "running")) lines.push(theme.fg("accent", ` ${liveDetailHintText()}`));
1364
1616
  return lines.map((line) => truncLine(line, width));
1365
1617
  }
@@ -1496,9 +1748,18 @@ function progressiveJobLine(job: AsyncJobState, theme: Theme, width: number, fra
1496
1748
  const stats = widgetStats(job, theme);
1497
1749
  const activity = widgetActivity(job);
1498
1750
  const status = job.status === "complete" ? "done" : job.status;
1751
+ const lane = projectAsyncLane(job);
1752
+ const laneSummary = lane
1753
+ ? [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(" · ")
1754
+ : "";
1755
+ const laneSignals = lane
1756
+ ? [lane.next ? `next:${lane.next}` : undefined, ...lane.chips.map((chip) => formatLaneChip(chip, theme))].filter(Boolean).join(" · ")
1757
+ : "";
1499
1758
  const parts = [
1500
1759
  `${themeBold(theme, widgetJobName(job))}${contextModeBadge(theme, job.context)}`,
1501
1760
  theme.fg("dim", status),
1761
+ laneSignals ? laneSignals : "",
1762
+ laneSummary ? theme.fg("dim", laneSummary) : "",
1502
1763
  stats,
1503
1764
  activity && activity.toLowerCase() !== status ? theme.fg("dim", activity) : "",
1504
1765
  ].filter(Boolean);
@@ -1644,6 +1905,7 @@ export function buildWidgetLines(jobs: AsyncJobState[], theme: Theme, width = ge
1644
1905
  items.push([
1645
1906
  `${widgetStatusGlyph(job, theme, frame)} ${themeBold(theme, widgetJobName(job))}${contextModeBadge(theme, job.context)}${stats ? ` ${theme.fg("dim", "·")} ${stats}` : ""}`,
1646
1907
  ` ${theme.fg("dim", `⎿ ${widgetActivity(job)}`)}`,
1908
+ ...widgetLaneDetailLines(job, theme),
1647
1909
  ...widgetParallelAgentDetails(job, theme, expanded, width, frame),
1648
1910
  ]);
1649
1911
  slots--;
@@ -1661,6 +1923,7 @@ export function buildWidgetLines(jobs: AsyncJobState[], theme: Theme, width = ge
1661
1923
  items.push([
1662
1924
  `${widgetStatusGlyph(job, theme, frame)} ${themeBold(theme, widgetJobName(job))}${contextModeBadge(theme, job.context)}${stats ? ` ${theme.fg("dim", "·")} ${stats}` : ""}`,
1663
1925
  ` ${theme.fg("dim", `⎿ ${widgetActivity(job)}`)}`,
1926
+ ...widgetLaneDetailLines(job, theme),
1664
1927
  ...widgetParallelAgentDetails(job, theme, expanded, width, frame),
1665
1928
  ]);
1666
1929
  slots--;
@@ -1728,7 +1991,7 @@ function renderSingleCompact(
1728
1991
  const detailIndent = mainWindowIndent(layout, 1);
1729
1992
  const continuationIndent = mainWindowIndent(layout, 2) + (layout.horizontalSpacing > 0 ? " " : "");
1730
1993
  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.agent))}${modelDisplay}${contextBadge}${stats ? ` ${theme.fg("dim", "·")} ${stats}` : ""}`, width), 0, 0));
1994
+ c.addChild(new Text(truncLine(`${resultGlyph(r, output, theme, isRunning, undefined, frame)} ${theme.fg("toolTitle", theme.bold(childDisplayName(r)))}${modelDisplay}${contextBadge}${stats ? ` ${theme.fg("dim", "·")} ${stats}` : ""}`, width), 0, 0));
1732
1995
 
1733
1996
  if (isRunning && r.progress) {
1734
1997
  const task = compactTaskText(r.task);
@@ -1761,6 +2024,7 @@ function renderSingleCompact(
1761
2024
  }
1762
2025
 
1763
2026
  function workflowRowGlyph(row: WorkflowChatProgressRow, theme: Theme, frame?: number): string {
2027
+ if (row.state === "planned") return theme.fg("muted", "◦");
1764
2028
  if (row.state === "running") return theme.fg("accent", runningGlyph(frame));
1765
2029
  if (row.state === "complete") return theme.fg("success", "✓");
1766
2030
  if (row.state === "detached" || row.state === "stopped") return theme.fg("warning", "■");
@@ -1769,6 +2033,7 @@ function workflowRowGlyph(row: WorkflowChatProgressRow, theme: Theme, frame?: nu
1769
2033
 
1770
2034
  function workflowRowStateLabel(row: WorkflowChatProgressRow, theme: Theme): string {
1771
2035
  const label = (row.state === "complete" ? "complete" : row.state).padEnd(8);
2036
+ if (row.state === "planned") return theme.fg("dim", label);
1772
2037
  if (row.state === "running") return theme.fg("accent", label);
1773
2038
  if (row.state === "complete") return theme.fg("success", label);
1774
2039
  if (row.state === "detached" || row.state === "stopped") return theme.fg("warning", label);
@@ -1783,9 +2048,9 @@ function workflowOverallState(rows: WorkflowChatProgressRow[], hasTerminalValue:
1783
2048
  return "running";
1784
2049
  }
1785
2050
 
1786
- function renderWorkflowChatProgress(d: Details, result: AgentToolResult<Details>, theme: Theme, layout: MainWindowRenderLayout, frame?: number): Component {
2051
+ function renderWorkflowChatProgress(d: Details, result: AgentToolResult<Details>, theme: Theme, layout: MainWindowRenderLayout, frame?: number, expanded = false): Component {
1787
2052
  const workflow = d.workflow;
1788
- const rows = workflow ? buildWorkflowChatProgressRows(workflow.trace) : [];
2053
+ const rows = workflow ? buildWorkflowChatProgressRows(workflow.trace, d.preflight) : d.preflight ? buildWorkflowChatProgressRows([], d.preflight) : [];
1789
2054
  const state = workflowOverallState(rows, workflow?.value !== undefined, result.isError);
1790
2055
  const glyph = state === "running" ? theme.fg("accent", runningGlyph(frame)) : state === "complete" ? theme.fg("success", "✓") : state === "paused" ? theme.fg("warning", "■") : theme.fg("error", "✗");
1791
2056
  const width = getTermWidth() - 4;
@@ -1796,6 +2061,7 @@ function renderWorkflowChatProgress(d: Details, result: AgentToolResult<Details>
1796
2061
  const rowIndent = mainWindowIndent(layout, 1);
1797
2062
  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
2063
  c.addChild(new Text(truncLine(theme.fg("dim", `${rowIndent}Repo ${repoLabel}`), width), 0, 0));
2064
+ if (d.preflight) c.addChild(new Text(truncLine(theme.fg("dim", formatWorkflowPreflightPlanSummary(d.preflight, { indent: rowIndent })), width), 0, 0));
1799
2065
  if (phase) c.addChild(new Text(truncLine(theme.fg("dim", `${rowIndent}Phase ${phase}`), width), 0, 0));
1800
2066
  if (rows.length === 0) {
1801
2067
  c.addChild(new Text(truncLine(theme.fg("dim", `${rowIndent}◦ waiting for workflow child launches`), width), 0, 0));
@@ -1809,7 +2075,20 @@ function renderWorkflowChatProgress(d: Details, result: AgentToolResult<Details>
1809
2075
  const duration = row.durationMs !== undefined ? ` ${theme.fg("dim", `· ${formatDuration(row.durationMs)}`)}` : "";
1810
2076
  const run = row.runId ? ` ${theme.fg("dim", `[${row.runId.slice(0, 8)}]`)}` : "";
1811
2077
  const error = row.error ? ` ${theme.fg(row.state === "detached" ? "warning" : "error", `· ${compactWorkflowError(row.error)}`)}` : "";
1812
- c.addChild(new Text(truncLine(`${rowIndent}${workflowRowGlyph(row, theme, frame)} ${status} ${theme.bold(row.key)}${label}${run}${duration}${error}`, width), 0, 0));
2078
+ const hints = expanded && row.preflight ? [
2079
+ row.preflight.mode ? `mode:${row.preflight.mode}` : undefined,
2080
+ row.preflight.decision ? `decision:${row.preflight.decision}` : undefined,
2081
+ row.preflight.claims?.length ? `claims:${row.preflight.claims.join(",")}` : undefined,
2082
+ row.preflight.expectedOutput ? `expected:${row.preflight.expectedOutput}` : undefined,
2083
+ row.preflight.independence ? `independence:${row.preflight.independence}` : undefined,
2084
+ ].filter((value): value is string => Boolean(value)).join(" · ") : "";
2085
+ 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));
2086
+ }
2087
+ if (workflow?.preflightWarnings?.length) {
2088
+ const warningLines = expanded
2089
+ ? formatWorkflowPreflightWarnings(workflow.preflightWarnings, { indent: rowIndent }).split("\n")
2090
+ : [formatWorkflowPreflightWarningSummary(workflow.preflightWarnings, { indent: rowIndent, hint: "expand for debug" })];
2091
+ for (const warningLine of warningLines) c.addChild(new Text(truncLine(theme.fg("warning", warningLine), width), 0, 0));
1813
2092
  }
1814
2093
  if (workflow?.emits.length) c.addChild(new Text(truncLine(theme.fg("dim", `${rowIndent}Emits ${workflow.emits.length}`), width), 0, 0));
1815
2094
  return c;
@@ -1825,6 +2104,7 @@ function renderMultiCompact(d: Details, theme: Theme, layout: MainWindowRenderLa
1825
2104
  || workflowGraphHasStatus(d, ["failed"]);
1826
2105
  const paused = d.results.some((r) => r.interrupted)
1827
2106
  || workflowGraphHasStatus(d, ["paused"]);
2107
+ const partial = workflowGraphHasStatus(d, ["partial"]);
1828
2108
  let totalSummary = d.progressSummary;
1829
2109
  if (!totalSummary) {
1830
2110
  let sawProgress = false;
@@ -1848,6 +2128,7 @@ function renderMultiCompact(d: Details, theme: Theme, layout: MainWindowRenderLa
1848
2128
  stopped,
1849
2129
  interrupted: paused,
1850
2130
  failed,
2131
+ partial,
1851
2132
  seed: runningSeed(progressRunningSeed(totalSummary), d.currentStepIndex),
1852
2133
  frame,
1853
2134
  });
@@ -1857,7 +2138,6 @@ function renderMultiCompact(d: Details, theme: Theme, layout: MainWindowRenderLa
1857
2138
  const width = getTermWidth() - 4;
1858
2139
  const rowIndent = mainWindowIndent(layout, 1);
1859
2140
  const detailIndent = mainWindowIndent(layout, 2);
1860
- const continuationIndent = mainWindowIndent(layout, 3) + (layout.horizontalSpacing > 0 ? " " : "");
1861
2141
  c.addChild(new Text(truncLine(`${glyph} ${theme.fg("toolTitle", theme.bold(d.mode))}${contextBadge}${stats ? ` ${theme.fg("dim", "·")} ${stats}` : ""}`, width), 0, 0));
1862
2142
 
1863
2143
  const useResultsDirectly = multiLabel.hasParallelInChain || !d.chainAgents?.length;
@@ -1869,7 +2149,8 @@ function renderMultiCompact(d: Details, theme: Theme, layout: MainWindowRenderLa
1869
2149
  const r = d.results[i];
1870
2150
  const fallbackLabel = itemTitle.toLowerCase();
1871
2151
  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}`) };
2152
+ const fallbackAgent = useResultsDirectly ? (r?.agent || `${fallbackLabel}-${rowNumber}`) : (d.chainAgents![i] || r?.agent || `${fallbackLabel}-${rowNumber}`);
2153
+ return { kind: "result", resultIndex: i, rowNumber, agentName: r?.sessionName?.trim() || fallbackAgent };
1873
2154
  });
1874
2155
  for (const entry of renderEntries) {
1875
2156
  if (entry.kind === "placeholder") {
@@ -1951,7 +2232,8 @@ export function renderSubagentSummary(
1951
2232
  const failed = result.isError === true
1952
2233
  || results.some((entry) => !hasTerminalResultFlag(entry) && entry.exitCode !== 0 && !isResultRunning(entry))
1953
2234
  || Boolean(details && workflowGraphHasStatus(details, ["failed"]));
1954
- const state = running ? "running" : failed ? "failed" : stopped ? "stopped" : paused ? "paused" : "completed";
2235
+ const partial = Boolean(details && workflowGraphHasStatus(details, ["partial"]));
2236
+ const state = running ? "running" : failed ? "failed" : stopped ? "stopped" : paused ? "paused" : partial ? "partial" : "completed";
1955
2237
  const glyph = state === "running"
1956
2238
  ? theme.fg("accent", STATIC_RUNNING_GLYPH)
1957
2239
  : state === "completed"
@@ -1960,7 +2242,7 @@ export function renderSubagentSummary(
1960
2242
  ? theme.fg("error", "✗")
1961
2243
  : theme.fg("warning", "■");
1962
2244
  const label = details?.mode === "single" && results.length === 1
1963
- ? results[0]?.agent || "subagent"
2245
+ ? childDisplayName(results[0])
1964
2246
  : details?.mode || "subagent";
1965
2247
  return new Text(
1966
2248
  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 +2266,7 @@ export function renderSubagentResult(
1984
2266
  const compact = (component: Component): Component => capCompactMainWindowResult(component, layout, theme, !options.expanded);
1985
2267
  const d = result.details;
1986
2268
  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));
2269
+ return compact(renderWorkflowChatProgress(d, result, theme, options.expanded ? resolveMainWindowRenderLayout() : layout, frame, options.expanded));
1988
2270
  }
1989
2271
  if (!d || !d.results.length) {
1990
2272
  const t = result.content[0];
@@ -1995,9 +2277,12 @@ export function renderSubagentResult(
1995
2277
  if (d && !options.expanded && !result.isError) {
1996
2278
  const lines = text.split(/\r?\n/);
1997
2279
  const firstNonEmptyLine = lines.find((line) => line.trim())?.trim() || "(no output)";
2280
+ const compactLine = d.mode === "workflow" && d.preflight
2281
+ ? formatWorkflowPreflightPlanSummary(d.preflight)
2282
+ : firstNonEmptyLine;
1998
2283
  const c = new Container();
1999
2284
  const detailIndent = mainWindowIndent(layout, 1);
2000
- c.addChild(new Text(truncLine(`${contextPrefix}${firstNonEmptyLine} · ${lines.length} lines`, width), 0, 0));
2285
+ c.addChild(new Text(truncLine(`${contextPrefix}${compactLine} · ${lines.length} lines`, width), 0, 0));
2001
2286
  c.addChild(new Text(truncLine(theme.fg("accent", `${detailIndent}Press ${liveDetailKeyText()} for full output`), width), 0, 0));
2002
2287
  return compact(c);
2003
2288
  }
@@ -2030,7 +2315,7 @@ export function renderSubagentResult(
2030
2315
  const fit = (text: string) => expanded ? text : truncLine(text, w);
2031
2316
  const toolCallLines = getToolCallLines(r, expanded);
2032
2317
  const c = new Container();
2033
- c.addChild(new Text(fit(`${presentation.glyph} ${theme.fg("toolTitle", theme.bold(r.agent))}${contextBadge}${progressInfo} ${theme.fg("dim", "·")} ${presentation.label}`), 0, 0));
2318
+ c.addChild(new Text(fit(`${presentation.glyph} ${theme.fg("toolTitle", theme.bold(childDisplayName(r)))}${contextBadge}${progressInfo} ${theme.fg("dim", "·")} ${presentation.label}`), 0, 0));
2034
2319
  c.addChild(new Spacer(1));
2035
2320
  const taskMaxLen = Math.max(20, w - 8);
2036
2321
  const taskPreview = expanded || r.task.length <= taskMaxLen
@@ -2118,6 +2403,7 @@ export function renderSubagentResult(
2118
2403
  || workflowGraphHasStatus(d, ["failed"]);
2119
2404
  const paused = d.results.some((r) => r.interrupted)
2120
2405
  || workflowGraphHasStatus(d, ["paused"]);
2406
+ const partial = workflowGraphHasStatus(d, ["partial"]);
2121
2407
  const completedWithoutOutput = d.results.some((r) =>
2122
2408
  !hasTerminalResultFlag(r)
2123
2409
  && r.exitCode === 0
@@ -2130,6 +2416,7 @@ export function renderSubagentResult(
2130
2416
  stopped,
2131
2417
  interrupted: paused,
2132
2418
  failed,
2419
+ partial,
2133
2420
  completedWithoutOutput,
2134
2421
  frame,
2135
2422
  }), theme);
@@ -2169,6 +2456,7 @@ export function renderSubagentResult(
2169
2456
  ? d.chainAgents
2170
2457
  .map((agent, i) => {
2171
2458
  const result = d.results[i];
2459
+ const displayName = result?.sessionName?.trim() || agent;
2172
2460
  const isCurrent = i === (d.currentStepIndex ?? d.results.length);
2173
2461
  const stepPresentation = result
2174
2462
  ? styledResultPresentation(resultPresentation(result, getSingleResultOutput(result), isCurrent && hasRunning && !hasTerminalResultFlag(result)), theme)
@@ -2176,7 +2464,7 @@ export function renderSubagentResult(
2176
2464
  const stepStatus = stepPresentation
2177
2465
  ? `${stepPresentation.glyph} ${stepPresentation.label}`
2178
2466
  : theme.fg("dim", "◦ pending");
2179
- return `${stepStatus} ${agent}${contextModeBadge(theme, result?.context)}`;
2467
+ return `${stepStatus} ${displayName}${contextModeBadge(theme, result?.context)}`;
2180
2468
  })
2181
2469
  .join(theme.fg("dim", " → "))
2182
2470
  : null;
@@ -2203,7 +2491,8 @@ export function renderSubagentResult(
2203
2491
  const i = displayStart + offset;
2204
2492
  const r = d.results[i];
2205
2493
  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}`) };
2494
+ const fallbackAgent = useResultsDirectly ? (r?.agent || `step-${rowNumber}`) : (d.chainAgents![i] || r?.agent || `step-${rowNumber}`);
2495
+ return { kind: "result", resultIndex: i, rowNumber, agentName: r?.sessionName?.trim() || fallbackAgent };
2207
2496
  });
2208
2497
 
2209
2498
  c.addChild(new Spacer(1));
@@ -2243,8 +2532,8 @@ export function renderSubagentResult(
2243
2532
  const stepLabel = entry.rowLabel ?? resultRowLabel(multiLabel, i, stepNumber);
2244
2533
  const contextBadge = contextModeBadge(theme, r.context);
2245
2534
  const stepHeader = rRunning
2246
- ? `${rowPresentation.glyph} ${stepLabel}: ${theme.bold(theme.fg("warning", r.agent))}${contextBadge}${modelDisplay}${stats} ${theme.fg("dim", "·")} ${rowPresentation.label}`
2247
- : `${rowPresentation.glyph} ${stepLabel}: ${theme.bold(r.agent)}${contextBadge}${modelDisplay}${stats} ${theme.fg("dim", "·")} ${rowPresentation.label}`;
2535
+ ? `${rowPresentation.glyph} ${stepLabel}: ${theme.bold(theme.fg("warning", childDisplayName(r)))}${contextBadge}${modelDisplay}${stats} ${theme.fg("dim", "·")} ${rowPresentation.label}`
2536
+ : `${rowPresentation.glyph} ${stepLabel}: ${theme.bold(childDisplayName(r))}${contextBadge}${modelDisplay}${stats} ${theme.fg("dim", "·")} ${rowPresentation.label}`;
2248
2537
  const toolCallLines = getToolCallLines(r, expanded);
2249
2538
  c.addChild(new Text(fit(stepHeader), 0, 0));
2250
2539