{"version":3,"file":"fleet-view.d.ts","sourceRoot":"","sources":["../../../../src/runs/background/fleet-view.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAG/D,OAAO,EAGN,KAAK,WAAW,EAChB,KAAK,OAAO,EAEZ,KAAK,gBAAgB,EAErB,KAAK,aAAa,EAClB,MAAM,uBAAuB,CAAC;AAkB/B,UAAU,eAAe;IACxB,KAAK,CAAC,EAAE,MAAM,CAAC;CACf;AAED,UAAU,aAAa;IACtB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,IAAI,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,OAAO,GAAG,CAAC,KAAK,OAAO,CAAC;IAC7D,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,aAAa,CAAC;IACtB,SAAS,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,UAAU,iBAAiB;IAC1B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;CACxB;AA2VD,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,eAAe,EAAE,IAAI,GAAE,aAAkB,GAAG,eAAe,CAAC,OAAO,CAAC,CAuEjH;AAgFD,wBAAgB,wBAAwB,CACvC,MAAM,EAAE,WAAW,EACnB,QAAQ,EAAE,MAAM,EAChB,OAAO,GAAE,iBAAsB,GAC7B,MAAM,CAkER;AAED,wBAAgB,yBAAyB,CAAC,GAAG,EAAE,gBAAgB,EAAE,OAAO,GAAE,iBAAsB,GAAG,MAAM,CAwBxG;AAED,wBAAgB,2BAA2B,CAC1C,IAAI,EAAE;IACL,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,OAAO,CAAC,EAAE,KAAK,CAAC;QACf,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,OAAO,CAAC,EAAE,OAAO,CAAC;QAClB,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;KACzB,CAAC,CAAC;CACH,EACD,UAAU,EAAE,MAAM,EAClB,OAAO,GAAE,iBAAsB,GAC7B,MAAM,CAmDR","sourcesContent":["import * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport type { AgentToolResult } from \"@lpb-work/pi-agent-core\";\nimport { formatDuration, formatModelThinking, formatTokens, shortenPath } from \"../../shared/formatters.ts\";\nimport { formatActivityLabel } from \"../../shared/status-format.ts\";\nimport {\n\ttype ActivityState,\n\ttype AsyncJobStep,\n\ttype AsyncStatus,\n\ttype Details,\n\tDIRS,\n\ttype NestedRunSummary,\n\ttype SubagentRunMode,\n\ttype SubagentState,\n} from \"../../shared/types.ts\";\nimport { readStatus } from \"../../shared/utils.ts\";\nimport { contextModeLabel, summarizeContextModes } from \"../shared/context-mode.ts\";\nimport { formatNestedRunStatusLines } from \"../shared/nested-render.ts\";\nimport {\n\ttype AsyncRunSummary,\n\tformatAsyncRunOutputPath,\n\tformatAsyncRunProgressLabel,\n\tlistAsyncRuns,\n} from \"./async-status.ts\";\n\nconst DEFAULT_TRANSCRIPT_LINES = 80;\nconst MAX_TRANSCRIPT_LINES = 500;\nconst TRANSCRIPT_TAIL_BYTES = 256 * 1024;\n\ntype ForegroundControl = SubagentState[\"foregroundControls\"] extends Map<string, infer T> ? T : never;\ntype ForegroundRun = NonNullable<SubagentState[\"foregroundRuns\"]> extends Map<string, infer T> ? T : never;\n\ninterface FleetViewParams {\n\tlines?: number;\n}\n\ninterface FleetViewDeps {\n\tasyncDirRoot?: string;\n\tresultsDir?: string;\n\tkill?: (pid: number, signal?: NodeJS.Signals | 0) => boolean;\n\tnow?: () => number;\n\tstate?: SubagentState;\n\tchildSafe?: boolean;\n}\n\ninterface TranscriptOptions {\n\tindex?: number;\n\tlines?: number;\n\tsessionRoots?: string[];\n}\n\ninterface TextTailResult {\n\tpath: string;\n\tlines: string[];\n\ttruncated: boolean;\n\terror?: string;\n}\n\nfunction transcriptLineLimit(value: number | undefined): number {\n\tif (value === undefined) return DEFAULT_TRANSCRIPT_LINES;\n\tif (!Number.isFinite(value)) return DEFAULT_TRANSCRIPT_LINES;\n\treturn Math.max(1, Math.min(MAX_TRANSCRIPT_LINES, Math.trunc(value)));\n}\n\nfunction uniqueStrings(values: Array<string | undefined>): string[] {\n\tconst seen = new Set<string>();\n\tconst result: string[] = [];\n\tfor (const value of values) {\n\t\tif (!value || seen.has(value)) continue;\n\t\tseen.add(value);\n\t\tresult.push(value);\n\t}\n\treturn result;\n}\n\nfunction resolveMaybeRelative(asyncDir: string, filePath: string | undefined): string | undefined {\n\tif (!filePath) return undefined;\n\treturn path.resolve(asyncDir, filePath);\n}\n\nfunction pathWithin(base: string, candidate: string): boolean {\n\tconst resolvedBase = path.resolve(base);\n\tconst resolvedCandidate = path.resolve(candidate);\n\treturn resolvedCandidate === resolvedBase || resolvedCandidate.startsWith(`${resolvedBase}${path.sep}`);\n}\n\nfunction getErrorMessage(error: unknown): string {\n\treturn error instanceof Error ? error.message : String(error);\n}\n\nfunction isNotFoundError(error: unknown): boolean {\n\treturn (\n\t\ttypeof error === \"object\" &&\n\t\terror !== null &&\n\t\t\"code\" in error &&\n\t\t(error as NodeJS.ErrnoException).code === \"ENOENT\"\n\t);\n}\n\nfunction readTextTail(filePath: string, maxLines: number): TextTailResult {\n\tlet stat: fs.Stats;\n\ttry {\n\t\tstat = fs.statSync(filePath);\n\t} catch (error) {\n\t\tif (isNotFoundError(error)) return { path: filePath, lines: [], truncated: false };\n\t\treturn { path: filePath, lines: [], truncated: false, error: getErrorMessage(error) };\n\t}\n\tif (stat.size === 0) return { path: filePath, lines: [], truncated: false };\n\n\tlet fd: number | undefined;\n\ttry {\n\t\tconst bytesToRead = Math.min(stat.size, TRANSCRIPT_TAIL_BYTES);\n\t\tconst start = stat.size - bytesToRead;\n\t\tconst buffer = Buffer.alloc(bytesToRead);\n\t\tfd = fs.openSync(filePath, \"r\");\n\t\tconst bytesRead = fs.readSync(fd, buffer, 0, bytesToRead, start);\n\t\tconst content = buffer.subarray(0, bytesRead).toString(\"utf-8\");\n\t\tlet lines = content.split(/\\r?\\n/);\n\t\tif (start > 0 && lines.length > 0) lines = lines.slice(1);\n\t\tif (lines.at(-1) === \"\") lines = lines.slice(0, -1);\n\t\treturn { path: filePath, lines: lines.slice(-maxLines), truncated: start > 0 || lines.length > maxLines };\n\t} catch (error) {\n\t\treturn { path: filePath, lines: [], truncated: false, error: getErrorMessage(error) };\n\t} finally {\n\t\tif (fd !== undefined) fs.closeSync(fd);\n\t}\n}\n\nfunction readContainedTextTail(\n\tfilePath: string,\n\tmaxLines: number,\n\ttrustedRoots: string[],\n\tlabel: string,\n): TextTailResult {\n\tif (trustedRoots.length === 0)\n\t\treturn {\n\t\t\tpath: filePath,\n\t\t\tlines: [],\n\t\t\ttruncated: false,\n\t\t\terror: `Refusing to read ${label} transcript path without a trusted root: ${filePath}`,\n\t\t};\n\tconst resolvedPath = path.resolve(filePath);\n\tif (!trustedRoots.some((root) => pathWithin(root, resolvedPath))) {\n\t\treturn {\n\t\t\tpath: filePath,\n\t\t\tlines: [],\n\t\t\ttruncated: false,\n\t\t\terror: `Refusing to read ${label} transcript path outside trusted roots: ${filePath}`,\n\t\t};\n\t}\n\tlet lstat: fs.Stats;\n\ttry {\n\t\tlstat = fs.lstatSync(resolvedPath);\n\t} catch (error) {\n\t\tif (isNotFoundError(error)) return { path: filePath, lines: [], truncated: false };\n\t\treturn { path: filePath, lines: [], truncated: false, error: getErrorMessage(error) };\n\t}\n\tif (lstat.isSymbolicLink())\n\t\treturn {\n\t\t\tpath: filePath,\n\t\t\tlines: [],\n\t\t\ttruncated: false,\n\t\t\terror: `Refusing to read symlink ${label} transcript path: ${filePath}`,\n\t\t};\n\tif (!lstat.isFile())\n\t\treturn {\n\t\t\tpath: filePath,\n\t\t\tlines: [],\n\t\t\ttruncated: false,\n\t\t\terror: `Refusing to read non-file ${label} transcript path: ${filePath}`,\n\t\t};\n\tlet realPath: string;\n\tlet realRoots: string[];\n\ttry {\n\t\trealPath = fs.realpathSync(resolvedPath);\n\t\trealRoots = trustedRoots.filter((root) => fs.existsSync(root)).map((root) => fs.realpathSync(root));\n\t} catch (error) {\n\t\treturn { path: filePath, lines: [], truncated: false, error: getErrorMessage(error) };\n\t}\n\tif (!realRoots.some((root) => pathWithin(root, realPath))) {\n\t\treturn {\n\t\t\tpath: filePath,\n\t\t\tlines: [],\n\t\t\ttruncated: false,\n\t\t\terror: `Refusing to read ${label} transcript path outside trusted roots: ${filePath}`,\n\t\t};\n\t}\n\treturn readTextTail(resolvedPath, maxLines);\n}\n\nfunction stringifyJsonPreview(value: unknown, maxLength = 240): string {\n\tlet raw: string;\n\tif (typeof value === \"string\") raw = value;\n\telse raw = JSON.stringify(value);\n\treturn raw.length > maxLength ? `${raw.slice(0, maxLength)}…` : raw;\n}\n\nfunction contentText(content: unknown): string {\n\tif (typeof content === \"string\") return content;\n\tif (!Array.isArray(content)) return \"\";\n\treturn content\n\t\t.map((part) => {\n\t\t\tif (!part || typeof part !== \"object\") return \"\";\n\t\t\tconst entry = part as {\n\t\t\t\ttype?: unknown;\n\t\t\t\ttext?: unknown;\n\t\t\t\tname?: unknown;\n\t\t\t\ttoolName?: unknown;\n\t\t\t\targs?: unknown;\n\t\t\t\tresult?: unknown;\n\t\t\t\tcontent?: unknown;\n\t\t\t};\n\t\t\tif (typeof entry.text === \"string\") return entry.text;\n\t\t\tif (entry.type === \"toolCall\" || entry.type === \"tool_call\") {\n\t\t\t\tconst name =\n\t\t\t\t\ttypeof entry.name === \"string\"\n\t\t\t\t\t\t? entry.name\n\t\t\t\t\t\t: typeof entry.toolName === \"string\"\n\t\t\t\t\t\t\t? entry.toolName\n\t\t\t\t\t\t\t: \"tool\";\n\t\t\t\treturn `[tool: ${name}${entry.args === undefined ? \"\" : ` ${stringifyJsonPreview(entry.args)}`}]`;\n\t\t\t}\n\t\t\tif (entry.type === \"toolResult\" || entry.type === \"tool_result\") {\n\t\t\t\treturn `[tool result${entry.result === undefined ? \"\" : `: ${stringifyJsonPreview(entry.result)}`}]`;\n\t\t\t}\n\t\t\tif (entry.content !== undefined) return stringifyJsonPreview(entry.content);\n\t\t\treturn \"\";\n\t\t})\n\t\t.filter(Boolean)\n\t\t.join(\"\\n\");\n}\n\nfunction sessionMessageLine(record: unknown): string | undefined {\n\tif (!record || typeof record !== \"object\") return undefined;\n\tconst outer = record as { message?: unknown; role?: unknown; content?: unknown; type?: unknown };\n\tconst message =\n\t\touter.message && typeof outer.message === \"object\"\n\t\t\t? (outer.message as { role?: unknown; content?: unknown })\n\t\t\t: outer;\n\tconst role = typeof message.role === \"string\" ? message.role : undefined;\n\tif (!role) return undefined;\n\tconst text = contentText(message.content).trim();\n\tif (!text) return undefined;\n\treturn `${role}: ${text}`;\n}\n\nfunction readSessionTranscriptTail(\n\tsessionFile: string,\n\tmaxLines: number,\n\ttrustedRoots: string[],\n): { lines: string[]; warnings: string[] } {\n\tconst tail = readContainedTextTail(sessionFile, Math.max(maxLines * 4, maxLines), trustedRoots, \"session\");\n\tconst warnings: string[] = [];\n\tif (tail.error) warnings.push(`Session read failed for ${sessionFile}: ${tail.error}`);\n\tconst lines: string[] = [];\n\tlet malformed = 0;\n\tfor (const line of tail.lines) {\n\t\tif (!line.trim()) continue;\n\t\ttry {\n\t\t\tconst parsed = JSON.parse(line) as unknown;\n\t\t\tconst messageLine = sessionMessageLine(parsed);\n\t\t\tif (messageLine) lines.push(messageLine);\n\t\t} catch {\n\t\t\tmalformed++;\n\t\t}\n\t}\n\tif (malformed > 0) warnings.push(`Skipped ${malformed} malformed session tail line${malformed === 1 ? \"\" : \"s\"}.`);\n\treturn { lines: lines.slice(-maxLines), warnings };\n}\n\nfunction formatActivityFacts(input: {\n\tactivityState?: ActivityState;\n\tlastActivityAt?: number;\n\tcurrentTool?: string;\n\tcurrentToolStartedAt?: number;\n\tcurrentPath?: string;\n\tturnCount?: number;\n\ttoolCount?: number;\n\ttokens?: { total: number };\n}): string | undefined {\n\tconst facts: string[] = [];\n\tif (input.currentTool && input.currentToolStartedAt !== undefined)\n\t\tfacts.push(`tool ${input.currentTool} ${formatDuration(Math.max(0, Date.now() - input.currentToolStartedAt))}`);\n\telse if (input.currentTool) facts.push(`tool ${input.currentTool}`);\n\tif (input.currentPath) facts.push(shortenPath(input.currentPath));\n\tif (input.turnCount !== undefined) facts.push(`${input.turnCount} turns`);\n\tif (input.toolCount !== undefined) facts.push(`${input.toolCount} tools`);\n\tif (input.tokens?.total) facts.push(`${formatTokens(input.tokens.total)} tok`);\n\tconst activity = formatActivityLabel(input.lastActivityAt, input.activityState);\n\treturn activity || facts.length ? [activity, ...facts].filter(Boolean).join(\" | \") : undefined;\n}\n\nfunction foregroundModeName(control: ForegroundControl): string {\n\tif (control.mode === \"single\" && control.currentAgent) return control.currentAgent;\n\treturn control.mode;\n}\n\nfunction formatForegroundFleetLines(controls: ForegroundControl[]): string[] {\n\tif (controls.length === 0) return [];\n\tconst lines = [\"Foreground runs:\"];\n\tconst ordered = [...controls].sort((left, right) => right.updatedAt - left.updatedAt);\n\tfor (const control of ordered) {\n\t\tconst activity = formatActivityFacts({\n\t\t\tactivityState: control.currentActivityState,\n\t\t\tlastActivityAt: control.lastActivityAt,\n\t\t\tcurrentTool: control.currentTool,\n\t\t\tcurrentToolStartedAt: control.currentToolStartedAt,\n\t\t\tcurrentPath: control.currentPath,\n\t\t\tturnCount: control.turnCount,\n\t\t\ttoolCount: control.toolCount,\n\t\t\t...(control.tokens !== undefined ? { tokens: { total: control.tokens } } : {}),\n\t\t});\n\t\tconst current = control.currentAgent\n\t\t\t? ` | ${control.currentAgent}${control.currentIndex !== undefined ? ` #${control.currentIndex}` : \"\"}`\n\t\t\t: \"\";\n\t\tlines.push(\n\t\t\t`- ${control.runId} | running | ${foregroundModeName(control)}${current}${activity ? ` | ${activity}` : \"\"}`,\n\t\t);\n\t\tlines.push(`  status: subagent({ action: \"status\", id: \"${control.runId}\" })`);\n\t\tlines.push(\n\t\t\t\"  transcript: live in the expanded foreground result; persisted session transcript appears after completion when sessions are enabled.\",\n\t\t);\n\t\tlines.push(\n\t\t\t...formatNestedRunStatusLines(control.nestedChildren, { indent: \"  \", commandHints: true, maxLines: 12 }),\n\t\t);\n\t}\n\treturn lines;\n}\n\nfunction formatDetachedForegroundFleetLines(runs: ForegroundRun[]): string[] {\n\tif (runs.length === 0) return [];\n\tconst lines = [\"Detached foreground runs:\"];\n\tconst ordered = [...runs].sort((left, right) => right.updatedAt - left.updatedAt);\n\tfor (const run of ordered) {\n\t\tconst detachedChildren = run.children.filter((child) => child.status === \"detached\");\n\t\tconst childSummary = detachedChildren.map((child) => `${child.agent} #${child.index}`).join(\", \");\n\t\tlines.push(`- ${run.runId} | detached | ${run.mode}${childSummary ? ` | ${childSummary}` : \"\"}`);\n\t\tlines.push(`  status: subagent({ action: \"status\", id: \"${run.runId}\" })`);\n\t\tlines.push(\n\t\t\t`  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.`,\n\t\t);\n\t}\n\treturn lines;\n}\n\nfunction formatAsyncFleetLines(runs: AsyncRunSummary[]): string[] {\n\tif (runs.length === 0) return [];\n\tconst lines = [\"Async runs:\"];\n\tfor (const run of runs) {\n\t\tconst progress = formatAsyncRunProgressLabel(run);\n\t\tconst activity = formatActivityFacts(run);\n\t\tconst cwd = run.cwd ? shortenPath(run.cwd) : shortenPath(run.asyncDir);\n\t\tconst pending = run.pendingAppends\n\t\t\t? ` | ${run.pendingAppends} pending append${run.pendingAppends === 1 ? \"\" : \"s\"}`\n\t\t\t: \"\";\n\t\tconst runContext = contextModeLabel(run.context);\n\t\tlines.push(\n\t\t\t`- ${run.id} | ${run.state}${activity ? ` | ${activity}` : \"\"} | ${run.mode}${runContext ? ` ${runContext}` : \"\"} | ${progress}${pending} | ${cwd}`,\n\t\t);\n\t\tlines.push(`  status: subagent({ action: \"status\", id: \"${run.id}\" })`);\n\t\tlines.push(`  transcript: subagent({ action: \"status\", id: \"${run.id}\", view: \"transcript\" })`);\n\t\tfor (const step of run.steps) {\n\t\t\tconst display = step.label ? `${step.label} (${step.agent})` : step.agent;\n\t\t\tconst stepContext = contextModeLabel(step.context);\n\t\t\tconst phase = step.phase ? `[${step.phase}] ` : \"\";\n\t\t\tconst stepActivity = formatActivityFacts(step);\n\t\t\tconst modelThinking = formatModelThinking(step.model, step.thinking);\n\t\t\tconst parts = [\n\t\t\t\t`${step.index}. ${phase}${display}${stepContext ? ` ${stepContext}` : \"\"}`,\n\t\t\t\tstep.status,\n\t\t\t\tstepActivity,\n\t\t\t\tmodelThinking,\n\t\t\t].filter(Boolean);\n\t\t\tlines.push(`  ${parts.join(\" | \")}`);\n\t\t\tconst output = path.join(run.asyncDir, `output-${step.index}.log`);\n\t\t\tif (fs.existsSync(output)) lines.push(`    output: ${shortenPath(output)}`);\n\t\t\tif (step.sessionFile) lines.push(`    session: ${shortenPath(step.sessionFile)}`);\n\t\t\tif (step.status === \"running\" || step.recentOutput?.length || fs.existsSync(output)) {\n\t\t\t\tlines.push(\n\t\t\t\t\t`    transcript: subagent({ action: \"status\", id: \"${run.id}\", index: ${step.index}, view: \"transcript\" })`,\n\t\t\t\t);\n\t\t\t}\n\t\t\tlines.push(...formatNestedRunStatusLines(step.children, { indent: \"    \", commandHints: true, maxLines: 12 }));\n\t\t}\n\t\tconst attached = new Set(run.steps.flatMap((step) => step.children?.map((child) => child.id) ?? []));\n\t\tconst unattached = run.nestedChildren?.filter((child) => !attached.has(child.id)) ?? [];\n\t\tlines.push(...formatNestedRunStatusLines(unattached, { indent: \"  \", commandHints: true, maxLines: 12 }));\n\t\tif (run.error) lines.push(`  error: ${run.error}`);\n\t\tfor (const warning of run.nestedWarnings ?? []) lines.push(`  warning: ${warning}`);\n\t\tconst outputPath = formatAsyncRunOutputPath(run);\n\t\tif (outputPath) lines.push(`  output: ${shortenPath(outputPath)}`);\n\t\tif (run.sessionFile) lines.push(`  session: ${shortenPath(run.sessionFile)}`);\n\t}\n\treturn lines;\n}\n\nexport function inspectSubagentFleet(_params: FleetViewParams, deps: FleetViewDeps = {}): AgentToolResult<Details> {\n\tif (deps.childSafe) {\n\t\treturn {\n\t\t\tcontent: [\n\t\t\t\t{\n\t\t\t\t\ttype: \"text\",\n\t\t\t\t\ttext: 'Child-safe subagent fleet view is unavailable without an explicit run id. Use subagent({ action: \"status\", id: \"...\" }) for the delegated run you can see.',\n\t\t\t\t},\n\t\t\t],\n\t\t\tisError: true,\n\t\t\tdetails: { mode: \"management\", results: [] },\n\t\t};\n\t}\n\n\tlet asyncRuns: AsyncRunSummary[];\n\ttry {\n\t\tasyncRuns = listAsyncRuns(deps.asyncDirRoot ?? DIRS.async, {\n\t\t\tstates: [\"queued\", \"running\"],\n\t\t\tsessionId: deps.state?.currentSessionId ?? undefined,\n\t\t\tresultsDir: deps.resultsDir ?? DIRS.results,\n\t\t\tkill: deps.kill,\n\t\t\tnow: deps.now,\n\t\t});\n\t} catch (error) {\n\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\treturn {\n\t\t\tcontent: [{ type: \"text\", text: message }],\n\t\t\tisError: true,\n\t\t\tdetails: { mode: \"management\", results: [] },\n\t\t};\n\t}\n\n\tconst foregroundControls = deps.state ? [...deps.state.foregroundControls.values()] : [];\n\tconst activeForegroundIds = new Set(foregroundControls.map((control) => control.runId));\n\tconst detachedForegroundRuns = deps.state?.foregroundRuns\n\t\t? [...deps.state.foregroundRuns.values()].filter(\n\t\t\t\t(run) =>\n\t\t\t\t\trun.sessionId === deps.state?.currentSessionId &&\n\t\t\t\t\t!activeForegroundIds.has(run.runId) &&\n\t\t\t\t\trun.children.some((child) => child.status === \"detached\"),\n\t\t\t)\n\t\t: [];\n\tconst total = foregroundControls.length + detachedForegroundRuns.length + asyncRuns.length;\n\tif (total === 0) {\n\t\treturn {\n\t\t\tcontent: [\n\t\t\t\t{\n\t\t\t\t\ttype: \"text\",\n\t\t\t\t\ttext: 'No active subagent fleet. Background runs that already finished are available through completion notifications or subagent({ action: \"status\", id: \"...\" }).',\n\t\t\t\t},\n\t\t\t],\n\t\t\tdetails: { mode: \"management\", results: [] },\n\t\t};\n\t}\n\n\tconst lines = [`Subagent fleet: ${total} tracked`, \"\"];\n\tconst foregroundLines = formatForegroundFleetLines(foregroundControls);\n\tif (foregroundLines.length) lines.push(...foregroundLines, \"\");\n\tconst detachedForegroundLines = formatDetachedForegroundFleetLines(detachedForegroundRuns);\n\tif (detachedForegroundLines.length) lines.push(...detachedForegroundLines, \"\");\n\tconst asyncLines = formatAsyncFleetLines(asyncRuns);\n\tif (asyncLines.length) lines.push(...asyncLines, \"\");\n\tlines.push(\"Commands:\");\n\tlines.push('  Refresh fleet: subagent({ action: \"status\", view: \"fleet\" })');\n\tlines.push('  Tail run transcript: subagent({ action: \"status\", id: \"<run-id>\", view: \"transcript\" })');\n\tlines.push('  Tail child transcript: subagent({ action: \"status\", id: \"<run-id>\", index: 0, view: \"transcript\" })');\n\n\treturn {\n\t\tcontent: [{ type: \"text\", text: lines.join(\"\\n\").trimEnd() }],\n\t\tdetails: { mode: \"management\", results: [] },\n\t};\n}\n\nfunction validateTranscriptIndex(index: number | undefined, steps: AsyncJobStep[]): number | undefined {\n\tif (index === undefined) return undefined;\n\tif (!Number.isInteger(index)) throw new Error(\"Transcript index must be an integer.\");\n\tif (index < 0 || index >= steps.length)\n\t\tthrow new Error(\n\t\t\t`Transcript index ${index} is out of range for ${steps.length} child step${steps.length === 1 ? \"\" : \"s\"}.`,\n\t\t);\n\treturn index;\n}\n\nfunction selectTranscriptStep(\n\tstatus: AsyncStatus,\n\toptions: TranscriptOptions,\n): { index?: number; step?: AsyncJobStep; hint?: string } {\n\tconst steps = status.steps ?? [];\n\tlet selectedIndex = validateTranscriptIndex(options.index, steps);\n\tif (selectedIndex === undefined) {\n\t\tif (\n\t\t\tstatus.state === \"running\" &&\n\t\t\ttypeof status.currentStep === \"number\" &&\n\t\t\tstatus.currentStep >= 0 &&\n\t\t\tstatus.currentStep < steps.length\n\t\t) {\n\t\t\tselectedIndex = status.currentStep;\n\t\t} else if (steps.length === 1) {\n\t\t\tselectedIndex = 0;\n\t\t}\n\t}\n\tconst step = selectedIndex !== undefined ? steps[selectedIndex] : undefined;\n\tconst hint =\n\t\toptions.index === undefined && steps.length > 1\n\t\t\t? `Tip: pass index to inspect a specific child transcript (${steps.map((candidate, index) => `${index}=${candidate.agent}`).join(\", \")}).`\n\t\t\t: undefined;\n\treturn { index: selectedIndex, step, hint };\n}\n\nfunction stepStateLine(\n\tmode: SubagentRunMode,\n\tindex: number | undefined,\n\tstep: AsyncJobStep | undefined,\n): string | undefined {\n\tif (index === undefined || !step) return undefined;\n\tconst modelThinking = formatModelThinking(step.model, step.thinking);\n\tconst context = contextModeLabel(step.context);\n\tconst parts = [\n\t\t`${mode === \"parallel\" ? \"Agent\" : \"Step\"}: ${index} (${step.agent})${context ? ` ${context}` : \"\"}`,\n\t\tstep.status,\n\t\tformatActivityFacts(step),\n\t\tmodelThinking,\n\t\tstep.error ? `error: ${step.error}` : undefined,\n\t].filter(Boolean);\n\treturn parts.join(\" | \");\n}\n\nfunction appendKnownArtifacts(\n\tlines: string[],\n\tinput: { outputPaths: string[]; sessionFile?: string; eventsPath?: string; logPath?: string; resultPath?: string },\n): void {\n\tconst artifacts: string[] = [];\n\tfor (const outputPath of input.outputPaths) artifacts.push(`Output: ${outputPath}`);\n\tif (input.sessionFile) artifacts.push(`Session: ${input.sessionFile}`);\n\tif (input.eventsPath) artifacts.push(`Events: ${input.eventsPath}`);\n\tif (input.logPath) artifacts.push(`Log: ${input.logPath}`);\n\tif (input.resultPath) artifacts.push(`Result: ${input.resultPath}`);\n\tif (!artifacts.length) return;\n\tlines.push(\"Artifacts:\");\n\tfor (const artifact of artifacts) lines.push(`  ${artifact}`);\n}\n\nfunction appendTranscriptBody(lines: string[], sourceLabel: string, sourceLines: string[], truncated: boolean): void {\n\tlines.push(`${sourceLabel}${truncated ? \" (tail truncated)\" : \"\"}:`);\n\tif (sourceLines.length === 0) {\n\t\tlines.push(\"  (no transcript lines available yet)\");\n\t\treturn;\n\t}\n\tfor (const line of sourceLines) lines.push(`  ${line}`);\n}\n\nexport function formatAsyncRunTranscript(\n\tstatus: AsyncStatus,\n\tasyncDir: string,\n\toptions: TranscriptOptions = {},\n): string {\n\tconst lineLimit = transcriptLineLimit(options.lines);\n\tconst selected = selectTranscriptStep(status, options);\n\tconst stepOutputPath =\n\t\tselected.index !== undefined ? path.join(asyncDir, `output-${selected.index}.log`) : undefined;\n\tconst runOutputPath = resolveMaybeRelative(asyncDir, status.outputFile);\n\tconst logPath = path.join(asyncDir, `subagent-log-${status.runId}.md`);\n\tconst outputPaths =\n\t\tselected.index !== undefined\n\t\t\t? uniqueStrings([\n\t\t\t\t\tstepOutputPath,\n\t\t\t\t\trunOutputPath && stepOutputPath && path.resolve(runOutputPath) === path.resolve(stepOutputPath)\n\t\t\t\t\t\t? runOutputPath\n\t\t\t\t\t\t: undefined,\n\t\t\t\t])\n\t\t\t: uniqueStrings([runOutputPath]);\n\tconst sessionFile = selected.index !== undefined ? selected.step?.sessionFile : status.sessionFile;\n\tconst eventsPath = path.join(asyncDir, \"events.jsonl\");\n\n\tconst context = contextModeLabel(\n\t\tstatus.context ?? summarizeContextModes((status.steps ?? []).map((step) => step.context)),\n\t);\n\tconst lines = [\n\t\t`Run: ${status.runId}`,\n\t\t`State: ${status.state}`,\n\t\t`Mode: ${status.mode}${context ? ` ${context}` : \"\"}`,\n\t\tstepStateLine(status.mode, selected.index, selected.step),\n\t\tselected.hint,\n\t].filter((line): line is string => Boolean(line));\n\tappendKnownArtifacts(lines, {\n\t\toutputPaths,\n\t\tsessionFile,\n\t\teventsPath: fs.existsSync(eventsPath) ? eventsPath : undefined,\n\t\tlogPath: fs.existsSync(logPath) ? logPath : undefined,\n\t});\n\n\tconst warnings: string[] = [];\n\tlet transcriptLines: string[] = [];\n\tlet transcriptSource = \"Transcript tail\";\n\tlet truncated = false;\n\tfor (const outputPath of outputPaths) {\n\t\tconst tail = readContainedTextTail(outputPath, lineLimit, [asyncDir], \"output\");\n\t\tif (tail.error) warnings.push(`Output read failed for ${tail.path}: ${tail.error}`);\n\t\tif (tail.lines.length === 0) continue;\n\t\ttranscriptLines = tail.lines;\n\t\ttranscriptSource = `Transcript tail from ${tail.path}`;\n\t\ttruncated = tail.truncated;\n\t\tbreak;\n\t}\n\tif (transcriptLines.length === 0 && selected.step?.recentOutput?.length) {\n\t\ttranscriptLines = selected.step.recentOutput.slice(-lineLimit);\n\t\ttranscriptSource = \"Recent output from status.json\";\n\t}\n\tif (transcriptLines.length === 0 && sessionFile) {\n\t\tconst sessionTail = readSessionTranscriptTail(sessionFile, lineLimit, options.sessionRoots ?? []);\n\t\ttranscriptLines = sessionTail.lines;\n\t\twarnings.push(...sessionTail.warnings);\n\t\tif (transcriptLines.length > 0) transcriptSource = `Session transcript tail from ${sessionFile}`;\n\t}\n\n\tif (warnings.length) {\n\t\tlines.push(\"Warnings:\");\n\t\tfor (const warning of warnings) lines.push(`  ${warning}`);\n\t}\n\tappendTranscriptBody(lines, transcriptSource, transcriptLines, truncated);\n\treturn lines.join(\"\\n\");\n}\n\nexport function formatNestedRunTranscript(run: NestedRunSummary, options: TranscriptOptions = {}): string {\n\tif (run.asyncDir) {\n\t\tconst status = readStatus(run.asyncDir);\n\t\tif (status) return formatAsyncRunTranscript(status, run.asyncDir, options);\n\t}\n\tconst lineLimit = transcriptLineLimit(options.lines);\n\tconst lines = [\n\t\t`Nested run: ${run.id}`,\n\t\t`State: ${run.state}`,\n\t\trun.mode ? `Mode: ${run.mode}` : undefined,\n\t\trun.agent ? `Agent: ${run.agent}` : run.agents?.length ? `Agents: ${run.agents.join(\", \")}` : undefined,\n\t].filter((line): line is string => Boolean(line));\n\tappendKnownArtifacts(lines, { outputPaths: [], sessionFile: run.sessionFile });\n\tif (!run.sessionFile) {\n\t\tappendTranscriptBody(lines, \"Transcript tail\", [], false);\n\t\treturn lines.join(\"\\n\");\n\t}\n\tconst sessionTail = readSessionTranscriptTail(run.sessionFile, lineLimit, options.sessionRoots ?? []);\n\tif (sessionTail.warnings.length) {\n\t\tlines.push(\"Warnings:\");\n\t\tfor (const warning of sessionTail.warnings) lines.push(`  ${warning}`);\n\t}\n\tappendTranscriptBody(lines, `Session transcript tail from ${run.sessionFile}`, sessionTail.lines, false);\n\treturn lines.join(\"\\n\");\n}\n\nexport function formatAsyncResultTranscript(\n\tdata: {\n\t\tid?: string;\n\t\trunId?: string;\n\t\tstate?: string;\n\t\tsuccess?: boolean;\n\t\tsummary?: string;\n\t\toutput?: string;\n\t\tsessionFile?: string;\n\t\tagent?: string;\n\t\texitCode?: number | null;\n\t\tresults?: Array<{\n\t\t\tagent?: string;\n\t\t\toutput?: string;\n\t\t\tsummary?: string;\n\t\t\tsessionFile?: string;\n\t\t\tstate?: string;\n\t\t\tsuccess?: boolean;\n\t\t\texitCode?: number | null;\n\t\t}>;\n\t},\n\tresultPath: string,\n\toptions: TranscriptOptions = {},\n): string {\n\tconst lineLimit = transcriptLineLimit(options.lines);\n\tconst runId = data.runId ?? data.id ?? path.basename(resultPath, \".json\");\n\tconst children = Array.isArray(data.results)\n\t\t? data.results\n\t\t: data.agent\n\t\t\t? [\n\t\t\t\t\t{\n\t\t\t\t\t\tagent: data.agent,\n\t\t\t\t\t\toutput: data.output,\n\t\t\t\t\t\tsummary: data.summary,\n\t\t\t\t\t\tsessionFile: data.sessionFile,\n\t\t\t\t\t\tstate: data.state,\n\t\t\t\t\t\tsuccess: data.success,\n\t\t\t\t\t\texitCode: data.exitCode,\n\t\t\t\t\t},\n\t\t\t\t]\n\t\t\t: [];\n\tlet index = options.index;\n\tif (index !== undefined && !Number.isInteger(index)) throw new Error(\"Transcript index must be an integer.\");\n\tif (index === undefined && children.length === 1) index = 0;\n\tif (index !== undefined && (index < 0 || index >= children.length))\n\t\tthrow new Error(\n\t\t\t`Transcript index ${index} is out of range for ${children.length} result child${children.length === 1 ? \"\" : \"ren\"}.`,\n\t\t);\n\tconst child = index !== undefined ? children[index] : undefined;\n\tconst output =\n\t\tindex !== undefined\n\t\t\t? (child?.output ??\n\t\t\t\tchild?.summary ??\n\t\t\t\t(children.length === 1 ? (data.output ?? data.summary) : undefined) ??\n\t\t\t\t\"\")\n\t\t\t: (data.output ?? data.summary ?? \"\");\n\tconst transcriptLines = output.split(/\\r?\\n/).slice(-lineLimit);\n\tconst sessionFile = child?.sessionFile ?? data.sessionFile;\n\tconst lines = [\n\t\t`Run: ${runId}`,\n\t\t`State: ${data.state ?? (data.success ? \"complete\" : \"failed\")}`,\n\t\tindex !== undefined && child ? `Child: ${index} (${child.agent ?? \"subagent\"})` : undefined,\n\t\tindex === undefined && children.length > 1\n\t\t\t? `Tip: pass index to inspect a specific child transcript (${children.map((candidate, childIndex) => `${childIndex}=${candidate.agent ?? \"subagent\"}`).join(\", \")}).`\n\t\t\t: undefined,\n\t].filter((line): line is string => Boolean(line));\n\tappendKnownArtifacts(lines, { outputPaths: [], sessionFile, resultPath });\n\tappendTranscriptBody(\n\t\tlines,\n\t\t\"Result transcript tail\",\n\t\ttranscriptLines.filter((line) => line.trim()),\n\t\toutput.split(/\\r?\\n/).length > lineLimit,\n\t);\n\treturn lines.join(\"\\n\");\n}\n"]}