pi-subagents 0.57.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 (115) hide show
  1. package/CHANGELOG.md +104 -0
  2. package/docs/agents.md +18 -9
  3. package/docs/configuration.md +4 -4
  4. package/docs/extension-api.md +40 -1
  5. package/docs/models.md +27 -4
  6. package/docs/observability.md +1 -1
  7. package/docs/tool-reference.md +92 -6
  8. package/docs/workflows.md +115 -0
  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 +3 -1
  13. package/skills/pi-subagents/references/execution-controls.md +6 -2
  14. package/skills/pi-subagents/references/management-authoring-rpc.md +2 -2
  15. package/skills/pi-subagents/references/prompting-and-roles.md +26 -5
  16. package/src/agents/agent-management.ts +28 -17
  17. package/src/agents/agent-serializer.ts +9 -2
  18. package/src/agents/agents.ts +103 -27
  19. package/src/agents/runtime-agent-events.ts +70 -0
  20. package/src/agents/runtime-agent-registry.ts +19 -18
  21. package/src/api/agents.ts +10 -5
  22. package/src/api/background-work.ts +5 -1
  23. package/src/api/delegation.ts +0 -7
  24. package/src/api/preflight.ts +32 -10
  25. package/src/extension/fanout-child.ts +5 -3
  26. package/src/extension/index.ts +66 -27
  27. package/src/extension/public-execution.ts +15 -2
  28. package/src/extension/rpc.ts +35 -14
  29. package/src/extension/schemas.ts +37 -12
  30. package/src/extension/tool-description.ts +25 -6
  31. package/src/integrations/herdr-status.ts +5 -0
  32. package/src/intercom/result-intercom.ts +2 -0
  33. package/src/profiles/profiles.ts +5 -6
  34. package/src/runs/background/active-async-capacity.ts +2 -2
  35. package/src/runs/background/async-execution.ts +78 -46
  36. package/src/runs/background/async-job-tracker.ts +14 -12
  37. package/src/runs/background/async-resume.ts +29 -27
  38. package/src/runs/background/async-status-snapshot.ts +23 -261
  39. package/src/runs/background/async-status.ts +68 -7
  40. package/src/runs/background/chain-append.ts +8 -3
  41. package/src/runs/background/chain-root-attachment.ts +60 -8
  42. package/src/runs/background/fleet-view.ts +21 -11
  43. package/src/runs/background/notify.ts +184 -9
  44. package/src/runs/background/result-delivery-ownership.ts +45 -0
  45. package/src/runs/background/result-files.ts +2 -1
  46. package/src/runs/background/result-watcher.ts +29 -10
  47. package/src/runs/background/resume-guidance.ts +1 -1
  48. package/src/runs/background/retained-children.ts +1 -1
  49. package/src/runs/background/run-status.ts +18 -8
  50. package/src/runs/background/scheduled-runs.ts +86 -7
  51. package/src/runs/background/stale-run-reconciler.ts +10 -4
  52. package/src/runs/background/steering.ts +4 -14
  53. package/src/runs/background/subagent-runner.ts +389 -350
  54. package/src/runs/background/subagent-wait.ts +58 -10
  55. package/src/runs/background/terminal-run-index.ts +1 -1
  56. package/src/runs/background/wait-completions.ts +22 -1
  57. package/src/runs/background/wait-config.ts +23 -9
  58. package/src/runs/background/wait-tool.ts +9 -2
  59. package/src/runs/foreground/async-steering-action.ts +2 -2
  60. package/src/runs/foreground/execution.ts +134 -121
  61. package/src/runs/foreground/foreground-control.ts +3 -0
  62. package/src/runs/foreground/foreground-history.ts +1 -0
  63. package/src/runs/foreground/subagent-executor.ts +527 -257
  64. package/src/runs/foreground/workflow-detach-reconcile.ts +130 -120
  65. package/src/runs/shared/abort-recovery.ts +119 -0
  66. package/src/runs/shared/async-status-projection.ts +463 -0
  67. package/src/runs/shared/child-identity.ts +19 -4
  68. package/src/runs/shared/child-launch-plan.ts +151 -0
  69. package/src/runs/shared/completion-evidence.ts +89 -0
  70. package/src/runs/shared/completion-guard.ts +5 -4
  71. package/src/runs/shared/dynamic-fanout.ts +3 -3
  72. package/src/runs/shared/fast-mode-extension.ts +5 -5
  73. package/src/runs/shared/host-step-status.ts +230 -0
  74. package/src/runs/shared/lane-metadata.ts +105 -0
  75. package/src/runs/shared/launch-cwd.ts +16 -0
  76. package/src/runs/shared/long-running-guard.ts +2 -1
  77. package/src/runs/shared/mcp-config-sources.ts +422 -0
  78. package/src/runs/shared/mcp-direct-tool-allowlist.ts +221 -158
  79. package/src/runs/shared/mcp-direct-tool-grant.ts +197 -0
  80. package/src/runs/shared/model-exclusions.ts +12 -0
  81. package/src/runs/shared/model-fallback.ts +25 -6
  82. package/src/runs/shared/nested-events.ts +6 -2
  83. package/src/runs/shared/nested-render.ts +7 -3
  84. package/src/runs/shared/parallel-handoff.ts +419 -7
  85. package/src/runs/shared/parallel-utils.ts +15 -1
  86. package/src/runs/shared/pi-args.ts +81 -8
  87. package/src/runs/shared/single-output.ts +44 -4
  88. package/src/runs/shared/subagent-prompt-runtime.ts +98 -13
  89. package/src/runs/shared/worktree-cleanup-plan.ts +847 -0
  90. package/src/runs/shared/worktree.ts +18 -0
  91. package/src/shared/child-session-name.ts +46 -0
  92. package/src/shared/extension-context.ts +24 -0
  93. package/src/shared/formatters.ts +11 -2
  94. package/src/shared/launch-contract.ts +5 -1
  95. package/src/shared/settings.ts +9 -103
  96. package/src/shared/types.ts +240 -30
  97. package/src/shared/utils.ts +41 -84
  98. package/src/slash/delegation-adapters.ts +1 -8
  99. package/src/slash/delegation-request.ts +0 -4
  100. package/src/slash/slash-bridge.ts +1 -2
  101. package/src/slash/slash-commands.ts +369 -90
  102. package/src/slash/slash-live-state.ts +22 -11
  103. package/src/slash/subagents-admin.ts +3 -0
  104. package/src/tui/fleet-status.ts +125 -74
  105. package/src/tui/fleet.ts +11 -5
  106. package/src/tui/render.ts +330 -33
  107. package/src/watchdog/turn-delta.ts +1 -1
  108. package/src/workflows/chat-progress.ts +6 -3
  109. package/src/workflows/host-command.ts +230 -0
  110. package/src/workflows/scripted-workflow.ts +496 -35
  111. package/src/workflows/workflow-child-summary.ts +9 -5
  112. package/src/workflows/workflow-preflight.ts +270 -0
  113. package/src/workflows/workflow-receipt.ts +79 -5
  114. package/src/workflows/workflow-settlement.ts +246 -0
  115. package/src/runs/shared/turn-budget.ts +0 -98
