pi-subagents 0.58.0 → 0.59.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (105) hide show
  1. package/CHANGELOG.md +65 -0
  2. package/docs/agents.md +5 -3
  3. package/docs/configuration.md +4 -4
  4. package/docs/extension-api.md +1 -1
  5. package/docs/models.md +24 -1
  6. package/docs/observability.md +1 -1
  7. package/docs/tool-reference.md +89 -4
  8. package/docs/workflows.md +93 -2
  9. package/package.json +3 -1
  10. package/prompts/review-loop.md +2 -2
  11. package/skills/council-mode/SKILL.md +1 -1
  12. package/skills/pi-subagents/SKILL.md +1 -1
  13. package/skills/pi-subagents/references/execution-controls.md +5 -1
  14. package/skills/pi-subagents/references/management-authoring-rpc.md +1 -2
  15. package/skills/pi-subagents/references/prompting-and-roles.md +24 -3
  16. package/src/agents/agent-management.ts +5 -17
  17. package/src/agents/agent-serializer.ts +4 -2
  18. package/src/agents/agents.ts +67 -26
  19. package/src/agents/runtime-agent-registry.ts +9 -16
  20. package/src/api/background-work.ts +5 -1
  21. package/src/api/delegation.ts +0 -7
  22. package/src/api/preflight.ts +6 -9
  23. package/src/extension/fanout-child.ts +5 -3
  24. package/src/extension/index.ts +51 -23
  25. package/src/extension/public-execution.ts +15 -2
  26. package/src/extension/schemas.ts +35 -10
  27. package/src/extension/tool-description.ts +18 -3
  28. package/src/intercom/result-intercom.ts +2 -0
  29. package/src/profiles/profiles.ts +5 -6
  30. package/src/runs/background/active-async-capacity.ts +2 -2
  31. package/src/runs/background/async-execution.ts +50 -35
  32. package/src/runs/background/async-job-tracker.ts +14 -12
  33. package/src/runs/background/async-resume.ts +23 -25
  34. package/src/runs/background/async-status-snapshot.ts +23 -261
  35. package/src/runs/background/async-status.ts +66 -7
  36. package/src/runs/background/chain-append.ts +6 -3
  37. package/src/runs/background/chain-root-attachment.ts +60 -8
  38. package/src/runs/background/fleet-view.ts +21 -11
  39. package/src/runs/background/notify.ts +158 -6
  40. package/src/runs/background/result-files.ts +2 -1
  41. package/src/runs/background/result-watcher.ts +2 -0
  42. package/src/runs/background/resume-guidance.ts +1 -1
  43. package/src/runs/background/retained-children.ts +1 -1
  44. package/src/runs/background/run-status.ts +18 -8
  45. package/src/runs/background/scheduled-runs.ts +86 -7
  46. package/src/runs/background/stale-run-reconciler.ts +10 -4
  47. package/src/runs/background/steering.ts +4 -14
  48. package/src/runs/background/subagent-runner.ts +346 -357
  49. package/src/runs/background/subagent-wait.ts +58 -10
  50. package/src/runs/background/terminal-run-index.ts +1 -1
  51. package/src/runs/background/wait-completions.ts +22 -1
  52. package/src/runs/background/wait-config.ts +23 -9
  53. package/src/runs/background/wait-tool.ts +9 -2
  54. package/src/runs/foreground/async-steering-action.ts +2 -2
  55. package/src/runs/foreground/execution.ts +114 -120
  56. package/src/runs/foreground/foreground-control.ts +3 -0
  57. package/src/runs/foreground/foreground-history.ts +1 -0
  58. package/src/runs/foreground/subagent-executor.ts +478 -239
  59. package/src/runs/foreground/workflow-detach-reconcile.ts +99 -200
  60. package/src/runs/shared/abort-recovery.ts +119 -0
  61. package/src/runs/shared/async-status-projection.ts +463 -0
  62. package/src/runs/shared/child-identity.ts +19 -4
  63. package/src/runs/shared/child-launch-plan.ts +151 -0
  64. package/src/runs/shared/completion-evidence.ts +89 -0
  65. package/src/runs/shared/completion-guard.ts +1 -1
  66. package/src/runs/shared/dynamic-fanout.ts +2 -2
  67. package/src/runs/shared/host-step-status.ts +230 -0
  68. package/src/runs/shared/lane-metadata.ts +105 -0
  69. package/src/runs/shared/mcp-config-sources.ts +42 -6
  70. package/src/runs/shared/mcp-direct-tool-allowlist.ts +93 -143
  71. package/src/runs/shared/mcp-direct-tool-grant.ts +197 -0
  72. package/src/runs/shared/model-fallback.ts +9 -2
  73. package/src/runs/shared/nested-events.ts +6 -2
  74. package/src/runs/shared/nested-render.ts +7 -3
  75. package/src/runs/shared/parallel-handoff.ts +419 -7
  76. package/src/runs/shared/parallel-utils.ts +7 -0
  77. package/src/runs/shared/pi-args.ts +20 -2
  78. package/src/runs/shared/single-output.ts +27 -4
  79. package/src/runs/shared/subagent-prompt-runtime.ts +13 -4
  80. package/src/runs/shared/worktree-cleanup-plan.ts +847 -0
  81. package/src/runs/shared/worktree.ts +18 -0
  82. package/src/shared/child-session-name.ts +46 -0
  83. package/src/shared/extension-context.ts +24 -0
  84. package/src/shared/formatters.ts +5 -2
  85. package/src/shared/launch-contract.ts +1 -1
  86. package/src/shared/settings.ts +9 -103
  87. package/src/shared/types.ts +198 -29
  88. package/src/shared/utils.ts +35 -55
  89. package/src/slash/delegation-adapters.ts +1 -8
  90. package/src/slash/delegation-request.ts +0 -4
  91. package/src/slash/slash-bridge.ts +1 -2
  92. package/src/slash/slash-commands.ts +369 -90
  93. package/src/slash/slash-live-state.ts +22 -11
  94. package/src/tui/fleet-status.ts +125 -74
  95. package/src/tui/fleet.ts +11 -5
  96. package/src/tui/render.ts +316 -27
  97. package/src/watchdog/turn-delta.ts +1 -1
  98. package/src/workflows/chat-progress.ts +6 -3
  99. package/src/workflows/host-command.ts +230 -0
  100. package/src/workflows/scripted-workflow.ts +452 -38
  101. package/src/workflows/workflow-child-summary.ts +9 -5
  102. package/src/workflows/workflow-preflight.ts +270 -0
  103. package/src/workflows/workflow-receipt.ts +43 -4
  104. package/src/workflows/workflow-settlement.ts +246 -0
  105. package/src/runs/shared/turn-budget.ts +0 -98
