pi-subagents 0.63.0 → 0.65.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 (107) hide show
  1. package/CHANGELOG.md +57 -1
  2. package/README.md +2 -2
  3. package/agents/reviewer.md +1 -1
  4. package/agents/scout.md +1 -1
  5. package/docs/agents.md +18 -16
  6. package/docs/configuration.md +7 -15
  7. package/docs/extension-api.md +15 -6
  8. package/docs/missions.md +2 -0
  9. package/docs/observability.md +10 -12
  10. package/docs/tool-reference.md +7 -6
  11. package/docs/watchdog.md +92 -114
  12. package/docs/workflows.md +5 -3
  13. package/package.json +3 -4
  14. package/skills/pi-subagents/references/constraints-and-recipes.md +1 -1
  15. package/skills/pi-subagents/references/execution-controls.md +6 -5
  16. package/src/agents/agent-management.ts +47 -15
  17. package/src/api/capability-ceiling.ts +0 -1
  18. package/src/api/{pi-args.ts → child-tool-plan.ts} +1 -1
  19. package/src/api/preflight.ts +2 -3
  20. package/src/extension/doctor.ts +2 -10
  21. package/src/extension/fanout-child.ts +9 -11
  22. package/src/extension/index.ts +27 -5
  23. package/src/extension/public-execution.ts +14 -0
  24. package/src/extension/rpc.ts +3 -2
  25. package/src/extension/schemas.ts +2 -1
  26. package/src/extension/tool-description.ts +9 -8
  27. package/src/intercom/native-supervisor-channel.ts +138 -60
  28. package/src/intercom/supervisor-ui.ts +243 -0
  29. package/src/runs/background/async-execution.ts +46 -24
  30. package/src/runs/background/async-job-tracker.ts +11 -0
  31. package/src/runs/background/async-resume.ts +11 -2
  32. package/src/runs/background/control-channel.ts +2 -204
  33. package/src/runs/background/notify.ts +41 -2
  34. package/src/runs/background/process-terminal.ts +1 -1
  35. package/src/runs/background/run-child-session.ts +613 -0
  36. package/src/runs/background/run-status.ts +0 -1
  37. package/src/runs/background/runner-aliases.ts +125 -0
  38. package/src/runs/background/runner-child-sessions.ts +31 -0
  39. package/src/runs/background/scheduled-runs.ts +18 -4
  40. package/src/runs/background/subagent-runner.ts +203 -898
  41. package/src/runs/foreground/async-steering-action.ts +1 -17
  42. package/src/runs/foreground/execution.ts +196 -378
  43. package/src/runs/foreground/foreground-control.ts +4 -0
  44. package/src/runs/foreground/subagent-executor.ts +121 -58
  45. package/src/runs/foreground/workflow-foreground-steering.ts +24 -98
  46. package/src/runs/shared/abort-recovery.ts +3 -3
  47. package/src/runs/shared/acceptance.ts +10 -0
  48. package/src/runs/shared/async-status-projection.ts +11 -43
  49. package/src/runs/shared/capability-ceiling.ts +1 -2
  50. package/src/runs/shared/child-hooks.ts +25 -0
  51. package/src/runs/shared/child-identity.ts +13 -2
  52. package/src/runs/shared/child-launch.ts +314 -0
  53. package/src/runs/shared/child-lifecycle.ts +25 -0
  54. package/src/runs/shared/child-runtime-config.ts +126 -0
  55. package/src/runs/shared/child-session.ts +342 -0
  56. package/src/runs/shared/child-tool-plan.ts +530 -0
  57. package/src/runs/shared/claude-code-adapter.ts +5 -1
  58. package/src/runs/shared/completion-guard.ts +1 -1
  59. package/src/runs/shared/external-cli-preflight.ts +16 -0
  60. package/src/runs/shared/mcp-direct-tool-allowlist.ts +5 -4
  61. package/src/runs/shared/model-exclusions.ts +82 -14
  62. package/src/runs/shared/model-fallback.ts +47 -4
  63. package/src/runs/shared/nested-events.ts +29 -45
  64. package/src/runs/shared/nested-path.ts +0 -14
  65. package/src/runs/shared/orca-progress-tabs.ts +12 -7
  66. package/src/runs/shared/parallel-utils.ts +0 -2
  67. package/src/runs/shared/permissions.ts +0 -13
  68. package/src/runs/shared/process-signal.ts +4 -1
  69. package/src/runs/shared/run-fanout-budget.ts +0 -13
  70. package/src/runs/shared/runtime-acknowledged-extensions.ts +0 -27
  71. package/src/runs/shared/structured-output.ts +17 -4
  72. package/src/runs/shared/subagent-control.ts +6 -2
  73. package/src/runs/shared/subagent-prompt-runtime.ts +87 -384
  74. package/src/runs/shared/tool-availability.ts +18 -62
  75. package/src/runs/shared/tool-budget.ts +0 -14
  76. package/src/runs/shared/worktree-cleanup-plan.ts +25 -6
  77. package/src/runs/shared/worktree.ts +117 -30
  78. package/src/shared/child-session-name.ts +1 -1
  79. package/src/shared/jsonl-writer.ts +11 -0
  80. package/src/shared/thinking-ceiling.ts +0 -6
  81. package/src/shared/types.ts +64 -30
  82. package/src/shared/utils.ts +3 -4
  83. package/src/slash/slash-commands.ts +0 -6
  84. package/src/tui/fleet.ts +0 -1
  85. package/src/tui/render.ts +238 -31
  86. package/src/watchdog/child-status.ts +54 -34
  87. package/src/watchdog/diff-tool.ts +77 -0
  88. package/src/watchdog/emission-guard.ts +5 -3
  89. package/src/watchdog/guidance.ts +20 -0
  90. package/src/watchdog/register-child.ts +28 -25
  91. package/src/watchdog/register-main.ts +10 -9
  92. package/src/watchdog/render.ts +4 -5
  93. package/src/watchdog/review.ts +15 -4
  94. package/src/watchdog/rules.ts +70 -0
  95. package/src/watchdog/runtime.ts +75 -92
  96. package/src/watchdog/scope.ts +0 -11
  97. package/src/watchdog/settings.ts +48 -104
  98. package/src/watchdog/types.ts +18 -32
  99. package/src/watchdog/warning-format.ts +0 -1
  100. package/src/workflows/chat-progress.ts +3 -2
  101. package/src/workflows/scripted-workflow.ts +48 -1
  102. package/src/workflows/workflow-checklist.ts +10 -12
  103. package/src/workflows/workflow-preflight.ts +28 -1
  104. package/src/runs/shared/child-protocol.ts +0 -415
  105. package/src/runs/shared/pi-args.ts +0 -1062
  106. package/src/runs/shared/subagent-startup-retry.ts +0 -116
  107. package/src/shared/post-exit-stdio-guard.ts +0 -85