@@ -2,7 +2,7 @@ import * as fs from "node:fs";
2
2
  import * as path from "node:path";
3
3
  import { formatDuration, formatModelThinking, formatTokens, shortenPath } from "../../shared/formatters.ts";
4
4
  import { formatActivityLabel, formatParallelOutcome } from "../../shared/status-format.ts";
5
- import { type ActivityState, type AsyncJobStep, type AsyncParallelGroupStatus, type AsyncStatus, type CostSummary, type Details, type LaunchResolvedChildExtensionsV1, type RuntimeAcknowledgedChildExtensionsV1, type NestedRunSummary, type SteeringStatus, type SubagentRunMode, type TokenUsage, type TurnBudgetState, type UsageBudgetState } from "../../shared/types.ts";
5
+ import { type ActivityState, type AsyncJobStep, type AsyncParallelGroupStatus, type AsyncStatus, type CostSummary, type Details, type HostStepNodeV1, type HostStepState, type LaunchResolvedChildExtensionsV1, type RuntimeAcknowledgedChildExtensionsV1, type NestedRunSummary, type SteeringStatus, type SubagentRunMode, type TokenUsage, type TurnBudgetState, type UsageBudgetState, type WorkflowPreflightV1 } from "../../shared/types.ts";
6
6
  import type { ResolvedSubagentCapabilityCeiling, SubagentCapabilityAudit } from "../shared/capability-ceiling.ts";
7
7
  import { readStatus } from "../../shared/utils.ts";
8
8
  import { attachRootChildrenToSteps, buildNestedRouteIndex, findNestedRouteForRootId, type NestedRoute, projectNestedEvents } from "../shared/nested-events.ts";
@@ -17,16 +17,25 @@ import { readRecentTerminalRunIndex, TERMINAL_RUN_INDEX_DIR } from "./terminal-r
17
17
  import { canScanAsyncRunPrefix } from "./run-id-query.ts";
18
18
  import { asyncStatusChildIdentity } from "../shared/child-identity.ts";
19
19
  import { parseWorkflowChildSummary } from "../../workflows/workflow-child-summary.ts";
20
+ import { assertWorkflowGraphHostSteps, hostStepReportName, hostStepVerdictLabel, validHostStepNodes } from "../shared/host-step-status.ts";
21
+ import { projectAsyncWorkflowRows } from "../shared/async-status-projection.ts";
22
+ import { validateAsyncStatusLaneMetadata } from "../shared/lane-metadata.ts";
23
+ import { formatWorkflowPreflightPlanSummary, formatWorkflowPreflightWarningSummary } from "../../workflows/workflow-preflight.ts";
20
24
 