@@ -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;
@@ -58,6 +67,7 @@ interface AsyncRunStepSummary {
58
67
  stopped?: boolean;
59
68
  turnBudget?: TurnBudgetState;
60
69
  turnBudgetExceeded?: boolean;
70
+ toolBudgetBlocked?: boolean;
61
71
  wrapUpRequested?: boolean;
62
72
  acceptance?: AsyncJobStep["acceptance"];
63
73
  agentContract?: AsyncJobStep["agentContract"];
@@ -77,7 +87,7 @@ export interface AsyncRunSummary {
77
87
  asyncDir: string;
78
88
  toolCallId?: string;
79
89
  sessionId?: string;
80
- state: "queued" | "running" | "complete" | "failed" | "paused" | "stopped" | "rejected";
90
+ state: "queued" | "running" | "complete" | "failed" | "partial" | "paused" | "stopped" | "rejected";
81
91
  error?: string;
82
92
  activityState?: ActivityState;
83
93
  lastActivityAt?: number;
@@ -105,6 +115,7 @@ export interface AsyncRunSummary {
105
115
  chainStepCount?: number;
106
116
  pendingAppends?: number;
107
117
  parallelGroups?: AsyncParallelGroupStatus[];
118
+ hostSteps?: HostStepNodeV1[];
108
119
  steps: AsyncRunStepSummary[];
109
120
  sessionDir?: string;
110
121
  outputFile?: string;
@@ -122,8 +133,10 @@ export interface AsyncRunSummary {
122
133
  capabilityAudit?: SubagentCapabilityAudit;
123
134
  parentWorkflowRunId?: string;
124
135
  workflowKey?: string;
136
+ lane?: AsyncStatus["lane"];
125
137
  workflow?: Details["workflow"];
126
138
  workflowChildren?: Details["workflowChildren"];
139
+ preflight?: WorkflowPreflightV1;
127
140
  }
128
141
 
129
142
  interface AsyncRunListOptions {
@@ -232,21 +245,32 @@ function deriveAsyncActivityState(asyncDir: string, status: AsyncStatus): { acti
232
245
  }
233
246
 
234
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")}'`);
235
249
  const workflowChildren = parseWorkflowChildSummary(status.workflowChildren);
236
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);
237
253
  if (status.sessionId !== undefined && typeof status.sessionId !== "string") {
238
254
  throw new Error(`Invalid async status '${path.join(asyncDir, "status.json")}': sessionId must be a string.`);
239
255
  }
240
256
  const { activityState, lastActivityAt } = deriveAsyncActivityState(asyncDir, status);
241
257
  const processTerminal = readProcessTerminal(asyncDir, { runId: status.runId, runnerProcessInstanceId: status.processTerminal?.runnerProcessInstanceId })
242
258
  ?? sanitizeProcessTerminal(status.processTerminal, { runId: status.runId, runnerProcessInstanceId: status.processTerminal?.runnerProcessInstanceId }, path.join(asyncDir, "status.json"));
243
- const runFanoutBudgetDescriptor = readRunFanoutBudgetDescriptor(asyncDir);
244
- 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
+ }
245
269
  const steps = status.steps ?? [];
246
270
  const chainStepCount = status.chainStepCount ?? steps.length;
247
271
  const parallelGroups = normalizeParallelGroups(status.parallelGroups, steps.length, chainStepCount);
248
272
  let nestedChildren: NestedRunSummary[] = [];
249
- if (nestedWarnings.length === 0 && nestedRoute) {
273
+ if (nestedProjectionAllowed && nestedRoute) {
250
274
  try {
251
275
  // The route is resolved by the caller via buildNestedRouteIndex, so this
252
276
  // avoids a fresh scan of the nested-events directory per run.
@@ -262,11 +286,15 @@ function statusToSummary(asyncDir: string, status: AsyncStatus & { cwd?: string
262
286
  index,
263
287
  childId: asyncStatusChildIdentity(step, index),
264
288
  agent: step.agent,
289
+ ...(step.sessionName ? { sessionName: step.sessionName } : {}),
265
290
  ...(step.context ? { context: step.context } : {}),
266
291
  ...(step.label ? { label: step.label } : {}),
267
292
  ...(step.description ? { description: step.description } : {}),
268
293
  ...(step.phase ? { phase: step.phase } : {}),
269
294
  ...(step.workflowKey ? { workflowKey: step.workflowKey } : {}),
295
+ ...(step.lane ? { lane: step.lane } : {}),
296
+ ...(step.worktreePath ? { worktreePath: step.worktreePath } : {}),
297
+ ...(step.branch ? { branch: step.branch } : {}),
270
298
  ...(step.runId ? { runId: step.runId } : {}),
271
299
  ...(step.outputName ? { outputName: step.outputName } : {}),
272
300
  ...(step.structured ? { structured: step.structured } : {}),
@@ -301,6 +329,7 @@ function statusToSummary(asyncDir: string, status: AsyncStatus & { cwd?: string
301
329
  ...(step.stopRequestedAt !== undefined ? { stopRequestedAt: step.stopRequestedAt } : {}),
302
330
  ...(step.turnBudget ? { turnBudget: step.turnBudget } : {}),
303
331
  ...(step.turnBudgetExceeded !== undefined ? { turnBudgetExceeded: step.turnBudgetExceeded } : {}),
332
+ ...(step.toolBudgetBlocked !== undefined ? { toolBudgetBlocked: step.toolBudgetBlocked } : {}),
304
333
  ...(step.wrapUpRequested !== undefined ? { wrapUpRequested: step.wrapUpRequested } : {}),
305
334
  ...(step.acceptance ? { acceptance: step.acceptance } : {}),
306
335
  ...(step.agentContract ? { agentContract: step.agentContract } : {}),
@@ -310,6 +339,7 @@ function statusToSummary(asyncDir: string, status: AsyncStatus & { cwd?: string
310
339
  ...(step.execution ? { execution: step.execution } : {}),
311
340
  ...(step.review ? { review: step.review } : {}),
312
341
  ...(step.effects ? { effects: step.effects } : {}),
342
+ ...(step.watchdog ? { watchdog: step.watchdog } : {}),
313
343
  ...(step.processTerminal ? { processTerminal: sanitizeProcessTerminal(step.processTerminal, { runId: status.runId, runnerProcessInstanceId: step.processTerminal.runnerProcessInstanceId }, `${path.join(asyncDir, "status.json")} step ${index}`) } : {}),
314
344
  ...(step.capabilityCeiling ? { capabilityCeiling: step.capabilityCeiling } : {}),
315
345
  ...(step.capabilityAudit ? { capabilityAudit: step.capabilityAudit } : {}),
@@ -350,6 +380,7 @@ function statusToSummary(asyncDir: string, status: AsyncStatus & { cwd?: string
350
380
  ...(status.chainStepCount !== undefined ? { chainStepCount: status.chainStepCount } : {}),
351
381
  ...(status.pendingAppends !== undefined ? { pendingAppends: status.pendingAppends } : {}),
352
382
  ...(parallelGroups.length ? { parallelGroups } : {}),
383
+ ...(hostSteps.length ? { hostSteps } : {}),
353
384
  steps: summarizedSteps,
354
385
  ...(nestedChildren.length ? { nestedChildren } : {}),
355
386
  ...(nestedWarnings.length ? { nestedWarnings } : {}),
@@ -362,8 +393,10 @@ function statusToSummary(asyncDir: string, status: AsyncStatus & { cwd?: string
362
393
  ...(status.capabilityAudit ? { capabilityAudit: status.capabilityAudit } : {}),
363
394
  ...(status.parentWorkflowRunId ? { parentWorkflowRunId: status.parentWorkflowRunId } : {}),
364
395
  ...(status.workflowKey ? { workflowKey: status.workflowKey } : {}),
396
+ ...(status.lane ? { lane: status.lane } : {}),
365
397
  ...(status.workflow ? { workflow: status.workflow } : {}),
366
398
  ...(workflowChildren ? { workflowChildren } : {}),
399
+ ...(status.preflight ? { preflight: status.preflight } : {}),
367
400
  ...(status.sessionDir ? { sessionDir: status.sessionDir } : {}),
368
401
  ...(status.outputFile ? { outputFile: status.outputFile } : {}),
369
402
  ...(status.totalTokens ? { totalTokens: status.totalTokens } : {}),
@@ -383,6 +416,7 @@ function sortRuns(runs: AsyncRunSummary[]): AsyncRunSummary[] {
383
416
  case "running": return 0;
384
417
  case "queued": return 1;
385
418
  case "failed": return 2;
419
+ case "partial": return 2;
386
420
  case "stopped": return 2;
387
421
  case "paused": return 2;
388
422
  case "complete": return 3;
@@ -511,7 +545,7 @@ function formatActivityFacts(input: { activityState?: ActivityState; lastActivit
511
545
  }
512
546
 
513
547
  function formatStepLine(step: AsyncRunStepSummary): string {
514
- const display = step.label ? `${step.label} (${step.agent})` : step.agent;
548
+ const display = step.sessionName?.trim() || (step.label ? `${step.label} (${step.agent})` : step.agent);
515
549
  const context = contextModeLabel(step.context);
516
550
  const phase = step.phase ? `[${step.phase}] ` : "";
517
551
  const parts = [`${step.index + 1}. ${phase}${display}${context ? ` ${context}` : ""}`, step.status];
@@ -521,9 +555,26 @@ function formatStepLine(step: AsyncRunStepSummary): string {
521
555
  if (modelThinking) parts.push(modelThinking);
522
556
  if (step.durationMs !== undefined) parts.push(formatDuration(step.durationMs));
523
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"}`);
524
560
  return parts.join(" | ");
525
561
  }
526
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
+
527
578
  export function formatAsyncRunOutputPath(run: Pick<AsyncRunSummary, "asyncDir" | "outputFile">): string | undefined {
528
579
  if (!run.outputFile) return undefined;
529
580
  return path.isAbsolute(run.outputFile) ? run.outputFile : path.join(run.asyncDir, run.outputFile);
@@ -556,7 +607,8 @@ function formatRunHeader(run: AsyncRunSummary): string {
556
607
  const activity = formatActivityFacts(run);
557
608
  const pending = run.pendingAppends ? ` | ${run.pendingAppends} pending append${run.pendingAppends === 1 ? "" : "s"}` : "";
558
609
  const context = contextModeLabel(run.context);
559
- 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}`;
560
612
  }
561
613
 
562
614
  export function formatAsyncRunList(runs: AsyncRunSummary[], heading = "Active async runs"): string {
@@ -565,10 +617,17 @@ export function formatAsyncRunList(runs: AsyncRunSummary[], heading = "Active as
565
617
  const lines = [`${heading}: ${runs.length}`, ""];
566
618
  for (const run of runs) {
567
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);
568
623
  for (const step of run.steps) {
569
624
  lines.push(` ${formatStepLine(step)}`);
570
625
  lines.push(...formatNestedRunStatusLines(step.children, { indent: " ", maxLines: 12 }));
571
626
  }
627
+ for (const row of projectAsyncWorkflowRows([], run.hostSteps)) {
628
+ const line = formatHostStepLine(row);
629
+ if (line) lines.push(` ${line}`);
630
+ }
572
631
  const attached = new Set(run.steps.flatMap((step) => step.children?.map((child) => child.id) ?? []));
573
632
  const unattached = run.nestedChildren?.filter((child) => !attached.has(child.id)) ?? [];
574
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,
@@ -180,7 +183,7 @@ function statusStepForTask(task: RunnerSubagentStep): StatusStep {
180
183
  };
181
184
  }
182
185
 
183
- function statusStepsForRunnerStep(step: RunnerStep, stepIndex: number): StatusStep[] {
186
+ function statusStepsForRunnerStep(step: RunnerStep): StatusStep[] {
184
187
  if (isParallelGroup(step)) return step.parallel.map(statusStepForTask);
185
188
  if (isDynamicRunnerGroup(step)) {
186
189
  return [{
@@ -297,7 +300,7 @@ export function appendRunnerStepsToStatus(input: {
297
300
  for (const step of input.steps) {
298
301
  const stepIndex = input.status.chainStepCount ?? input.status.steps?.length ?? 0;
299
302
  const flatIndex = input.status.steps?.length ?? 0;
300
- const statusSteps = statusStepsForRunnerStep(step, stepIndex);
303
+ const statusSteps = statusStepsForRunnerStep(step);
301
304
  input.status.steps ??= [];
302
305
  input.status.steps.push(...statusSteps);
303
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);