@@ -15,6 +15,7 @@ import type { ThinkingLevel } from "./model-info.ts";
15
15
  import type { GlobalMissionIndexRecord, MissionRecord, MissionStoreConfig } from "../missions/types.ts";
16
16
  import type { ExtensionBindings } from "../runs/shared/extension-bindings.ts";
17
17
  import type { WorkflowChildPermitContext } from "./workflow-child-permit.ts";
18
+ import type { WatchdogWarningDetails } from "../watchdog/types.ts";
18
19
 
19
20
  // ============================================================================
20
21
  // Basic Types
@@ -368,6 +369,7 @@ export interface ControlEvent {
368
369
  tokens?: number;
369
370
  toolCount?: number;
370
371
  currentTool?: string;
372
+ toolCallId?: string;
371
373
  currentToolDurationMs?: number;
372
374
  currentPath?: string;
373
375
  elapsedMs?: number;
@@ -785,6 +787,8 @@ export interface SteeringRecoveryDescriptor {
785
787
  agentContract?: AgentContract;
786
788
  agent: string;
787
789
  sessionFile?: string;
790
+ /** Git ref used to allocate managed worktrees for this run. */
791
+ baseRef?: string;
788
792
  cwd: string;
789
793
  model?: string;
790
794
  modelProvider?: string;
@@ -894,13 +898,19 @@ export interface SubagentResultIntercomPayload {
894
898
  // Progress Tracking
895
899
  // ============================================================================
896
900
 
901
+ export interface ChildWatchdogWarningSummary extends Pick<WatchdogWarningDetails, "severity" | "category" | "summary" | "evidence" | "recommendedAction" | "displayedAt"> {
902
+ /** True when a later assistant turn in the child followed the warning. */
903
+ addressed: boolean;
904
+ stalemate: boolean;
905
+ }
906
+
897
907
  export interface ChildWatchdogProgress {
898
- phase: "idle" | "reviewing" | "autofollow" | "settling" | "stale" | "failed";
908
+ phase: "idle" | "reviewing" | "stale" | "failed";
899
909
  seq: number;
900
910
  lastUpdate: number;
901
- followUpPending: boolean;
902
911
  reason?: string;
903
912
  timedOut?: boolean;
913
+ warnings?: ChildWatchdogWarningSummary[];
904
914
  }
905
915
 
906
916
  export interface AgentProgress {
@@ -1152,15 +1162,6 @@ export interface AcceptanceLedger {
1152
1162
  };
1153
1163
  }
1154
1164
 
1155
- export interface ProtocolOutputLimit {
1156
- code: "protocol_output_limit";
1157
- stream: "stdout" | "stderr";
1158
- limitBytes: number;
1159
- observedBytes: number;
1160
- diagnosticPrefix: string;
1161
- diagnosticTail: string;
1162
- }
1163
-
1164
1165
  export interface LaunchResolvedChildExtensionsV1 {
1165
1166
  version: 1;
1166
1167
  /** This is parent-resolved launch intent, not child-runtime acknowledgement that extensions loaded. */
@@ -1255,7 +1256,6 @@ export interface SingleResult {
1255
1256
  * a signal to reduce input size or re-decompose the task.
1256
1257
  */
1257
1258
  contextOverflow?: boolean;
1258
- protocolError?: ProtocolOutputLimit;
1259
1259
  sessionFile?: string;
1260
1260
  skills?: string[];
1261
1261
  skillsWarning?: string;
@@ -1362,7 +1362,7 @@ export interface AgentCapabilityRow {
1362
1362
  executable: boolean;
1363
1363
  restrictionSources?: string[];
1364
1364
  aliases?: string[];
1365
- runner: { type: "pi" } | { type: "external-cli"; adapter?: string; capabilities: ExternalCliCapabilities } | { type: "external-job"; provider: string; available?: boolean; capabilities: ExternalJobRunnerStatus["capabilities"] };
1365
+ runner: { type: "pi" } | { type: "external-cli"; adapter?: string; command: string; available: boolean; unavailableReason?: string; capabilities: ExternalCliCapabilities } | { type: "external-job"; provider: string; available?: boolean; capabilities: ExternalJobRunnerStatus["capabilities"] };
1366
1366
  tools: { ambient: boolean; names: string[]; excludeTools?: string[]; mcpDirectTools: string[]; mutationTools?: string[] };
1367
1367
  model?: { value?: string; fallbackModels?: string[]; thinking?: string | false };
1368
1368
  execution?: { defaultAsync?: boolean; timeoutMs?: number };
@@ -2100,6 +2100,18 @@ export interface ForegroundChildControl {
2100
2100
  toolCount?: number;
2101
2101
  interrupt?: () => boolean;
2102
2102
  detach?: () => boolean;
2103
+ /** Steer the live in-process child session; undefined until the session exists. */
2104
+ steer?: (input: ForegroundSteerInput) => Promise<ForegroundSteerOutcome>;
2105
+ }
2106
+
2107
+ export interface ForegroundSteerInput {
2108
+ message: string;
2109
+ mode?: "steer" | "follow_up" | "auto";
2110
+ }
2111
+
2112
+ export interface ForegroundSteerOutcome {
2113
+ state: "delivered" | "queued" | "failed";
2114
+ reason?: string;
2103
2115
  }
2104
2116
 
2105
2117
  export interface ForegroundRunControl {
@@ -2108,8 +2120,6 @@ export interface ForegroundRunControl {
2108
2120
  parentWorkflowRunId?: string;
2109
2121
  /** Stable workflow lane key for this live foreground child. */
2110
2122
  workflowKey?: string;
2111
- /** Private control root used to steer a live workflow-owned child. */
2112
- workflowSteeringDir?: string;
2113
2123
  /** Originating parent session; required for public fleet projection. */
2114
2124
  sessionId?: string;
2115
2125
  mode: SubagentRunMode;
@@ -2151,6 +2161,7 @@ export interface ForegroundRunControl {
2151
2161
  nestedChildren?: NestedRunSummary[];
2152
2162
  interrupt?: () => boolean;
2153
2163
  detach?: () => boolean;
2164
+ steer?: ForegroundChildControl["steer"];
2154
2165
  }
2155
2166
 
2156
2167
  export interface WaitSubscriptionRecord {
@@ -2311,17 +2322,25 @@ export interface SubagentChildStatusEvent {
2311
2322
  // Execution Options
2312
2323
  // ============================================================================
2313
2324
 
2325
+ /** Live controls for one in-process foreground child session. */
2326
+ export interface ForegroundChildSessionControls {
2327
+ steer: (text: string) => Promise<void>;
2328
+ followUp: (text: string) => Promise<void>;
2329
+ }
2330
+
2314
2331
  export interface RunSyncOptions {
2315
2332
  /** Exact discovery provenance for an unknown-agent error; omission uses defensive fallback discovery. */
2316
2333
  unknownAgentDiagnosticContext?: import("../agents/agents.ts").UnknownAgentDiagnosticContext;
2334
+ /** Session factory for the in-process child; defaults to the process-wide factory. */
2335
+ childSessionFactory?: import("../runs/shared/child-session.ts").ChildSessionFactory;
2336
+ /** The launching executor's own child runtime when it is itself an in-process child. */
2337
+ childRuntime?: import("../runs/shared/child-runtime-config.ts").ChildRuntimeConfig;
2338
+ /** Fires once the child session exists and can be steered. */
2339
+ onChildSession?: (controls: ForegroundChildSessionControls) => void;
2317
2340
  /** Opt-in global permission rules; missing tools remain allowed. */
2318
2341
  permissions?: import("../runs/shared/permissions.ts").PermissionConfig;
2319
2342
  /** Session id of the direct parent session for permission-system ask forwarding. */
2320
2343
  parentSessionId?: string;
2321
- /** Private prompt-runtime steering transport for workflow-owned foreground children. */
2322
- steerInboxDir?: string;
2323
- steerCapabilityPath?: string;
2324
- steerAckDir?: string;
2325
2344
  /** Resolved launch context for this child. */
2326
2345
  context?: "fresh" | "fork";
2327
2346
  cwd?: string;
@@ -2403,6 +2422,7 @@ export interface RunSyncOptions {
2403
2422
  schemaPath: string;
2404
2423
  outputPath: string;
2405
2424
  acceptanceReportPath?: string;
2425
+ acceptanceReportRequired?: boolean;
2406
2426
  };
2407
2427
  agentContract?: AgentContract;
2408
2428
  acceptance?: AcceptanceInput;
@@ -2758,8 +2778,17 @@ export function normalizeMaxSubagentDepth(value: unknown): number | undefined {
2758
2778
  return normalizeNonNegativeInteger(value);
2759
2779
  }
2760
2780
 
2761
- export function resolveCurrentMaxSubagentDepth(configMaxDepth?: number): number {
2762
- return normalizeMaxSubagentDepth(process.env.PI_SUBAGENT_MAX_DEPTH)
2781
+ /** Depth context of the executor's own child runtime, when it runs as an in-process child. */
2782
+ export interface SubagentDepthContext {
2783
+ depth: number;
2784
+ maxDepth?: number;
2785
+ }
2786
+
2787
+ /** Operator override for the top-level parent; children inherit their limit through their runtime config. */
2788
+ export const SUBAGENT_MAX_DEPTH_ENV = "PI_SUBAGENT_MAX_DEPTH";
2789
+
2790
+ export function resolveCurrentMaxSubagentDepth(configMaxDepth?: number, runtime?: SubagentDepthContext): number {
2791
+ return normalizeMaxSubagentDepth(runtime ? runtime.maxDepth : process.env[SUBAGENT_MAX_DEPTH_ENV])
2763
2792
  ?? normalizeMaxSubagentDepth(configMaxDepth)
2764
2793
  ?? DEFAULT_SUBAGENT_MAX_DEPTH;
2765
2794
  }
@@ -2770,19 +2799,24 @@ export function resolveChildMaxSubagentDepth(parentMaxDepth: number, agentMaxDep
2770
2799
  return normalizedAgent === undefined ? normalizedParent : Math.min(normalizedParent, normalizedAgent);
2771
2800
  }
2772
2801
 
2773
- export function checkSubagentDepth(configMaxDepth?: number): { blocked: boolean; depth: number; maxDepth: number } {
2774
- const depth = Number(process.env.PI_SUBAGENT_DEPTH ?? "0");
2775
- const maxDepth = resolveCurrentMaxSubagentDepth(configMaxDepth);
2776
- const blocked = Number.isFinite(depth) && depth >= maxDepth;
2802
+ /** Depth of the executor itself: 0 for a top-level parent, its own child depth otherwise. */
2803
+ export function resolveCurrentSubagentDepth(runtime?: SubagentDepthContext): number {
2804
+ const depth = runtime ? runtime.depth : 0;
2805
+ return Number.isFinite(depth) ? depth : 0;
2806
+ }
2807
+
2808
+ export function checkSubagentDepth(configMaxDepth?: number, runtime?: SubagentDepthContext): { blocked: boolean; depth: number; maxDepth: number } {
2809
+ const depth = resolveCurrentSubagentDepth(runtime);
2810
+ const maxDepth = resolveCurrentMaxSubagentDepth(configMaxDepth, runtime);
2811
+ const blocked = depth >= maxDepth;
2777
2812
  return { blocked, depth, maxDepth };
2778
2813
  }
2779
2814
 
2780
- export function getSubagentDepthEnv(maxDepth?: number): Record<string, string> {
2781
- const parentDepth = Number(process.env.PI_SUBAGENT_DEPTH ?? "0");
2782
- const nextDepth = Number.isFinite(parentDepth) ? parentDepth + 1 : 1;
2815
+ /** Depth context handed to a child launched by an executor at `runtime` (undefined for a top-level parent). */
2816
+ export function resolveChildDepth(maxDepth?: number, runtime?: SubagentDepthContext): Required<SubagentDepthContext> {
2783
2817
  return {
2784
- PI_SUBAGENT_DEPTH: String(nextDepth),
2785
- PI_SUBAGENT_MAX_DEPTH: String(normalizeMaxSubagentDepth(maxDepth) ?? resolveCurrentMaxSubagentDepth()),
2818
+ depth: resolveCurrentSubagentDepth(runtime) + 1,
2819
+ maxDepth: normalizeMaxSubagentDepth(maxDepth) ?? resolveCurrentMaxSubagentDepth(undefined, runtime),
2786
2820
  };
2787
2821
  }
2788
2822
 
@@ -413,10 +413,9 @@ export function compactForegroundDetails(details: Details): Details {
413
413
  *
414
414
  * The completed-compaction helpers above bail out while a child is still
415
415
  * `running`, so a long or deeply nested fan-out streams full, unbounded progress on
416
- * every tick. Pi serializes each streamed `tool_execution_update` as a single
417
- * child-stdout line, which the parent reads under `MAX_CHILD_PENDING_LINE_BYTES`;
418
- * an unbounded running snapshot can cross that cap and kill the child with
419
- * `protocol_output_limit`.
416
+ * every tick. The parent records every streamed `tool_execution_update` in its
417
+ * transcript and `events.jsonl`, so an unbounded running snapshot grows those
418
+ * artifacts and the live display state without bound.
420
419
  *
421
420
  * These bound the STREAMED snapshot only. The final returned result keeps the full
422
421
  * live progress and message transcript, and every live-display consumer already
@@ -21,7 +21,6 @@ import { formatTokens, shortenPath } from "../shared/formatters.ts";
21
21
  import { listAsyncRuns, formatAsyncRunProgressLabel, type AsyncRunSummary } from "../runs/background/async-status.ts";
22
22
  import { encodeInspectReply, handleInspectRpcArgs, INSPECT_WIDGET_KEY } from "../runs/background/inspect-rpc.ts";
23
23
  import { listScheduledRunSummaries } from "../runs/background/scheduled-runs.ts";
24
- import { SUBAGENT_FANOUT_CHILD_ENV } from "../runs/shared/pi-args.ts";
25
24
  import { resolveAsyncStatusChild } from "../runs/shared/child-identity.ts";
26
25
  import { readStatus } from "../shared/utils.ts";
27
26
  import { getArtifactPaths, getArtifactsDir } from "../shared/artifacts.ts";
@@ -1030,11 +1029,6 @@ export function registerSlashCommands(
1030
1029
  return;
1031
1030
  }
1032
1031
 
1033
- if (process.env[SUBAGENT_FANOUT_CHILD_ENV] === "1") {
1034
- sendSlashText(pi, "Selector unavailable in child-safe fanout mode. Pass an explicit current-session top-level async run id, for example `/subagents-stop <run-id>` or `subagent({ action: \"stop\", id: \"<run-id>\" })`.");
1035
- return;
1036
- }
1037
-
1038
1032
  let targets: StopSelectorTarget[];
1039
1033
  try {
1040
1034
  targets = discoverStopTargets(ctx, state);
package/src/tui/fleet.ts CHANGED
@@ -204,7 +204,6 @@ export function collectFleetSnapshot(
204
204
  if (!control.parentWorkflowRunId
205
205
  || !workflowParentIds.has(control.parentWorkflowRunId)
206
206
  || !belongsToCurrentSession(control.sessionId, state.currentSessionId)
207
- || !control.workflowSteeringDir
208
207
  || activeChildCount === 0) continue;
209
208
  liveWorkflowForegroundControls.add(control);
210
209
  workflowForegroundChildCounts.set(control.parentWorkflowRunId, (workflowForegroundChildCounts.get(control.parentWorkflowRunId) ?? 0) + activeChildCount);
package/src/tui/render.ts CHANGED
@@ -7,6 +7,7 @@ import { createHash } from "node:crypto";
7
7
  import type { AgentToolResult } from "@earendil-works/pi-agent-core";
8
8
  import { getMarkdownTheme, keyText, type ExtensionContext } from "@earendil-works/pi-coding-agent";
9
9
  import { Container, Markdown, Spacer, Text, visibleWidth, type Component } from "@earendil-works/pi-tui";
10
+ import { unresolvedChildWatchdogBlockers } from "../watchdog/child-status.ts";
10
11
  import {
11
12
  type AgentProgress,
12
13
  type AsyncJobState,
@@ -93,6 +94,10 @@ export function liveDetailHintText(): string {
93
94
  return `Press ${liveDetailKeyText()} for live detail · ${formatShortcutLabel(FLEET_OPEN_SHORTCUT)} Fleet`;
94
95
  }
95
96
 
97
+ function workflowDetailHintText(): string {
98
+ return `Press ${liveDetailKeyText()} for details · ${formatShortcutLabel(FLEET_OPEN_SHORTCUT)} Fleet`;
99
+ }
100
+
96
101
  function foregroundSingleHintText(shortcut?: string): string {
97
102
  if (!shortcut) return liveDetailHintText();
98
103
  const label = formatShortcutLabel(shortcut);
@@ -360,6 +365,7 @@ function workflowStepPriority(step: AsyncJobStep, currentNodeId?: string): numbe
360
365
  || step.turnBudgetExceeded === true
361
366
  || step.activityState === "needs_attention"
362
367
  || step.watchdog?.phase === "stale"
368
+ || unresolvedChildWatchdogBlockers(step.watchdog).length > 0
363
369
  || gate !== undefined
364
370
  ) return 1;
365
371
  if (step.status === "pending") return 2;
@@ -505,6 +511,7 @@ function laneGate(step: AsyncJobStep | undefined): string | undefined {
505
511
  }
506
512
 
507
513
  function laneNextAction(state: AsyncLaneProjection["state"], step: AsyncJobStep | undefined, output: string | undefined, gate: string | undefined): string | undefined {
514
+ if (unresolvedChildWatchdogBlockers(step?.watchdog).length > 0) return "resolve watchdog blockers";
508
515
  if (step?.watchdog?.phase === "stale") return "inspect stale state";
509
516
  if (step?.toolBudgetBlocked === true || step?.turnBudgetExceeded === true) return "inspect blocked state";
510
517
  if (gate === "review blockers") return "resolve review blockers";
@@ -543,6 +550,7 @@ export function projectAsyncLane(job: AsyncJobState, ...args: [selectedStep?: As
543
550
  selectedStep?.activityState === "active_long_running" ? "long-running" : undefined,
544
551
  selectedStep?.activityState === "needs_attention" ? "attention" : undefined,
545
552
  selectedStep?.watchdog?.phase === "stale" ? "stale" : undefined,
553
+ unresolvedChildWatchdogBlockers(selectedStep?.watchdog).length > 0 ? `wd:${unresolvedChildWatchdogBlockers(selectedStep?.watchdog).length}` : undefined,
546
554
  selectedStep?.toolBudgetBlocked === true || selectedStep?.turnBudgetExceeded === true ? "blocked" : undefined,
547
555
  ].filter((chip): chip is string => Boolean(chip));
548
556
  const state = isTerminalLaneState(job.status) ? job.status : selectedStep?.status ?? job.status;
@@ -605,15 +613,8 @@ function widgetLaneDetailLines(job: AsyncJobState, theme: Theme, projection?: Wo
605
613
  return lane ? formatLaneProjectionLines(lane, theme, " ") : [];
606
614
  }
607
615
 
608
- function workflowPreflightLines(job: AsyncJobState, expanded = false): string[] {
616
+ function workflowPreflightLines(job: AsyncJobState): string[] {
609
617
  if (job.mode !== "workflow" || !job.preflight) return [];
610
- if (!expanded) {
611
- const warning = formatWorkflowPreflightWarningSummary(job.workflow?.preflightWarnings, { indent: " ", hint: "expand for debug" });
612
- return [
613
- formatWorkflowPreflightPlanSummary(job.preflight, { indent: " " }),
614
- ...(warning ? [warning] : []),
615
- ];
616
- }
617
618
  return [
618
619
  ...formatWorkflowPreflight(job.preflight, { indent: " " }).split("\n"),
619
620
  ...(job.workflow?.preflightWarnings ? formatWorkflowPreflightWarnings(job.workflow.preflightWarnings, { indent: " " }).split("\n") : []),
@@ -1001,6 +1002,7 @@ function widgetStepRenderKey(step: AsyncJobStep, index: number, expanded = false
1001
1002
  step.execution?.stopped,
1002
1003
  step.execution?.detached,
1003
1004
  step.watchdog?.phase,
1005
+ unresolvedChildWatchdogBlockers(step.watchdog).length,
1004
1006
  step.error,
1005
1007
  expanded ? expandedStepActivityRenderKey(step) : undefined,
1006
1008
  nestedRenderKey(step.children, expanded),
@@ -1053,6 +1055,7 @@ export function widgetRenderKey(job: AsyncJobState, expanded = false): string {
1053
1055
  return JSON.stringify({
1054
1056
  asyncDir: job.asyncDir,
1055
1057
  status: job.status,
1058
+ description: job.mode === "workflow" ? job.description : undefined,
1056
1059
  activityState: job.activityState,
1057
1060
  lastActivityAt: job.lastActivityAt,
1058
1061
  currentTool: job.currentTool,
@@ -1070,8 +1073,8 @@ export function widgetRenderKey(job: AsyncJobState, expanded = false): string {
1070
1073
  currentNodeId: job.workflowGraph.currentNodeId,
1071
1074
  stages: projection.stages.map((node) => [node.id, node.status, node.agent, node.phase, node.label, node.flatIndex, node.outputName, node.structured, node.error]),
1072
1075
  } : undefined,
1073
- preflight: expanded ? job.preflight : job.preflight ? formatWorkflowPreflightPlanSummary(job.preflight) : undefined,
1074
- preflightWarnings: expanded ? job.workflow?.preflightWarnings : job.workflow?.preflightWarnings?.length || undefined,
1076
+ preflight: expanded ? job.preflight : job.preflight?.lanes.map((lane) => [lane.key, lane.mode, lane.decision, lane.claims, lane.expectedOutput]),
1077
+ preflightWarnings: expanded ? job.workflow?.preflightWarnings : undefined,
1075
1078
  checklist: projection.checklist ? {
1076
1079
  total: projection.checklist.total,
1077
1080
  done: projection.checklist.done,
@@ -1079,7 +1082,7 @@ export function widgetRenderKey(job: AsyncJobState, expanded = false): string {
1079
1082
  queued: projection.checklist.queued,
1080
1083
  blocked: projection.checklist.blocked,
1081
1084
  failed: projection.checklist.failed,
1082
- phases: projection.checklist.phases.map((phase) => [phase.key, phase.state, phase.done, phase.total, phase.running, phase.queued, phase.blocked, phase.failed, phase.paused, phase.stopped, phase.items.map((item) => [item.key, item.state, item.currentTool, item.currentPath, item.durationMs, item.toolCount, item.error])]),
1085
+ phases: projection.checklist.phases.map((phase) => [phase.key, phase.state, phase.done, phase.total, phase.running, phase.queued, phase.blocked, phase.failed, phase.paused, phase.stopped, phase.items.map((item) => [item.key, item.label, item.agent, item.state, item.preflight?.key, item.preflight?.mode, item.preflight?.decision, item.preflight?.claims, item.preflight?.expectedOutput, item.currentTool, item.currentPath, item.durationMs, item.toolCount, item.error])]),
1083
1086
  } : undefined,
1084
1087
  steps: job.steps?.map((step, index) => widgetStepRenderKey(step, index, expanded)),
1085
1088
  nestedChildren: nestedRenderKey(job.nestedChildren, expanded),
@@ -1335,6 +1338,208 @@ function workflowChecklistWidgetLines(checklist: WorkflowChecklistProjection | u
1335
1338
  return lines;
1336
1339
  }
1337
1340
 
1341
+ interface CompactWorkflowLaneRow {
1342
+ key: string;
1343
+ state: WorkflowChecklistState;
1344
+ agent?: string;
1345
+ mode?: string;
1346
+ decision?: string;
1347
+ claims?: string;
1348
+ expectedOutput?: string;
1349
+ label?: string;
1350
+ toolUses?: number;
1351
+ durationMs?: number;
1352
+ }
1353
+
1354
+ interface CompactWorkflowLaneCounts {
1355
+ total: number;
1356
+ done: number;
1357
+ active: number;
1358
+ queued: number;
1359
+ blocked: number;
1360
+ failed: number;
1361
+ paused: number;
1362
+ stopped: number;
1363
+ }
1364
+
1365
+ function compactWorkflowLaneState(items: readonly WorkflowChecklistItem[]): WorkflowChecklistState {
1366
+ let state: WorkflowChecklistState | undefined;
1367
+ for (const item of items) {
1368
+ if (state === undefined || workflowChecklistItemPriority(item.state) < workflowChecklistItemPriority(state)) state = item.state;
1369
+ }
1370
+ return state ?? "queued";
1371
+ }
1372
+
1373
+ function compactWorkflowLaneOwner(items: readonly WorkflowChecklistItem[]): string | undefined {
1374
+ const owners = [...new Set(items.map((item) => item.agent ?? item.role).filter((value): value is string => Boolean(value?.trim())))];
1375
+ if (owners.length === 0) return undefined;
1376
+ return boundedLaneValue(owners.length > 2 ? `${owners.slice(0, 2).join(", ")} +${owners.length - 2}` : owners.join(", "), 32);
1377
+ }
1378
+
1379
+ function compactWorkflowLaneLabel(items: readonly WorkflowChecklistItem[], key: string): string | undefined {
1380
+ const labels = [...new Set(items.map((item) => item.label).filter((value): value is string => Boolean(value?.trim() && value.trim() !== key)))];
1381
+ if (labels.length === 0) return undefined;
1382
+ return boundedLaneValue(labels.length > 1 ? `${labels[0]} +${labels.length - 1}` : labels[0], 48);
1383
+ }
1384
+
1385
+ function compactWorkflowLaneRow(key: string, items: readonly WorkflowChecklistItem[], lane = items.find((item) => item.preflight)?.preflight, fallbackState: WorkflowChecklistState = "queued"): CompactWorkflowLaneRow {
1386
+ const state = items.length ? compactWorkflowLaneState(items) : fallbackState;
1387
+ const toolCounts = items.map((item) => item.toolCount).filter((value): value is number => value !== undefined);
1388
+ const durations = items.map((item) => item.durationMs).filter((value): value is number => value !== undefined);
1389
+ const label = compactWorkflowLaneLabel(items, key);
1390
+ return {
1391
+ key: boundedLaneValue(key, 40) ?? key,
1392
+ state,
1393
+ agent: compactWorkflowLaneOwner(items),
1394
+ ...(lane?.mode ? { mode: lane.mode } : {}),
1395
+ ...(lane?.decision ? { decision: boundedLaneValue(lane.decision, 56) } : {}),
1396
+ ...(lane?.claims?.length ? { claims: boundedLaneValue(lane.claims.join(", "), 56) } : {}),
1397
+ ...(lane?.expectedOutput ? { expectedOutput: boundedLaneValue(lane.expectedOutput, 56) } : {}),
1398
+ ...(label ? { label } : {}),
1399
+ ...(toolCounts.length ? { toolUses: toolCounts.reduce((sum, value) => sum + value, 0) } : {}),
1400
+ ...(durations.length ? { durationMs: Math.max(...durations) } : {}),
1401
+ };
1402
+ }
1403
+
1404
+ function compactWorkflowFallbackState(job: AsyncJobState): WorkflowChecklistState {
1405
+ if (job.activityState === "needs_attention" || job.timedOut || job.toolBudgetBlocked || job.turnBudgetExceeded) return "blocked";
1406
+ switch (job.status) {
1407
+ case "complete": return "complete";
1408
+ case "failed": return "failed";
1409
+ case "paused": return "paused";
1410
+ case "stopped": return "stopped";
1411
+ case "partial":
1412
+ case "rejected": return "blocked";
1413
+ default: return "queued";
1414
+ }
1415
+ }
1416
+
1417
+ /** Flatten the loaded checklist into one compact row per declared lane. */
1418
+ function compactWorkflowLaneRows(job: AsyncJobState, checklist: WorkflowChecklistProjection | undefined): CompactWorkflowLaneRow[] {
1419
+ const groups = new Map<string, WorkflowChecklistItem[]>();
1420
+ for (const item of checklist?.phases.flatMap((phase) => phase.items) ?? []) {
1421
+ const key = item.preflight?.key ?? item.key;
1422
+ const group = groups.get(key);
1423
+ if (group) group.push(item);
1424
+ else groups.set(key, [item]);
1425
+ }
1426
+
1427
+ const rows: CompactWorkflowLaneRow[] = [];
1428
+ const preflightKeys = new Set(job.preflight?.lanes.map((lane) => lane.key) ?? []);
1429
+ if (job.preflight?.lanes.length) {
1430
+ for (const lane of job.preflight.lanes) {
1431
+ rows.push(compactWorkflowLaneRow(lane.key, groups.get(lane.key) ?? [], lane, compactWorkflowFallbackState(job)));
1432
+ }
1433
+ }
1434
+
1435
+ for (const [key, items] of groups) {
1436
+ if (preflightKeys.has(key)) continue;
1437
+ rows.push(compactWorkflowLaneRow(key, items));
1438
+ }
1439
+ return rows;
1440
+ }
1441
+
1442
+ function compactWorkflowLaneCounts(rows: readonly CompactWorkflowLaneRow[]): CompactWorkflowLaneCounts {
1443
+ const counts: CompactWorkflowLaneCounts = { total: rows.length, done: 0, active: 0, queued: 0, blocked: 0, failed: 0, paused: 0, stopped: 0 };
1444
+ for (const row of rows) {
1445
+ switch (row.state) {
1446
+ case "complete": counts.done++; break;
1447
+ case "running": counts.active++; break;
1448
+ case "queued": counts.queued++; break;
1449
+ case "blocked": counts.blocked++; break;
1450
+ case "failed": counts.failed++; break;
1451
+ case "paused": counts.paused++; break;
1452
+ case "stopped": counts.stopped++; break;
1453
+ }
1454
+ }
1455
+ return counts;
1456
+ }
1457
+
1458
+ function compactWorkflowLaneLine(row: CompactWorkflowLaneRow, theme: Theme, indent: string, frame?: number): string {
1459
+ const owner = row.agent && row.mode ? `${row.agent}/${row.mode}` : row.agent ?? row.mode;
1460
+ const state = row.state === "complete" ? undefined : row.state === "running" ? "active" : row.state;
1461
+ const intent = [
1462
+ row.decision,
1463
+ row.claims,
1464
+ row.expectedOutput,
1465
+ !row.mode && row.label ? row.label : undefined,
1466
+ ].filter((value): value is string => Boolean(value));
1467
+ return `${indent}${workflowChecklistGlyph({ state: row.state, durationMs: row.durationMs, toolCount: row.toolUses }, theme, frame)} ${theme.bold(row.key)}${owner ? ` ${theme.fg("dim", `· ${owner}`)}` : ""}${state ? ` ${theme.fg("dim", `· ${state}`)}` : ""}${intent.length ? ` ${theme.fg("dim", `· ${intent.join(" · ")}`)}` : ""}`;
1468
+ }
1469
+
1470
+ function compactWorkflowShortId(job: AsyncJobState): string {
1471
+ return job.asyncId.slice(0, 8);
1472
+ }
1473
+
1474
+ function compactWorkflowDisplayLabel(job: AsyncJobState): string {
1475
+ return boundedLaneValue(job.description, 64) ?? compactWorkflowShortId(job);
1476
+ }
1477
+
1478
+ function compactWorkflowStats(job: AsyncJobState, rows: readonly CompactWorkflowLaneRow[], theme: Theme, projection: WorkflowWidgetProjection): string {
1479
+ const counts = compactWorkflowLaneCounts(rows);
1480
+ const total = counts.total || projection.stageProgress?.total || job.stepsTotal || job.agents?.length || 0;
1481
+ const progress = [
1482
+ total > 0 ? `${counts.done}/${total} done` : undefined,
1483
+ counts.active ? `${counts.active} active` : undefined,
1484
+ counts.queued ? `${counts.queued} queued` : undefined,
1485
+ counts.blocked ? `${counts.blocked} blocked` : undefined,
1486
+ counts.failed ? `${counts.failed} failed` : undefined,
1487
+ counts.paused ? `${counts.paused} paused` : undefined,
1488
+ counts.stopped ? `${counts.stopped} stopped` : undefined,
1489
+ ].filter((value): value is string => Boolean(value));
1490
+ const rowToolUses = rows.map((row) => row.toolUses).filter((value): value is number => value !== undefined);
1491
+ const toolUses = job.toolCount ?? (rowToolUses.length ? rowToolUses.reduce((sum, value) => sum + value, 0) : undefined);
1492
+ const rowDuration = rows.map((row) => row.durationMs).filter((value): value is number => value !== undefined);
1493
+ const durationMs = job.startedAt !== undefined && job.updatedAt !== undefined
1494
+ ? Math.max(0, job.updatedAt - job.startedAt)
1495
+ : rowDuration.length ? Math.max(...rowDuration) : undefined;
1496
+ return statJoin(theme, [
1497
+ `id: ${compactWorkflowShortId(job)}`,
1498
+ ...progress,
1499
+ toolUses !== undefined ? formatToolUseStat(toolUses) : undefined,
1500
+ durationMs !== undefined ? formatDuration(durationMs) : undefined,
1501
+ ].filter((value): value is string => Boolean(value)));
1502
+ }
1503
+
1504
+ interface CompactWorkflowBottleneck {
1505
+ text: string;
1506
+ tone: "warning" | "error";
1507
+ }
1508
+
1509
+ function compactWorkflowBottleneck(rows: readonly CompactWorkflowLaneRow[], job: AsyncJobState): CompactWorkflowBottleneck | undefined {
1510
+ let row: CompactWorkflowLaneRow | undefined;
1511
+ for (const candidate of rows) {
1512
+ if (candidate.state !== "blocked" && candidate.state !== "failed" && candidate.state !== "paused" && candidate.state !== "stopped") continue;
1513
+ if (!row || workflowChecklistItemPriority(candidate.state) < workflowChecklistItemPriority(row.state)) row = candidate;
1514
+ }
1515
+ if (row) return { text: `${row.key} · ${row.state}`, tone: row.state === "blocked" || row.state === "failed" ? "error" : "warning" };
1516
+ if (job.activityState === "needs_attention" || job.timedOut || job.toolBudgetBlocked || job.turnBudgetExceeded) return { text: `workflow · ${job.activityState === "needs_attention" ? "needs attention" : "blocked"}`, tone: job.activityState === "needs_attention" ? "warning" : "error" };
1517
+ if (job.status === "failed" || job.status === "partial" || job.status === "rejected") return { text: `workflow · ${job.status}`, tone: "error" };
1518
+ if (job.status === "paused" || job.status === "stopped") return { text: `workflow · ${job.status}`, tone: "warning" };
1519
+ return undefined;
1520
+ }
1521
+
1522
+ function compactWorkflowHeaderLine(job: AsyncJobState, theme: Theme, width?: number): string {
1523
+ const label = compactWorkflowDisplayLabel(job);
1524
+ const full = `${theme.fg("toolTitle", themeBold(theme, `async workflow: ${label}`))} ${theme.fg("dim", "─ background")}`;
1525
+ if (width === undefined || visibleWidth(full) <= width) return full;
1526
+ return `${theme.fg("toolTitle", themeBold(theme, `async workflow ${label}`))} ${theme.fg("dim", "· background")}`;
1527
+ }
1528
+
1529
+ function compactWorkflowWidgetBodyLines(job: AsyncJobState, theme: Theme, frame: number | undefined, projection: WorkflowWidgetProjection): string[] {
1530
+ const rows = compactWorkflowLaneRows(job, projection.checklist);
1531
+ const lines = [` ${compactWorkflowStats(job, rows, theme, projection)}`];
1532
+ if (rows.length) {
1533
+ for (const row of rows) lines.push(compactWorkflowLaneLine(row, theme, " ", frame));
1534
+ } else {
1535
+ lines.push(` ${theme.fg("dim", "◦ waiting for workflow lanes")}`);
1536
+ }
1537
+ const bottleneck = compactWorkflowBottleneck(rows, job);
1538
+ if (bottleneck) lines.push(` ${theme.fg(bottleneck.tone, `bottleneck · ${bottleneck.text}`)}`);
1539
+ if (job.status === "running" || rows.some((row) => row.state === "running")) lines.push(` ${theme.fg("accent", workflowDetailHintText())}`);
1540
+ return lines;
1541
+ }
1542
+
1338
1543
  function widgetStepActivity(step: NonNullable<AsyncJobState["steps"]>[number], snapshotNow?: number): string {
1339
1544
  const facts: string[] = [];
1340
1545
  if (step.currentTool && step.currentToolStartedAt !== undefined && snapshotNow !== undefined) facts.push(`${step.currentTool} ${formatDuration(Math.max(0, snapshotNow - step.currentToolStartedAt))}`);
@@ -2149,19 +2354,13 @@ function hostStepWidgetLines(job: AsyncJobState, theme: Theme, indent: string):
2149
2354
 
2150
2355
  function foregroundStyleWidgetDetails(job: AsyncJobState, theme: Theme, expanded: boolean, width: number, frame?: number, projection = buildWorkflowWidgetProjection(job)): string[] {
2151
2356
  const { steps } = projection;
2152
- const checklistPrimary = !expanded && job.mode === "workflow" && projection.checklist !== undefined;
2153
- if (checklistPrimary) {
2154
- const lines = [
2155
- ...workflowPreflightLines(job, false),
2156
- ...workflowChecklistWidgetLines(projection.checklist, theme, " ", false, frame, false),
2157
- ];
2158
- if (job.status === "running" || projection.checklist!.running > 0) lines.push(` ${theme.fg("accent", liveDetailHintText())}`);
2159
- return lines;
2357
+ if (!expanded && job.mode === "workflow") {
2358
+ return compactWorkflowWidgetBodyLines(job, theme, frame, projection);
2160
2359
  }
2161
2360
  if (!steps.length) {
2162
2361
  const lane = projectAsyncLane(job, laneStepForJob(job, steps));
2163
2362
  return [
2164
- ...workflowPreflightLines(job, expanded),
2363
+ ...(expanded ? workflowPreflightLines(job) : []),
2165
2364
  ...workflowChecklistWidgetLines(projection.checklist, theme, " ", expanded, frame),
2166
2365
  ...(lane ? formatLaneProjectionLines(lane, theme, " ") : []),
2167
2366
  ...hostStepWidgetLines(job, theme, " "),
@@ -2171,7 +2370,7 @@ function foregroundStyleWidgetDetails(job: AsyncJobState, theme: Theme, expanded
2171
2370
  }
2172
2371
  if (job.mode === "chain" && !job.activeParallelGroup && job.parallelGroups?.length) return widgetChainDetails(job, theme, expanded, width, frame);
2173
2372
  const lines: string[] = [
2174
- ...workflowPreflightLines(job, expanded),
2373
+ ...(expanded ? workflowPreflightLines(job) : []),
2175
2374
  ...workflowChecklistWidgetLines(projection.checklist, theme, " ", expanded, frame),
2176
2375
  ];
2177
2376
  const group = activeParallelWidgetGroup(job);
@@ -2205,6 +2404,7 @@ function foregroundStyleWidgetDetails(job: AsyncJobState, theme: Theme, expanded
2205
2404
  }
2206
2405
 
2207
2406
  function buildSingleWidgetLines(job: AsyncJobState, theme: Theme, width: number, expanded: boolean, frame?: number, projection = buildWorkflowWidgetProjection(job)): string[] {
2407
+ if (!expanded && job.mode === "workflow") return [compactWorkflowHeaderLine(job, theme, width), ...compactWorkflowWidgetBodyLines(job, theme, frame, projection)].map((line) => truncLine(line, width));
2208
2408
  const stats = widgetStats(job, theme, projection, !expanded);
2209
2409
  const count = job.mode === "workflow"
2210
2410
  ? projection.checklist?.total ?? projection.stageProgress?.total ?? job.stepsTotal ?? job.agents?.length ?? job.steps?.length
@@ -2381,11 +2581,17 @@ function progressiveHeaderLine(jobs: AsyncJobState[], theme: Theme, width: numbe
2381
2581
  }
2382
2582
 
2383
2583
  function progressiveJobLine(job: AsyncJobState, theme: Theme, width: number, frame?: number, projection = buildWorkflowWidgetProjection(job)): string {
2384
- const checklistPrimary = job.mode === "workflow" && projection.checklist !== undefined;
2385
- const stats = widgetStats(job, theme, projection, true);
2386
- const activity = checklistPrimary ? "" : widgetActivity(job);
2584
+ const compactWorkflow = job.mode === "workflow";
2585
+ const compactRows = compactWorkflow ? compactWorkflowLaneRows(job, projection.checklist) : undefined;
2586
+ const stats = compactWorkflow ? compactWorkflowStats(job, compactRows ?? [], theme, projection) : widgetStats(job, theme, projection, true);
2587
+ if (compactWorkflow) {
2588
+ const bottleneck = compactWorkflowBottleneck(compactRows ?? [], job);
2589
+ const suffix = [stats, bottleneck ? theme.fg(bottleneck.tone, `bottleneck · ${bottleneck.text}`) : undefined].filter((value): value is string => Boolean(value)).join(` ${theme.fg("dim", "·")} `);
2590
+ return truncLine(` ${widgetStatusGlyph(job, theme, frame)} ${compactWorkflowHeaderLine(job, theme, Math.max(0, width - 4))}${suffix ? ` ${theme.fg("dim", "·")} ${suffix}` : ""}`, width);
2591
+ }
2592
+ const activity = widgetActivity(job);
2387
2593
  const status = job.status === "complete" ? "done" : job.status;
2388
- const lane = checklistPrimary ? undefined : projectAsyncLane(job, laneStepForJob(job, projection.steps));
2594
+ const lane = projectAsyncLane(job, laneStepForJob(job, projection.steps));
2389
2595
  const laneSummary = lane
2390
2596
  ? [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(" · ")
2391
2597
  : "";
@@ -2552,12 +2758,11 @@ function buildWidgetLinesWithProjection(jobs: AsyncJobState[], theme: Theme, wid
2552
2758
  let slots = MAX_WIDGET_JOBS;
2553
2759
  const appendJob = (job: AsyncJobState): void => {
2554
2760
  const projection = projectionFor(job);
2555
- const checklistPrimary = !expanded && job.mode === "workflow" && projection.checklist !== undefined;
2556
- const stats = widgetStats(job, theme, projection, !expanded);
2557
- const details = checklistPrimary
2761
+ const compactWorkflow = !expanded && job.mode === "workflow";
2762
+ const stats = compactWorkflow ? "" : widgetStats(job, theme, projection, !expanded);
2763
+ const details = compactWorkflow
2558
2764
  ? [
2559
- ...workflowChecklistWidgetLines(projection.checklist, theme, " ", false, frame, false),
2560
- ...(job.status === "running" || projection.checklist!.running > 0 ? [` ${theme.fg("accent", liveDetailHintText())}`] : []),
2765
+ ...compactWorkflowWidgetBodyLines(job, theme, frame, projection),
2561
2766
  ]
2562
2767
  : [
2563
2768
  ` ${theme.fg("dim", `⎿ ${widgetActivity(job)}`)}`,
@@ -2566,7 +2771,9 @@ function buildWidgetLinesWithProjection(jobs: AsyncJobState[], theme: Theme, wid
2566
2771
  ...widgetParallelAgentDetails(job, theme, expanded, width, frame),
2567
2772
  ];
2568
2773
  items.push([
2569
- `${widgetStatusGlyph(job, theme, frame)} ${themeBold(theme, widgetJobName(job))}${contextModeBadge(theme, job.context)}${stats ? ` ${theme.fg("dim", "·")} ${stats}` : ""}`,
2774
+ compactWorkflow
2775
+ ? compactWorkflowHeaderLine(job, theme, width)
2776
+ : `${widgetStatusGlyph(job, theme, frame)} ${themeBold(theme, widgetJobName(job))}${contextModeBadge(theme, job.context)}${stats ? ` ${theme.fg("dim", "·")} ${stats}` : ""}`,
2570
2777
  ...details,
2571
2778
  ]);
2572
2779
  };