21
25
  interface AsyncRunStepSummary {
22
26
  index: number;
23
27
  childId?: string;
24
28
  agent: string;
29
+ /** Human-readable display name for the child session, when derived at launch. */
30
+ sessionName?: string;
25
31
  context?: ContextMode;
26
32
  label?: string;
27
33
  description?: string;
28
34
  phase?: string;
29
35
  workflowKey?: string;
36
+ lane?: AsyncJobStep["lane"];
37
+ worktreePath?: string;
38
+ branch?: string;
30
39
  runId?: string;
31
40
  outputName?: string;
32
41
  structured?: boolean;
@@ -48,6 +57,7 @@ interface AsyncRunStepSummary {
48
57
  totalCost?: CostSummary;
49
58
  skills?: string[];
50
59
  model?: string;
60
+ contextLimit?: number;
51
61
  thinking?: string;
52
62
  attemptedModels?: string[];
53
63
  sessionFile?: string;
@@ -57,6 +67,7 @@ interface AsyncRunStepSummary {
57
67
  stopped?: boolean;
58
68
  turnBudget?: TurnBudgetState;
59
69
  turnBudgetExceeded?: boolean;
70
+ toolBudgetBlocked?: boolean;
60
71
  wrapUpRequested?: boolean;
61
72
  acceptance?: AsyncJobStep["acceptance"];
62
73
  agentContract?: AsyncJobStep["agentContract"];
@@ -76,7 +87,7 @@ export interface AsyncRunSummary {
76
87
  asyncDir: string;
77
88
  toolCallId?: string;
78
89
  sessionId?: string;
79
- state: "queued" | "running" | "complete" | "failed" | "paused" | "stopped" | "rejected";
90
+ state: "queued" | "running" | "complete" | "failed" | "partial" | "paused" | "stopped" | "rejected";
80
91
  error?: string;
81
92
  activityState?: ActivityState;
82
93
  lastActivityAt?: number;
@@ -104,6 +115,7 @@ export interface AsyncRunSummary {
104
115
  chainStepCount?: number;
105
116
  pendingAppends?: number;
106
117
  parallelGroups?: AsyncParallelGroupStatus[];
118
+ hostSteps?: HostStepNodeV1[];
107
119
  steps: AsyncRunStepSummary[];
108
120
  sessionDir?: string;
109
121
  outputFile?: string;
@@ -121,8 +133,10 @@ export interface AsyncRunSummary {
121
133
  capabilityAudit?: SubagentCapabilityAudit;
122
134
  parentWorkflowRunId?: string;
123
135
  workflowKey?: string;
136
+ lane?: AsyncStatus["lane"];
124
137
  workflow?: Details["workflow"];
125
138
  workflowChildren?: Details["workflowChildren"];
139
+ preflight?: WorkflowPreflightV1;
126
140
  }
127
141
 
128
142
  interface AsyncRunListOptions {
@@ -231,21 +245,32 @@ function deriveAsyncActivityState(asyncDir: string, status: AsyncStatus): { acti
231
245
  }
232
246
 
233
247
  function statusToSummary(asyncDir: string, status: AsyncStatus & { cwd?: string }, nestedWarnings: string[] = [], nestedRoute?: NestedRoute): AsyncRunSummary {
248
+ validateAsyncStatusLaneMetadata(status, `Invalid async status '${path.join(asyncDir, "status.json")}'`);
234
249
  const workflowChildren = parseWorkflowChildSummary(status.workflowChildren);
235
250
  if (workflowChildren && workflowChildren.workflowRunId !== status.runId) throw new Error(`Invalid async status '${path.join(asyncDir, "status.json")}': workflowChildren.workflowRunId does not match.`);
251
+ assertWorkflowGraphHostSteps(status.workflowGraph, path.join(asyncDir, "status.json"), status.runId);
252
+ const hostSteps = validHostStepNodes(status.workflowGraph);
236
253
  if (status.sessionId !== undefined && typeof status.sessionId !== "string") {
237
254
  throw new Error(`Invalid async status '${path.join(asyncDir, "status.json")}': sessionId must be a string.`);
238
255
  }
239
256
  const { activityState, lastActivityAt } = deriveAsyncActivityState(asyncDir, status);
240
257
  const processTerminal = readProcessTerminal(asyncDir, { runId: status.runId, runnerProcessInstanceId: status.processTerminal?.runnerProcessInstanceId })
241
258
  ?? sanitizeProcessTerminal(status.processTerminal, { runId: status.runId, runnerProcessInstanceId: status.processTerminal?.runnerProcessInstanceId }, path.join(asyncDir, "status.json"));
242
- const runFanoutBudgetDescriptor = readRunFanoutBudgetDescriptor(asyncDir);
243
- const runFanoutBudget = runFanoutBudgetDescriptor ? getRunFanoutBudgetSnapshot(runFanoutBudgetDescriptor) : status.runFanoutBudget;
259
+ const nestedProjectionAllowed = nestedWarnings.length === 0;
260
+ // Degrade to the status-stored snapshot when the persisted budget is unavailable (e.g. removed
261
+ // by OS temp cleanup) instead of failing the whole run list; admission paths stay strict.
262
+ let runFanoutBudget: AsyncStatus["runFanoutBudget"] = status.runFanoutBudget;
263
+ try {
264
+ const runFanoutBudgetDescriptor = readRunFanoutBudgetDescriptor(asyncDir);
265
+ if (runFanoutBudgetDescriptor) runFanoutBudget = getRunFanoutBudgetSnapshot(runFanoutBudgetDescriptor);
266
+ } catch (error) {
267
+ nestedWarnings.push(`Run fan-out status unavailable: ${getErrorMessage(error)}`);
268
+ }
244
269
  const steps = status.steps ?? [];
245
270
  const chainStepCount = status.chainStepCount ?? steps.length;
246
271
  const parallelGroups = normalizeParallelGroups(status.parallelGroups, steps.length, chainStepCount);
247
272
  let nestedChildren: NestedRunSummary[] = [];
248
- if (nestedWarnings.length === 0 && nestedRoute) {
273
+ if (nestedProjectionAllowed && nestedRoute) {
249
274
  try {
250
275
  // The route is resolved by the caller via buildNestedRouteIndex, so this
251
276
  // avoids a fresh scan of the nested-events directory per run.
@@ -261,11 +286,15 @@ function statusToSummary(asyncDir: string, status: AsyncStatus & { cwd?: string
261
286
  index,
262
287
  childId: asyncStatusChildIdentity(step, index),
263
288
  agent: step.agent,
289
+ ...(step.sessionName ? { sessionName: step.sessionName } : {}),
264
290
  ...(step.context ? { context: step.context } : {}),
265
291
  ...(step.label ? { label: step.label } : {}),
266
292
  ...(step.description ? { description: step.description } : {}),
267
293
  ...(step.phase ? { phase: step.phase } : {}),
268
294
  ...(step.workflowKey ? { workflowKey: step.workflowKey } : {}),
295
+ ...(step.lane ? { lane: step.lane } : {}),
296
+ ...(step.worktreePath ? { worktreePath: step.worktreePath } : {}),
297
+ ...(step.branch ? { branch: step.branch } : {}),
269
298
  ...(step.runId ? { runId: step.runId } : {}),
270
299
  ...(step.outputName ? { outputName: step.outputName } : {}),
271
300
  ...(step.structured ? { structured: step.structured } : {}),
@@ -287,6 +316,7 @@ function statusToSummary(asyncDir: string, status: AsyncStatus & { cwd?: string
287
316
  ...(step.totalCost ? { totalCost: step.totalCost } : {}),
288
317
  ...(step.skills ? { skills: step.skills } : {}),
289
318
  ...(step.model ? { model: step.model } : {}),
319
+ ...(step.contextLimit !== undefined ? { contextLimit: step.contextLimit } : {}),
290
320
  ...(step.thinking ? { thinking: step.thinking } : {}),
291
321
  ...(step.thinkingCeiling ? { thinkingCeiling: step.thinkingCeiling } : {}),
292
322
  ...(step.attemptedModels ? { attemptedModels: step.attemptedModels } : {}),
@@ -299,6 +329,7 @@ function statusToSummary(asyncDir: string, status: AsyncStatus & { cwd?: string
299
329
  ...(step.stopRequestedAt !== undefined ? { stopRequestedAt: step.stopRequestedAt } : {}),
300
330
  ...(step.turnBudget ? { turnBudget: step.turnBudget } : {}),
301
331
  ...(step.turnBudgetExceeded !== undefined ? { turnBudgetExceeded: step.turnBudgetExceeded } : {}),
332
+ ...(step.toolBudgetBlocked !== undefined ? { toolBudgetBlocked: step.toolBudgetBlocked } : {}),
302
333
  ...(step.wrapUpRequested !== undefined ? { wrapUpRequested: step.wrapUpRequested } : {}),
303
334
  ...(step.acceptance ? { acceptance: step.acceptance } : {}),
304
335
  ...(step.agentContract ? { agentContract: step.agentContract } : {}),
@@ -308,6 +339,7 @@ function statusToSummary(asyncDir: string, status: AsyncStatus & { cwd?: string
308
339
  ...(step.execution ? { execution: step.execution } : {}),
309
340
  ...(step.review ? { review: step.review } : {}),
310
341
  ...(step.effects ? { effects: step.effects } : {}),
342
+ ...(step.watchdog ? { watchdog: step.watchdog } : {}),
311
343
  ...(step.processTerminal ? { processTerminal: sanitizeProcessTerminal(step.processTerminal, { runId: status.runId, runnerProcessInstanceId: step.processTerminal.runnerProcessInstanceId }, `${path.join(asyncDir, "status.json")} step ${index}`) } : {}),
312
344
  ...(step.capabilityCeiling ? { capabilityCeiling: step.capabilityCeiling } : {}),
313
345
  ...(step.capabilityAudit ? { capabilityAudit: step.capabilityAudit } : {}),
@@ -348,6 +380,7 @@ function statusToSummary(asyncDir: string, status: AsyncStatus & { cwd?: string
348
380
  ...(status.chainStepCount !== undefined ? { chainStepCount: status.chainStepCount } : {}),
349
381
  ...(status.pendingAppends !== undefined ? { pendingAppends: status.pendingAppends } : {}),
350
382
  ...(parallelGroups.length ? { parallelGroups } : {}),
383
+ ...(hostSteps.length ? { hostSteps } : {}),
351
384
  steps: summarizedSteps,
352
385
  ...(nestedChildren.length ? { nestedChildren } : {}),
353
386
  ...(nestedWarnings.length ? { nestedWarnings } : {}),
@@ -360,8 +393,10 @@ function statusToSummary(asyncDir: string, status: AsyncStatus & { cwd?: string
360
393
  ...(status.capabilityAudit ? { capabilityAudit: status.capabilityAudit } : {}),
361
394
  ...(status.parentWorkflowRunId ? { parentWorkflowRunId: status.parentWorkflowRunId } : {}),
362
395
  ...(status.workflowKey ? { workflowKey: status.workflowKey } : {}),
396
+ ...(status.lane ? { lane: status.lane } : {}),
363
397
  ...(status.workflow ? { workflow: status.workflow } : {}),
364
398
  ...(workflowChildren ? { workflowChildren } : {}),
399
+ ...(status.preflight ? { preflight: status.preflight } : {}),
365
400
  ...(status.sessionDir ? { sessionDir: status.sessionDir } : {}),
366
401
  ...(status.outputFile ? { outputFile: status.outputFile } : {}),
367
402
  ...(status.totalTokens ? { totalTokens: status.totalTokens } : {}),
@@ -381,6 +416,7 @@ function sortRuns(runs: AsyncRunSummary[]): AsyncRunSummary[] {
381
416
  case "running": return 0;
382
417
  case "queued": return 1;
383
418
  case "failed": return 2;
419
+ case "partial": return 2;
384
420
  case "stopped": return 2;
385
421
  case "paused": return 2;
386
422
  case "complete": return 3;
@@ -509,7 +545,7 @@ function formatActivityFacts(input: { activityState?: ActivityState; lastActivit
509
545
  }
510
546
 
511
547
  function formatStepLine(step: AsyncRunStepSummary): string {
512
- const display = step.label ? `${step.label} (${step.agent})` : step.agent;
548
+ const display = step.sessionName?.trim() || (step.label ? `${step.label} (${step.agent})` : step.agent);
513
549
  const context = contextModeLabel(step.context);
514
550
  const phase = step.phase ? `[${step.phase}] ` : "";
515
551
  const parts = [`${step.index + 1}. ${phase}${display}${context ? ` ${context}` : ""}`, step.status];
@@ -519,9 +555,26 @@ function formatStepLine(step: AsyncRunStepSummary): string {
519
555
  if (modelThinking) parts.push(modelThinking);
520
556
  if (step.durationMs !== undefined) parts.push(formatDuration(step.durationMs));
521
557
  if (step.tokens) parts.push(`${formatTokens(step.tokens.total)} tok`);
558
+ if (step.lane) parts.push(`lane ${step.lane.key}`);
559
+ if (step.worktreePath) parts.push(`worktree ${shortenPath(step.worktreePath)} · branch ${step.branch ?? "unknown"}`);
522
560
  return parts.join(" | ");
523
561
  }
524
562
 
563
+ function formatHostStepLine(row: ReturnType<typeof projectAsyncWorkflowRows>[number]): string {
564
+ if (!row.kind) return "";
565
+ const state = hostStepVerdictLabel(row.state as HostStepState, row.verdict);
566
+ const details = [
567
+ row.provider ? `provider:${row.provider}` : undefined,
568
+ row.role ? `role:${row.role}` : undefined,
569
+ row.target,
570
+ row.detail,
571
+ row.reasonCode ? `reason:${row.reasonCode}` : undefined,
572
+ row.freshness?.stale ? "stale" : row.freshness?.observedRef ? `ref:${row.freshness.observedRef}` : undefined,
573
+ row.reportPath ? `out:${hostStepReportName(row.reportPath)}` : undefined,
574
+ ].filter((value): value is string => Boolean(value));
575
+ return `host ${row.kind}: ${row.name} | ${state}${details.length ? ` | ${details.join(" | ")}` : ""}`;
576
+ }
577
+
525
578
  export function formatAsyncRunOutputPath(run: Pick<AsyncRunSummary, "asyncDir" | "outputFile">): string | undefined {
526
579
  if (!run.outputFile) return undefined;
527
580
  return path.isAbsolute(run.outputFile) ? run.outputFile : path.join(run.asyncDir, run.outputFile);
@@ -554,7 +607,8 @@ function formatRunHeader(run: AsyncRunSummary): string {
554
607
  const activity = formatActivityFacts(run);
555
608
  const pending = run.pendingAppends ? ` | ${run.pendingAppends} pending append${run.pendingAppends === 1 ? "" : "s"}` : "";
556
609
  const context = contextModeLabel(run.context);
557
- return `${run.id} | ${run.state}${activity ? ` | ${activity}` : ""} | ${run.mode}${context ? ` ${context}` : ""} | ${stepLabel}${pending} | ${cwd}`;
610
+ const lane = run.lane ? ` | lane ${run.lane.key}` : "";
611
+ return `${run.id} | ${run.state}${activity ? ` | ${activity}` : ""} | ${run.mode}${context ? ` ${context}` : ""} | ${stepLabel}${pending}${lane} | ${cwd}`;
558
612
  }
559
613
 
560
614
  export function formatAsyncRunList(runs: AsyncRunSummary[], heading = "Active async runs"): string {
@@ -563,10 +617,17 @@ export function formatAsyncRunList(runs: AsyncRunSummary[], heading = "Active as
563
617
  const lines = [`${heading}: ${runs.length}`, ""];
564
618
  for (const run of runs) {
565
619
  lines.push(`- ${formatRunHeader(run)}`);
620
+ if (run.preflight) lines.push(formatWorkflowPreflightPlanSummary(run.preflight, { indent: " " }));
621
+ const preflightWarning = formatWorkflowPreflightWarningSummary(run.workflow?.preflightWarnings, { indent: " " });
622
+ if (preflightWarning) lines.push(preflightWarning);
566
623
  for (const step of run.steps) {
567
624
  lines.push(` ${formatStepLine(step)}`);
568
625
  lines.push(...formatNestedRunStatusLines(step.children, { indent: " ", maxLines: 12 }));
569
626
  }
627
+ for (const row of projectAsyncWorkflowRows([], run.hostSteps)) {
628
+ const line = formatHostStepLine(row);
629
+ if (line) lines.push(` ${line}`);
630
+ }
570
631
  const attached = new Set(run.steps.flatMap((step) => step.children?.map((child) => child.id) ?? []));
571
632
  const unattached = run.nestedChildren?.filter((child) => !attached.has(child.id)) ?? [];
572
633
  lines.push(...formatNestedRunStatusLines(unattached, { indent: " ", maxLines: 12 }));
@@ -3,8 +3,9 @@ import * as fs from "node:fs";
3
3
  import * as path from "node:path";
4
4
  import { writeAtomicJson } from "../../shared/atomic-json.ts";
5
5
  import { appendJsonl } from "../../shared/artifacts.ts";
6
- import type { AsyncParallelGroupStatus, AsyncStatus, WorkflowGraphNode, WorkflowGraphSnapshot } from "../../shared/types.ts";
6
+ import type { AsyncStatus, WorkflowGraphNode, WorkflowGraphSnapshot } from "../../shared/types.ts";
7
7
  import { PROMPT_REDACTED, readStatus } from "../../shared/utils.ts";
8
+ import { deriveChildSessionName } from "../../shared/child-session-name.ts";
8
9
  import type { DynamicRunnerGroup, ParallelStepGroup, RunnerStep, RunnerSubagentStep } from "../shared/parallel-utils.ts";
9
10
  import { isDynamicRunnerGroup, isParallelGroup } from "../shared/parallel-utils.ts";
10
11
 
@@ -160,8 +161,10 @@ export function statusStepDescription(task: string | undefined): string | undefi
160
161
 
161
162
  function statusStepForTask(task: RunnerSubagentStep): StatusStep {
162
163
  const description = statusStepDescription(task.task);
164
+ const sessionName = task.sessionName ?? deriveChildSessionName({ agent: task.agent, task: task.task, label: task.label });
163
165
  return {
164
166
  agent: task.agent,
167
+ ...(sessionName ? { sessionName } : {}),
165
168
  ...(description ? { description } : {}),
166
169
  ...(task.context ? { context: task.context } : {}),
167
170
  phase: task.phase,
@@ -172,6 +175,7 @@ function statusStepForTask(task: RunnerSubagentStep): StatusStep {
172
175
  ...(task.sessionFile ? { sessionFile: task.sessionFile } : {}),
173
176
  skills: task.skills,
174
177
  model: task.model,
178
+ ...(task.contextLimit !== undefined ? { contextLimit: task.contextLimit } : {}),
175
179
  thinking: task.thinking,
176
180
  attemptedModels: task.modelCandidates && task.modelCandidates.length > 0 ? task.modelCandidates : task.model ? [task.model] : undefined,
177
181
  recentTools: [],
@@ -179,7 +183,7 @@ function statusStepForTask(task: RunnerSubagentStep): StatusStep {
179
183
  };
180
184
  }
181
185
 
182
- function statusStepsForRunnerStep(step: RunnerStep, stepIndex: number): StatusStep[] {
186
+ function statusStepsForRunnerStep(step: RunnerStep): StatusStep[] {
183
187
  if (isParallelGroup(step)) return step.parallel.map(statusStepForTask);
184
188
  if (isDynamicRunnerGroup(step)) {
185
189
  return [{
@@ -189,6 +193,7 @@ function statusStepsForRunnerStep(step: RunnerStep, stepIndex: number): StatusSt
189
193
  label: step.label ?? step.parallel.label ?? `Dynamic fanout (${step.collect.as})`,
190
194
  outputName: step.collect.as,
191
195
  structured: Boolean(step.collect.outputSchema),
196
+ ...(step.parallel.contextLimit !== undefined ? { contextLimit: step.parallel.contextLimit } : {}),
192
197
  status: "pending",
193
198
  recentTools: [],
194
199
  recentOutput: [],
@@ -295,7 +300,7 @@ export function appendRunnerStepsToStatus(input: {
295
300
  for (const step of input.steps) {
296
301
  const stepIndex = input.status.chainStepCount ?? input.status.steps?.length ?? 0;
297
302
  const flatIndex = input.status.steps?.length ?? 0;
298
- const statusSteps = statusStepsForRunnerStep(step, stepIndex);
303
+ const statusSteps = statusStepsForRunnerStep(step);
299
304
  input.status.steps ??= [];
300
305
  input.status.steps.push(...statusSteps);
301
306
  if (isParallelGroup(step)) {
@@ -1,6 +1,7 @@
1
1
  import * as fs from "node:fs";
2
- import { resultFilePath } from "./result-files.ts";
3
- import type { AcceptanceLedger, ArtifactPaths, AsyncStatus, CostSummary, ModelAttempt } from "../../shared/types.ts";
2
+ import * as path from "node:path";
3
+ import { resultFilePath, resultPayloadPathForSessionRun } from "./result-files.ts";
4
+ import type { AcceptanceLedger, ArtifactPaths, AsyncStatus, CostSummary, EffectsProjection, ExecutionProjection, ModelAttempt, Usage } from "../../shared/types.ts";
4
5
  import { readStatus } from "../../shared/utils.ts";
5
6
 
6
7
  export interface ImportedAsyncRoot {
@@ -12,6 +13,8 @@ export interface ImportedAsyncRoot {
12
13
 
13
14
  export interface ImportedAsyncRootResult {
14
15
  agent: string;
16
+ /** Human-readable display name for the child session, when derived at launch. */
17
+ sessionName?: string;
15
18
  output: string;
16
19
  success: boolean;
17
20
  exitCode: number;
@@ -23,6 +26,7 @@ export interface ImportedAsyncRootResult {
23
26
  modelAttempts?: ModelAttempt[];
24
27
  contextOverflow?: boolean;
25
28
  totalCost?: CostSummary;
29
+ usage?: Usage;
26
30
  structuredOutput?: unknown;
27
31
  structuredOutputPath?: string;
28
32
  structuredOutputSchemaPath?: string;
@@ -33,6 +37,8 @@ export interface ImportedAsyncRootResult {
33
37
  transcriptError?: string;
34
38
  timedOut?: boolean;
35
39
  stopped?: boolean;
40
+ execution?: ExecutionProjection;
41
+ effects?: EffectsProjection;
36
42
  }
37
43
 
38
44
  interface AsyncResultFile {
@@ -44,11 +50,14 @@ interface AsyncResultFile {
44
50
  stopped?: boolean;
45
51
  results?: Array<{
46
52
  agent?: string;
53
+ sessionName?: string;
47
54
  output?: string;
48
55
  error?: string;
49
56
  success?: boolean;
50
57
  timedOut?: boolean;
51
58
  stopped?: boolean;
59
+ execution?: ExecutionProjection;
60
+ effects?: EffectsProjection;
52
61
  sessionFile?: string;
53
62
  intercomTarget?: string;
54
63
  model?: string;
@@ -56,6 +65,7 @@ interface AsyncResultFile {
56
65
  modelAttempts?: ModelAttempt[];
57
66
  contextOverflow?: boolean;
58
67
  totalCost?: CostSummary;
68
+ usage?: Usage;
59
69
  structuredOutput?: unknown;
60
70
  structuredOutputPath?: string;
61
71
  structuredOutputSchemaPath?: string;
@@ -67,8 +77,25 @@ interface AsyncResultFile {
67
77
  }>;
68
78
  }
69
79
 
70
- const TERMINAL_STATES = new Set(["complete", "failed", "paused", "stopped"]);
71
- const TERMINAL_STEP_STATUSES = new Set(["complete", "completed", "failed", "paused", "stopped"]);
80
+ const TERMINAL_STATES = new Set(["complete", "failed", "partial", "paused", "stopped"]);
81
+ const TERMINAL_STEP_STATUSES = new Set(["complete", "completed", "failed", "partial", "paused", "stopped"]);
82
+
83
+ function usageFromAttempts(attempts: ModelAttempt[] | undefined): Usage | undefined {
84
+ if (!attempts?.length) return undefined;
85
+ const usage: Usage = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0, turns: 0 };
86
+ for (const attempt of attempts) {
87
+ if (!attempt.usage) continue;
88
+ usage.input += attempt.usage.input;
89
+ usage.output += attempt.usage.output;
90
+ usage.cacheRead += attempt.usage.cacheRead;
91
+ usage.cacheWrite += attempt.usage.cacheWrite;
92
+ usage.cost += attempt.usage.cost;
93
+ usage.turns += attempt.usage.turns;
94
+ }
95
+ return usage.input !== 0 || usage.output !== 0 || usage.cacheRead !== 0 || usage.cacheWrite !== 0 || usage.cost !== 0 || usage.turns !== 0
96
+ ? usage
97
+ : undefined;
98
+ }
72
99
 
73
100
  function readResultFile(resultPath: string): AsyncResultFile | undefined {
74
101
  try {
@@ -81,6 +108,13 @@ function readResultFile(resultPath: string): AsyncResultFile | undefined {
81
108
  }
82
109
  }
83
110
 
111
+ function readImportedResultFile(root: ImportedAsyncRoot, status: AsyncStatus | null): AsyncResultFile | undefined {
112
+ const direct = readResultFile(root.resultPath);
113
+ if (direct || !status?.sessionId) return direct;
114
+ const indexedPath = resultPayloadPathForSessionRun(path.dirname(root.resultPath), status.sessionId, root.runId);
115
+ return indexedPath ? readResultFile(indexedPath) : undefined;
116
+ }
117
+
84
118
  function selectedStatusStep(status: AsyncStatus | null, index: number): NonNullable<AsyncStatus["steps"]>[number] | undefined {
85
119
  return status?.steps?.[index];
86
120
  }
@@ -92,12 +126,12 @@ function isTerminalStatus(status: AsyncStatus | null, index: number): boolean {
92
126
  return TERMINAL_STATES.has(status.state);
93
127
  }
94
128
 
95
- function resultState(result: AsyncResultFile | undefined, child: NonNullable<AsyncResultFile["results"]>[number] | undefined): "complete" | "failed" | "paused" | "stopped" | undefined {
129
+ function resultState(result: AsyncResultFile | undefined, child: NonNullable<AsyncResultFile["results"]>[number] | undefined): "complete" | "failed" | "partial" | "paused" | "stopped" | undefined {
96
130
  if (!result) return undefined;
97
131
  if (child?.stopped === true) return "stopped";
98
132
  if (child?.success === true) return "complete";
99
- if (child?.success === false) return result.state === "stopped" ? "stopped" : result.state === "paused" ? "paused" : "failed";
100
- if (result.state === "complete" || result.state === "failed" || result.state === "paused" || result.state === "stopped") return result.state;
133
+ if (child?.success === false) return result.state === "stopped" ? "stopped" : result.state === "paused" ? "paused" : result.state === "partial" ? "partial" : "failed";
134
+ if (result.state === "complete" || result.state === "failed" || result.state === "partial" || result.state === "paused" || result.state === "stopped") return result.state;
101
135
  if (result.success === true) return "complete";
102
136
  if (result.success === false) return "failed";
103
137
  return undefined;
@@ -108,6 +142,11 @@ function outputFromTerminalStatus(root: ImportedAsyncRoot, status: AsyncStatus,
108
142
  const timedOut = step?.timedOut === true || status.timedOut === true;
109
143
  const stopped = step?.stopped === true || status.stopped === true || status.state === "stopped";
110
144
  const message = step?.error ?? status.error ?? (stopped ? "Subagent stopped by user." : `Attached async root ${root.runId} ended without a result file at ${root.resultPath}.`);
145
+ const partialStatus = status.state === "partial" || step?.status === "partial";
146
+ const execution = step?.execution ?? (partialStatus
147
+ ? { status: "partial" as const, success: false, exitCode: 1, error: message }
148
+ : undefined);
149
+ const usage = usageFromAttempts(step?.modelAttempts);
111
150
  return {
112
151
  agent,
113
152
  output: message,
@@ -116,22 +155,27 @@ function outputFromTerminalStatus(root: ImportedAsyncRoot, status: AsyncStatus,
116
155
  error: message,
117
156
  ...(timedOut ? { timedOut: true } : {}),
118
157
  ...(stopped ? { stopped: true } : {}),
158
+ ...(step?.sessionName ? { sessionName: step.sessionName } : {}),
119
159
  ...(step?.sessionFile ?? status.sessionFile ? { sessionFile: step?.sessionFile ?? status.sessionFile } : {}),
120
160
  ...(step?.model ? { model: step.model } : {}),
121
161
  ...(step?.attemptedModels ? { attemptedModels: step.attemptedModels } : {}),
122
162
  ...(step?.modelAttempts ? { modelAttempts: step.modelAttempts } : {}),
123
163
  ...(step?.contextOverflow ? { contextOverflow: true } : {}),
124
164
  ...(step?.totalCost ? { totalCost: step.totalCost } : {}),
165
+ ...(usage ? { usage } : {}),
125
166
  ...(step?.structuredOutput !== undefined ? { structuredOutput: step.structuredOutput } : {}),
126
167
  ...(step?.structuredOutputPath ? { structuredOutputPath: step.structuredOutputPath } : {}),
127
168
  ...(step?.structuredOutputSchemaPath ? { structuredOutputSchemaPath: step.structuredOutputSchemaPath } : {}),
128
169
  ...(step?.acceptance ? { acceptance: step.acceptance } : {}),
170
+ ...(execution ? { execution } : {}),
171
+ ...(step?.effects ? { effects: step.effects } : {}),
129
172
  ...(step?.transcriptPath ? { transcriptPath: step.transcriptPath } : {}),
130
173
  };
131
174
  }
132
175
 
133
176
  function outputFromTimeout(root: ImportedAsyncRoot, status: AsyncStatus | null, message: string): ImportedAsyncRootResult {
134
177
  const step = selectedStatusStep(status, root.index);
178
+ const usage = usageFromAttempts(step?.modelAttempts);
135
179
  return {
136
180
  agent: step?.agent ?? status?.steps?.[root.index]?.agent ?? "subagent",
137
181
  output: message,
@@ -139,12 +183,14 @@ function outputFromTimeout(root: ImportedAsyncRoot, status: AsyncStatus | null,
139
183
  exitCode: 1,
140
184
  error: message,
141
185
  timedOut: true,
186
+ ...(step?.sessionName ? { sessionName: step.sessionName } : {}),
142
187
  ...(step?.sessionFile ?? status?.sessionFile ? { sessionFile: step?.sessionFile ?? status?.sessionFile } : {}),
143
188
  ...(step?.model ? { model: step.model } : {}),
144
189
  ...(step?.attemptedModels ? { attemptedModels: step.attemptedModels } : {}),
145
190
  ...(step?.modelAttempts ? { modelAttempts: step.modelAttempts } : {}),
146
191
  ...(step?.contextOverflow ? { contextOverflow: true } : {}),
147
192
  ...(step?.totalCost ? { totalCost: step.totalCost } : {}),
193
+ ...(usage ? { usage } : {}),
148
194
  ...(step?.transcriptPath ? { transcriptPath: step.transcriptPath } : {}),
149
195
  };
150
196
  }
@@ -159,6 +205,8 @@ function buildImportedResult(root: ImportedAsyncRoot, status: AsyncStatus | null
159
205
  const stopped = child?.stopped === true || step?.stopped === true || result.stopped === true || status?.stopped === true || state === "stopped";
160
206
  const success = state === "complete" && !timedOut && !stopped;
161
207
  const error = child?.error ?? (success ? undefined : stopped ? "Subagent stopped by user." : result.error ?? result.summary ?? status?.error ?? `Attached async root ${root.runId} did not complete successfully.`);
208
+ const execution = state ? { status: state === "complete" ? "completed" as const : state, success, exitCode: success ? 0 : 1, ...(error ? { error } : {}) } : undefined;
209
+ const usage = child?.usage ?? usageFromAttempts(step?.modelAttempts);
162
210
  return {
163
211
  agent,
164
212
  output: success ? output : (output || error || ""),
@@ -167,6 +215,7 @@ function buildImportedResult(root: ImportedAsyncRoot, status: AsyncStatus | null
167
215
  ...(error ? { error } : {}),
168
216
  ...(timedOut ? { timedOut: true } : {}),
169
217
  ...(stopped ? { stopped: true } : {}),
218
+ ...(child?.sessionName ?? step?.sessionName ? { sessionName: child?.sessionName ?? step?.sessionName } : {}),
170
219
  ...(child?.sessionFile ?? step?.sessionFile ?? status?.sessionFile ? { sessionFile: child?.sessionFile ?? step?.sessionFile ?? status?.sessionFile } : {}),
171
220
  ...(child?.intercomTarget ? { intercomTarget: child.intercomTarget } : {}),
172
221
  ...(child?.model ?? step?.model ? { model: child?.model ?? step?.model } : {}),
@@ -174,6 +223,7 @@ function buildImportedResult(root: ImportedAsyncRoot, status: AsyncStatus | null
174
223
  ...(child?.modelAttempts ?? step?.modelAttempts ? { modelAttempts: child?.modelAttempts ?? step?.modelAttempts } : {}),
175
224
  ...(child?.contextOverflow || step?.contextOverflow ? { contextOverflow: true } : {}),
176
225
  ...(child?.totalCost ?? step?.totalCost ? { totalCost: child?.totalCost ?? step?.totalCost } : {}),
226
+ ...(usage ? { usage } : {}),
177
227
  ...(child?.structuredOutput !== undefined ? { structuredOutput: child.structuredOutput } : step?.structuredOutput !== undefined ? { structuredOutput: step.structuredOutput } : {}),
178
228
  ...(child?.structuredOutputPath ?? step?.structuredOutputPath ? { structuredOutputPath: child?.structuredOutputPath ?? step?.structuredOutputPath } : {}),
179
229
  ...(child?.structuredOutputSchemaPath ?? step?.structuredOutputSchemaPath ? { structuredOutputSchemaPath: child?.structuredOutputSchemaPath ?? step?.structuredOutputSchemaPath } : {}),
@@ -182,6 +232,8 @@ function buildImportedResult(root: ImportedAsyncRoot, status: AsyncStatus | null
182
232
  ...(child?.outputSaveError ? { outputSaveError: child.outputSaveError } : {}),
183
233
  ...(child?.transcriptPath ?? step?.transcriptPath ? { transcriptPath: child?.transcriptPath ?? step?.transcriptPath } : {}),
184
234
  ...(child?.transcriptError ? { transcriptError: child.transcriptError } : {}),
235
+ ...(execution ? { execution } : {}),
236
+ ...(child?.effects ?? step?.effects ? { effects: child?.effects ?? step?.effects } : {}),
185
237
  };
186
238
  }
187
239
 
@@ -196,7 +248,7 @@ export async function waitForImportedAsyncRoot(
196
248
  for (;;) {
197
249
  const status = readStatus(root.asyncDir);
198
250
  if (options.shouldAbort?.()) return outputFromTimeout(root, status, options.timeoutMessage ?? "Subagent timed out.");
199
- const result = readResultFile(root.resultPath);
251
+ const result = readImportedResultFile(root, status);
200
252
  if (result) return buildImportedResult(root, status, result);
201
253
  if (isTerminalStatus(status, root.index)) {
202
254
  terminalSince ??= now();
@@ -72,6 +72,14 @@ function uniqueStrings(values: Array<string | undefined>): string[] {
72
72
  return result;
73
73
  }
74
74
 
75
+ function fleetChildDisplayName(child: { agent?: string; sessionName?: string }, fallback = "subagent"): string {
76
+ return child.sessionName?.trim() || child.agent || fallback;
77
+ }
78
+
79
+ function fleetStepDisplayName(step: Pick<AsyncJobStep, "agent" | "sessionName" | "label">): string {
80
+ return step.sessionName?.trim() || (step.label ? `${step.label} (${step.agent})` : step.agent);
81
+ }
82
+
75
83
  function resolveMaybeRelative(asyncDir: string, filePath: string | undefined): string | undefined {
76
84
  if (!filePath) return undefined;
77
85
  return path.resolve(asyncDir, filePath);
@@ -283,7 +291,8 @@ function formatActivityFacts(input: {
283
291
  }
284
292
 
285
293
  function foregroundModeName(control: ForegroundControl): string {
286
- if (control.mode === "single" && control.currentAgent) return control.currentAgent;
294
+ const currentDisplayName = control.sessionName?.trim() || control.currentAgent;
295
+ if (control.mode === "single" && currentDisplayName) return currentDisplayName;
287
296
  return control.mode;
288
297
  }
289
298
 
@@ -302,7 +311,8 @@ function formatForegroundFleetLines(controls: ForegroundControl[]): string[] {
302
311
  toolCount: control.toolCount,
303
312
  ...(control.tokens !== undefined ? { tokens: { total: control.tokens } } : {}),
304
313
  });
305
- const current = control.currentAgent ? ` | ${control.currentAgent}${control.currentIndex !== undefined ? ` #${control.currentIndex}` : ""}` : "";
314
+ const currentDisplayName = control.sessionName?.trim() || control.currentAgent;
315
+ const current = currentDisplayName ? ` | ${currentDisplayName}${control.currentIndex !== undefined ? ` #${control.currentIndex}` : ""}` : "";
306
316
  lines.push(`- ${control.runId} | running | ${foregroundModeName(control)}${current}${activity ? ` | ${activity}` : ""}`);
307
317
  lines.push(` status: subagent({ action: "status", id: "${control.runId}" })`);
308
318
  lines.push(" transcript: live in the expanded foreground result; persisted session transcript appears after completion when sessions are enabled.");
@@ -317,7 +327,7 @@ function formatDetachedForegroundFleetLines(runs: ForegroundRun[]): string[] {
317
327
  const ordered = [...runs].sort((left, right) => right.updatedAt - left.updatedAt);
318
328
  for (const run of ordered) {
319
329
  const detachedChildren = run.children.filter((child) => child.status === "detached");
320
- const childSummary = detachedChildren.map((child) => `${child.agent} #${child.index}`).join(", ");
330
+ const childSummary = detachedChildren.map((child) => `${fleetChildDisplayName(child)} #${child.index}`).join(", ");
321
331
  lines.push(`- ${run.runId} | detached | ${run.mode}${childSummary ? ` | ${childSummary}` : ""}`);
322
332
  lines.push(` status: subagent({ action: "status", id: "${run.runId}" })`);
323
333
  lines.push(` recovery: reply to the supervisor request first, then wait with subagent_wait({ id: "${run.runId}" }); do not resume or launch a replacement while any child remains detached.`);
@@ -338,7 +348,7 @@ function formatAsyncFleetLines(runs: AsyncRunSummary[]): string[] {
338
348
  lines.push(` status: subagent({ action: "status", id: "${run.id}" })`);
339
349
  lines.push(` transcript: subagent({ action: "status", id: "${run.id}", view: "transcript" })`);
340
350
  for (const step of run.steps) {
341
- const display = step.label ? `${step.label} (${step.agent})` : step.agent;
351
+ const display = fleetStepDisplayName(step);
342
352
  const stepContext = contextModeLabel(step.context);
343
353
  const phase = step.phase ? `[${step.phase}] ` : "";
344
354
  const stepActivity = formatActivityFacts(step);
@@ -435,7 +445,7 @@ function selectTranscriptStep(status: AsyncStatus, options: TranscriptOptions):
435
445
  }
436
446
  const step = selectedIndex !== undefined ? steps[selectedIndex] : undefined;
437
447
  const hint = options.index === undefined && steps.length > 1
438
- ? `Tip: pass index to inspect a specific child transcript (${steps.map((candidate, index) => `${index}=${candidate.agent}`).join(", ")}).`
448
+ ? `Tip: pass index to inspect a specific child transcript (${steps.map((candidate, index) => `${index}=${fleetChildDisplayName(candidate)}`).join(", ")}).`
439
449
  : undefined;
440
450
  return { index: selectedIndex, step, hint };
441
451
  }
@@ -445,7 +455,7 @@ function stepStateLine(mode: SubagentRunMode, index: number | undefined, step: A
445
455
  const modelThinking = formatModelThinking(step.model, step.thinking);
446
456
  const context = contextModeLabel(step.context);
447
457
  const parts = [
448
- `${mode === "parallel" ? "Agent" : "Step"}: ${index} (${step.agent})${context ? ` ${context}` : ""}`,
458
+ `${mode === "parallel" ? "Agent" : "Step"}: ${index} (${fleetChildDisplayName(step)})${context ? ` ${context}` : ""}`,
449
459
  step.status,
450
460
  formatActivityFacts(step),
451
461
  modelThinking,
@@ -553,7 +563,7 @@ export function formatNestedRunTranscript(run: NestedRunSummary, options: Transc
553
563
  `Nested run: ${run.id}`,
554
564
  `State: ${run.state}`,
555
565
  run.mode ? `Mode: ${run.mode}` : undefined,
556
- run.agent ? `Agent: ${run.agent}` : run.agents?.length ? `Agents: ${run.agents.join(", ")}` : undefined,
566
+ run.sessionName?.trim() ? `Agent: ${run.sessionName.trim()}` : run.agent ? `Agent: ${run.agent}` : run.agents?.length ? `Agents: ${run.agents.join(", ")}` : undefined,
557
567
  ].filter((line): line is string => Boolean(line));
558
568
  appendKnownArtifacts(lines, { outputPaths: [], sessionFile: run.sessionFile });
559
569
  if (!run.sessionFile) {
@@ -579,14 +589,14 @@ export function formatAsyncResultTranscript(data: {
579
589
  sessionFile?: string;
580
590
  agent?: string;
581
591
  exitCode?: number | null;
582
- results?: Array<{ agent?: string; output?: string; summary?: string; sessionFile?: string; state?: string; success?: boolean; exitCode?: number | null }>;
592
+ results?: Array<{ agent?: string; sessionName?: string; output?: string; summary?: string; sessionFile?: string; state?: string; success?: boolean; exitCode?: number | null }>;
583
593
  }, resultPath: string, options: TranscriptOptions = {}): string {
584
594
  const lineLimit = transcriptLineLimit(options.lines);
585
595
  const runId = data.runId ?? data.id ?? path.basename(resultPath, ".json");
586
596
  const children = Array.isArray(data.results)
587
597
  ? data.results
588
598
  : data.agent
589
- ? [{ agent: data.agent, output: data.output, summary: data.summary, sessionFile: data.sessionFile, state: data.state, success: data.success, exitCode: data.exitCode }]
599
+ ? [{ agent: data.agent, sessionName: undefined, output: data.output, summary: data.summary, sessionFile: data.sessionFile, state: data.state, success: data.success, exitCode: data.exitCode }]
590
600
  : [];
591
601
  let index = options.index;
592
602
  if (index !== undefined && !Number.isInteger(index)) throw new Error("Transcript index must be an integer.");
@@ -601,8 +611,8 @@ export function formatAsyncResultTranscript(data: {
601
611
  const lines = [
602
612
  `Run: ${runId}`,
603
613
  `State: ${data.state ?? (data.success ? "complete" : "failed")}`,
604
- index !== undefined && child ? `Child: ${index} (${child.agent ?? "subagent"})` : undefined,
605
- index === undefined && children.length > 1 ? `Tip: pass index to inspect a specific child transcript (${children.map((candidate, childIndex) => `${childIndex}=${candidate.agent ?? "subagent"}`).join(", ")}).` : undefined,
614
+ index !== undefined && child ? `Child: ${index} (${fleetChildDisplayName(child)})` : undefined,
615
+ index === undefined && children.length > 1 ? `Tip: pass index to inspect a specific child transcript (${children.map((candidate, childIndex) => `${childIndex}=${fleetChildDisplayName(candidate)}`).join(", ")}).` : undefined,
606
616
  ].filter((line): line is string => Boolean(line));
607
617
  appendKnownArtifacts(lines, { outputPaths: [], sessionFile, resultPath });
608
618
  appendTranscriptBody(lines, "Result transcript tail", transcriptLines.filter((line) => line.trim()), output.split(/\r?\n/).length > lineLimit);