{"version":3,"file":"fleet-status.d.ts","sourceRoot":"","sources":["../../../src/tui/fleet-status.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAUlE,OAAO,KAAK,EAEX,kBAAkB,EAClB,gBAAgB,EAEhB,aAAa,EACb,MAAM,oBAAoB,CAAC;AAG5B,eAAO,MAAM,uBAAuB,0BAA0B,CAAC;AAM/D,KAAK,KAAK,GAAG,gBAAgB,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC;AAI7C,KAAK,gBAAgB,GAAG;IACvB,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,cAAc,CAAC,EAAE,gBAAgB,EAAE,CAAC;CACpC,CAAC;AAeF,MAAM,WAAW,kBAAkB;IAClC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,SAAS,CAAC,EAAE,kBAAkB,CAAC;CAC/B;AAED,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,OAAO,GAAG,kBAAkB,CAE5E;AAED,wBAAgB,kBAAkB,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAErD;AAED,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAMvD;AAiGD,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,aAAa,GAAG,gBAAgB,EAAE,CA2GlF;AAED,qBAAa,mBAAmB;IAC/B,OAAO,CAAC,GAAG,CAA+B;IAC1C,OAAO,CAAC,EAAE,CAAqC;IAC/C,OAAO,CAAC,GAAG,CAA6B;IACxC,OAAO,CAAC,gBAAgB,CAA2B;IACnD,OAAO,CAAC,KAAK,CAA6C;IAC1D,OAAO,CAAC,gBAAgB,CAAS;IACjC,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,WAAW,CAAU;IAC7B,OAAO,CAAC,aAAa,CAAS;IAC9B,OAAO,CAAC,aAAa,CAAM;IAC3B,OAAO,CAAC,OAAO,CAA0B;IACzC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAgB;IACtC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAA4C;IAC1E,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAS;IACtC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAqB;IAE/C,YACC,KAAK,EAAE,aAAa,EACpB,aAAa,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,EACxD,OAAO,GAAE,kBAAuB,EAOhC;IAED,UAAU,CAAC,GAAG,EAAE,gBAAgB,GAAG,IAAI,CAiBtC;IAED,OAAO,IAAI,IAAI,CASd;IAED,OAAO,IAAI,IAAI,CAqDd;IAED,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG;QAAE,OAAO,CAAC,EAAE,OAAO,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,SAAS,CA0DxE;IAED,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,GAAG,MAAM,EAAE,CAqC5C;IAED,OAAO,CAAC,WAAW;IAenB,OAAO,CAAC,eAAe;IAWvB,OAAO,CAAC,MAAM;IAId,OAAO,CAAC,UAAU;IAIlB,OAAO,CAAC,cAAc;IAItB,OAAO,CAAC,UAAU;IAMlB,OAAO,CAAC,cAAc;IAetB,OAAO,CAAC,YAAY;IAoCpB,OAAO,CAAC,kBAAkB;IAY1B,OAAO,CAAC,mBAAmB;CA+B3B","sourcesContent":["import type { ExtensionContext } from \"@lpb-work/pi-coding-agent\";\nimport {\n\ttype EditorComponent,\n\tisKeyRelease,\n\tKey,\n\tmatchesKey,\n\ttruncateToWidth,\n\tvisibleWidth,\n} from \"@lpb-work/pi-tui\";\nimport { formatModelThinking } from \"../shared/formatters.ts\";\nimport type {\n\tAsyncJobStep,\n\tFleetViewPlacement,\n\tNestedRunSummary,\n\tNestedStepSummary,\n\tSubagentState,\n} from \"../shared/types.ts\";\nimport { formatWorkflowJsonPreview } from \"../workflows/scripted-workflow.ts\";\n\nexport const FLEET_STATUS_WIDGET_KEY = \"subagent-fleet-status\";\n\n// Six rows fit the accepted collapsed hierarchy: one owner, four direct leaves, and overflow.\nconst MAX_AGENT_ROWS = 6;\nconst REFRESH_MS = 500;\n\ntype Theme = ExtensionContext[\"ui\"][\"theme\"];\ntype FleetStatusTui = {\n\trequestRender(): void;\n};\ntype FleetStatusEntry = {\n\tkey: string;\n\tagent: string;\n\tmodelThinking?: string;\n\tdescription?: string;\n\tstartedAt: number;\n\ttokens: number;\n\tstate: string;\n\tnestedChildren?: NestedRunSummary[];\n};\n\ntype FleetNestedRow = {\n\tname: string;\n\tstate: NestedRunSummary[\"state\"] | NestedStepSummary[\"status\"];\n\tmodelThinking?: string;\n\tactivity?: string;\n\tstartedAt?: number;\n\toverflow?: number;\n};\n\ntype FleetTreeRow =\n\t| { kind: \"owner\"; entry: FleetStatusEntry }\n\t| { kind: \"nested\"; ownerKey: string; row: FleetNestedRow; last: boolean };\n\nexport interface FleetStatusOptions {\n\trefreshMs?: number;\n\tmaxAgentRows?: number;\n\tplacement?: FleetViewPlacement;\n}\n\nexport function resolveFleetViewPlacement(value: unknown): FleetViewPlacement {\n\treturn value === \"aboveEditor\" ? \"aboveEditor\" : \"belowEditor\";\n}\n\nexport function formatFleetElapsed(ms: number): string {\n\treturn `${Math.max(0, Math.round(ms / 1000))}s`;\n}\n\nexport function formatFleetTokens(count: number): string {\n\tlet compact: string;\n\tif (count >= 1_000_000) compact = `${(count / 1_000_000).toFixed(1)}M`;\n\telse if (count >= 1_000) compact = `${(count / 1_000).toFixed(1)}k`;\n\telse compact = `${Math.max(0, Math.round(count))}`;\n\treturn `↓ ${compact} tokens`;\n}\n\nfunction rightAlign(left: string, right: string, width: number): string {\n\tconst rightWidth = visibleWidth(right);\n\tconst maxLeftWidth = Math.max(0, width - rightWidth - 1);\n\tconst leftClamped = truncateToWidth(left, maxLeftWidth);\n\tconst gap = Math.max(1, width - visibleWidth(leftClamped) - rightWidth);\n\treturn truncateToWidth(`${leftClamped}${\" \".repeat(gap)}${right}`, width);\n}\n\nfunction isActiveState(value: string): boolean {\n\treturn value === \"running\" || value === \"queued\" || value === \"pending\";\n}\n\nfunction nestedRunLabel(run: NestedRunSummary): string {\n\tif (run.agent) return run.agent;\n\tif (run.agents?.length)\n\t\treturn run.agents.length === 1\n\t\t\t? run.agents[0]!\n\t\t\t: `${run.agents.slice(0, 2).join(\", \")}${run.agents.length > 2 ? ` +${run.agents.length - 2}` : \"\"}`;\n\treturn run.id;\n}\n\nfunction nestedActivity(node: NestedRunSummary | NestedStepSummary): string | undefined {\n\tif (node.currentTool) return `tool ${node.currentTool}`;\n\tif (node.currentPath) return node.currentPath.split(/[\\\\/]/).at(-1);\n\tif (node.activityState === \"needs_attention\") return \"needs attention\";\n\tif (node.activityState === \"active_long_running\") return \"long-running\";\n\treturn undefined;\n}\n\nfunction nestedStatusGlyph(state: FleetNestedRow[\"state\"], theme: Theme): string {\n\tif (state === \"running\") return theme.fg(\"accent\", \"●\");\n\tif (state === \"queued\" || state === \"pending\") return theme.fg(\"muted\", \"◦\");\n\tif (state === \"complete\" || state === \"completed\") return theme.fg(\"success\", \"✓\");\n\tif (state === \"failed\" || state === \"rejected\") return theme.fg(\"error\", \"✗\");\n\treturn theme.fg(\"warning\", \"■\");\n}\n\nfunction nestedFleetRows(children: NestedRunSummary[] | undefined): FleetNestedRow[] {\n\tconst rows: FleetNestedRow[] = [];\n\tfor (const child of children ?? []) {\n\t\tconst steps = child.mode === \"parallel\" || child.mode === \"chain\" ? (child.steps ?? []) : [];\n\t\tif (steps.length > 0) {\n\t\t\tfor (const step of steps) {\n\t\t\t\tconst modelThinking = formatModelThinking(step.model, step.thinking) || undefined;\n\t\t\t\tconst activity = nestedActivity(step);\n\t\t\t\trows.push({\n\t\t\t\t\tname: step.agent,\n\t\t\t\t\tstate: step.status,\n\t\t\t\t\t...(modelThinking ? { modelThinking } : {}),\n\t\t\t\t\t...(activity ? { activity } : {}),\n\t\t\t\t\t...(step.startedAt !== undefined ? { startedAt: step.startedAt } : {}),\n\t\t\t\t});\n\t\t\t}\n\t\t\tcontinue;\n\t\t}\n\t\tconst modelThinking = formatModelThinking(child.model, child.thinking) || undefined;\n\t\tconst activity = nestedActivity(child);\n\t\trows.push({\n\t\t\tname: nestedRunLabel(child),\n\t\t\tstate: child.state,\n\t\t\t...(modelThinking ? { modelThinking } : {}),\n\t\t\t...(activity ? { activity } : {}),\n\t\t\t...(child.startedAt !== undefined ? { startedAt: child.startedAt } : {}),\n\t\t});\n\t}\n\tconst visible = rows.slice(0, 4);\n\tif (rows.length > visible.length)\n\t\tvisible.push({\n\t\t\tname: `… +${rows.length - visible.length} more nested leaves`,\n\t\t\tstate: \"complete\",\n\t\t\toverflow: rows.length - visible.length,\n\t\t});\n\treturn visible;\n}\n\nfunction fleetTreeRows(entries: FleetStatusEntry[]): FleetTreeRow[] {\n\tconst rows: FleetTreeRow[] = [];\n\tfor (const entry of entries) {\n\t\trows.push({ kind: \"owner\", entry });\n\t\tconst nested = nestedFleetRows(entry.nestedChildren);\n\t\tfor (const [index, row] of nested.entries())\n\t\t\trows.push({ kind: \"nested\", ownerKey: entry.key, row, last: index === nested.length - 1 });\n\t}\n\treturn rows;\n}\n\nfunction isStaleExtensionContextError(error: unknown): boolean {\n\t// Pi currently exposes stale contexts as plain Errors without a stable code or subtype.\n\treturn (\n\t\terror instanceof Error &&\n\t\t(error.message.includes(\"This extension ctx is stale\") ||\n\t\t\terror.message.includes(\"Extension context no longer active\"))\n\t);\n}\n\nexport function collectFleetStatusEntries(state: SubagentState): FleetStatusEntry[] {\n\tconst entries: FleetStatusEntry[] = [];\n\tfor (const control of state.foregroundControls.values()) {\n\t\tif (control.activeChildren) {\n\t\t\tfor (const child of [...control.activeChildren.values()].sort((left, right) => left.index - right.index)) {\n\t\t\t\tconst modelThinking = formatModelThinking(child.model, child.thinking) || undefined;\n\t\t\t\tentries.push({\n\t\t\t\t\tkey: `foreground-active:${control.runId}:${child.index}`,\n\t\t\t\t\tagent: child.agent,\n\t\t\t\t\t...(modelThinking ? { modelThinking } : {}),\n\t\t\t\t\tdescription: child.description,\n\t\t\t\t\tstartedAt: child.startedAt,\n\t\t\t\t\ttokens: child.tokens ?? 0,\n\t\t\t\t\tstate: \"running\",\n\t\t\t\t\t...((control.nestedChildren?.filter((nested) => nested.parentStepIndex === child.index).length ?? 0) > 0\n\t\t\t\t\t\t? {\n\t\t\t\t\t\t\t\tnestedChildren: control.nestedChildren!.filter(\n\t\t\t\t\t\t\t\t\t(nested) => nested.parentStepIndex === child.index,\n\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t: control.activeChildren.size === 1 && control.nestedChildren?.length\n\t\t\t\t\t\t\t? { nestedChildren: control.nestedChildren }\n\t\t\t\t\t\t\t: {}),\n\t\t\t\t});\n\t\t\t}\n\t\t\tcontinue;\n\t\t}\n\t\tconst modelThinking = formatModelThinking(control.model, control.thinking) || undefined;\n\t\tentries.push({\n\t\t\tkey: `foreground-active:${control.runId}:${control.currentIndex ?? 0}`,\n\t\t\tagent: control.currentAgent ?? control.mode,\n\t\t\t...(modelThinking ? { modelThinking } : {}),\n\t\t\tdescription: control.description,\n\t\t\tstartedAt: control.startedAt,\n\t\t\ttokens: control.tokens ?? 0,\n\t\t\tstate: \"running\",\n\t\t\t...(control.nestedChildren?.length ? { nestedChildren: control.nestedChildren } : {}),\n\t\t});\n\t}\n\n\tfor (const job of state.asyncJobs.values()) {\n\t\tif (!isActiveState(job.status)) continue;\n\t\tconst startedAt = job.startedAt ?? job.updatedAt ?? Date.now();\n\t\tif (job.mode === \"workflow\") {\n\t\t\tconst latestEmit = job.workflow?.emits?.length\n\t\t\t\t? formatWorkflowJsonPreview(job.workflow.emits.at(-1), 120)\n\t\t\t\t: undefined;\n\t\t\tentries.push({\n\t\t\t\tkey: `async:${job.asyncId}`,\n\t\t\t\tagent: \"workflow\",\n\t\t\t\tdescription: latestEmit !== undefined ? `latest emit: ${latestEmit}` : job.description,\n\t\t\t\tstartedAt,\n\t\t\t\ttokens: job.totalTokens?.total ?? 0,\n\t\t\t\tstate: job.status,\n\t\t\t\t...(job.nestedChildren?.length ? { nestedChildren: job.nestedChildren } : {}),\n\t\t\t});\n\t\t\tcontinue;\n\t\t}\n\t\tconst steps: AsyncJobStep[] | undefined = job.steps?.length\n\t\t\t? job.steps\n\t\t\t: job.agents?.map((agent, index) => {\n\t\t\t\t\tconst pending =\n\t\t\t\t\t\tjob.status === \"queued\" ||\n\t\t\t\t\t\t(job.mode === \"chain\" && !job.activeParallelGroup && index !== (job.currentStep ?? 0));\n\t\t\t\t\treturn { agent, index, status: pending ? \"pending\" : \"running\" };\n\t\t\t\t});\n\t\tif (!steps?.length) {\n\t\t\tentries.push({\n\t\t\t\tkey: `async:${job.asyncId}`,\n\t\t\t\tagent: job.mode ?? \"subagent\",\n\t\t\t\tdescription: job.description,\n\t\t\t\tstartedAt,\n\t\t\t\ttokens: job.totalTokens?.total ?? 0,\n\t\t\t\tstate: job.status,\n\t\t\t\t...(job.nestedChildren?.length ? { nestedChildren: job.nestedChildren } : {}),\n\t\t\t});\n\t\t\tcontinue;\n\t\t}\n\t\tfor (const [offset, step] of steps.entries()) {\n\t\t\tif (!isActiveState(step.status)) continue;\n\t\t\tconst index = step.index ?? offset;\n\t\t\tif (\n\t\t\t\tstep.status === \"pending\" &&\n\t\t\t\tjob.mode === \"chain\" &&\n\t\t\t\t!job.activeParallelGroup &&\n\t\t\t\tindex !== (job.currentStep ?? 0)\n\t\t\t)\n\t\t\t\tcontinue;\n\t\t\tconst modelThinking = formatModelThinking(step.model, step.thinking) || undefined;\n\t\t\tentries.push({\n\t\t\t\tkey: `async:${job.asyncId}:${index}`,\n\t\t\t\tagent: step.label ? `${step.label} (${step.agent})` : step.agent,\n\t\t\t\t...(modelThinking ? { modelThinking } : {}),\n\t\t\t\tdescription: step.description ?? job.description,\n\t\t\t\tstartedAt: step.startedAt ?? startedAt,\n\t\t\t\ttokens: step.tokens?.total ?? (steps.length === 1 ? (job.totalTokens?.total ?? 0) : 0),\n\t\t\t\tstate: step.status,\n\t\t\t\t...((step.children?.length ?? 0) > 0\n\t\t\t\t\t? { nestedChildren: step.children }\n\t\t\t\t\t: job.nestedChildren?.filter((nested) => nested.parentStepIndex === index).length\n\t\t\t\t\t\t? { nestedChildren: job.nestedChildren.filter((nested) => nested.parentStepIndex === index) }\n\t\t\t\t\t\t: {}),\n\t\t\t});\n\t\t}\n\t}\n\n\treturn entries.sort((left, right) => left.startedAt - right.startedAt || left.key.localeCompare(right.key));\n}\n\nexport class SubagentFleetStatus {\n\tprivate ctx: ExtensionContext | undefined;\n\tprivate ui: ExtensionContext[\"ui\"] | undefined;\n\tprivate tui: FleetStatusTui | undefined;\n\tprivate inputUnsubscribe: (() => void) | undefined;\n\tprivate timer: ReturnType<typeof setInterval> | undefined;\n\tprivate widgetRegistered = false;\n\tprivate active = false;\n\tprivate selectedKey = \"main\";\n\tprivate inspectorOpen = false;\n\tprivate lastRenderKey = \"\";\n\tprivate entries: FleetStatusEntry[] = [];\n\tprivate readonly state: SubagentState;\n\tprivate readonly openInspector: (itemKey: string) => Promise<void> | void;\n\tprivate readonly refreshMs: number;\n\tprivate readonly maxAgentRows: number;\n\tprivate readonly placement: FleetViewPlacement;\n\n\tconstructor(\n\t\tstate: SubagentState,\n\t\topenInspector: (itemKey: string) => Promise<void> | void,\n\t\toptions: FleetStatusOptions = {},\n\t) {\n\t\tthis.state = state;\n\t\tthis.openInspector = openInspector;\n\t\tthis.refreshMs = options.refreshMs ?? REFRESH_MS;\n\t\tthis.maxAgentRows = options.maxAgentRows ?? MAX_AGENT_ROWS;\n\t\tthis.placement = options.placement ?? \"belowEditor\";\n\t}\n\n\tsetContext(ctx: ExtensionContext): void {\n\t\tif (!ctx.hasUI) return;\n\t\tconst ui = ctx.ui;\n\t\tif (this.ui === ui) {\n\t\t\tthis.ctx = ctx;\n\t\t\tthis.refresh();\n\t\t\treturn;\n\t\t}\n\t\tthis.clearUiRegistration();\n\t\tthis.ctx = ctx;\n\t\tthis.ui = ui;\n\t\tif (typeof ui.onTerminalInput === \"function\") {\n\t\t\tthis.inputUnsubscribe = ui.onTerminalInput((data) => this.handleKey(data));\n\t\t}\n\t\tthis.timer = setInterval(() => this.refresh(), this.refreshMs);\n\t\tthis.timer.unref?.();\n\t\tthis.refresh();\n\t}\n\n\tdispose(): void {\n\t\tthis.clearUiRegistration();\n\t\tthis.ctx = undefined;\n\t\tthis.ui = undefined;\n\t\tthis.entries = [];\n\t\tthis.active = false;\n\t\tthis.selectedKey = \"main\";\n\t\tthis.inspectorOpen = false;\n\t\tthis.lastRenderKey = \"\";\n\t}\n\n\trefresh(): void {\n\t\tconst ctx = this.getActiveUiContext();\n\t\tif (!ctx) return;\n\t\tthis.entries = collectFleetStatusEntries(this.state);\n\t\tthis.clampSelection();\n\t\tif (this.inspectorOpen || this.state.fleetInspectorOpen) {\n\t\t\tthis.lastRenderKey = \"\";\n\t\t\tif (this.widgetRegistered) {\n\t\t\t\tctx.ui.setWidget(FLEET_STATUS_WIDGET_KEY, undefined);\n\t\t\t\tthis.widgetRegistered = false;\n\t\t\t\tthis.tui = undefined;\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\t\tif (this.entries.length === 0) {\n\t\t\tthis.active = false;\n\t\t\tthis.selectedKey = \"main\";\n\t\t\tthis.lastRenderKey = \"\";\n\t\t\tif (this.widgetRegistered) {\n\t\t\t\tctx.ui.setWidget(FLEET_STATUS_WIDGET_KEY, undefined);\n\t\t\t\tthis.widgetRegistered = false;\n\t\t\t\tthis.tui = undefined;\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\tconst renderKey = this.getRenderKey();\n\t\tif (!this.widgetRegistered) {\n\t\t\tctx.ui.setWidget(\n\t\t\t\tFLEET_STATUS_WIDGET_KEY,\n\t\t\t\t(tui, theme) => {\n\t\t\t\t\tthis.tui = tui;\n\t\t\t\t\treturn {\n\t\t\t\t\t\trender: (width: number) => this.render(width, theme),\n\t\t\t\t\t\tinvalidate: () => {\n\t\t\t\t\t\t\tthis.lastRenderKey = \"\";\n\t\t\t\t\t\t},\n\t\t\t\t\t\tdispose: () => {\n\t\t\t\t\t\t\tif (this.tui !== tui) return;\n\t\t\t\t\t\t\tthis.widgetRegistered = false;\n\t\t\t\t\t\t\tthis.tui = undefined;\n\t\t\t\t\t\t},\n\t\t\t\t\t};\n\t\t\t\t},\n\t\t\t\t{ placement: this.placement },\n\t\t\t);\n\t\t\tthis.widgetRegistered = true;\n\t\t\tthis.lastRenderKey = renderKey;\n\t\t\treturn;\n\t\t}\n\t\tif (renderKey === this.lastRenderKey) return;\n\t\tthis.lastRenderKey = renderKey;\n\t\tthis.tui?.requestRender();\n\t}\n\n\thandleKey(data: string): { consume?: boolean; data?: string } | undefined {\n\t\tconst ctx = this.getActiveUiContext();\n\t\tif (!ctx || this.entries.length === 0 || isKeyRelease(data)) return undefined;\n\t\tif (this.inspectorOpen) return undefined;\n\t\tif (!this.editorHasFocus()) {\n\t\t\tif (this.active) this.deactivate();\n\t\t\treturn undefined;\n\t\t}\n\n\t\tif (!this.active) {\n\t\t\tconst activates = matchesKey(data, \"down\") || matchesKey(data, \"left\");\n\t\t\tif (!activates || ctx.ui.getEditorText() !== \"\") return undefined;\n\t\t\tthis.active = true;\n\t\t\tthis.selectedKey = \"main\";\n\t\t\tthis.refresh();\n\t\t\treturn { consume: true };\n\t\t}\n\n\t\tconst roster = this.rosterKeys();\n\t\tconst selectedIndex = Math.max(0, roster.indexOf(this.selectedKey));\n\t\tif (matchesKey(data, \"down\") || matchesKey(data, \"j\")) {\n\t\t\tthis.selectedKey = roster[Math.min(roster.length - 1, selectedIndex + 1)] ?? \"main\";\n\t\t\tthis.refresh();\n\t\t\treturn { consume: true };\n\t\t}\n\t\tif (matchesKey(data, \"up\") || matchesKey(data, \"k\")) {\n\t\t\tif (selectedIndex === 0) {\n\t\t\t\tthis.deactivate();\n\t\t\t\treturn { consume: true };\n\t\t\t}\n\t\t\tthis.selectedKey = roster[selectedIndex - 1] ?? \"main\";\n\t\t\tthis.refresh();\n\t\t\treturn { consume: true };\n\t\t}\n\t\tif (matchesKey(data, \"escape\")) {\n\t\t\tthis.deactivate();\n\t\t\treturn { consume: true };\n\t\t}\n\t\tif (matchesKey(data, Key.enter)) {\n\t\t\tif (this.selectedKey === \"main\") {\n\t\t\t\tthis.deactivate();\n\t\t\t\treturn { consume: true };\n\t\t\t}\n\t\t\tthis.inspectorOpen = true;\n\t\t\tthis.refresh();\n\t\t\tconst selectedKey = this.selectedKey;\n\t\t\tvoid Promise.resolve()\n\t\t\t\t.then(() => this.openInspector(selectedKey))\n\t\t\t\t.catch((error) => ctx.ui.notify(error instanceof Error ? error.message : String(error), \"error\"))\n\t\t\t\t.finally(() => {\n\t\t\t\t\tthis.inspectorOpen = false;\n\t\t\t\t\tthis.refresh();\n\t\t\t\t});\n\t\t\treturn { consume: true };\n\t\t}\n\n\t\tthis.deactivate();\n\t\treturn undefined;\n\t}\n\n\trender(width: number, theme: Theme): string[] {\n\t\tif (this.entries.length === 0) return [];\n\t\tif (!this.active) {\n\t\t\tconst tokens = this.entries.reduce((total, entry) => total + entry.tokens, 0);\n\t\t\tconst label = `${this.entries.length} active ${this.entries.length === 1 ? \"agent\" : \"agents\"}`;\n\t\t\treturn [\n\t\t\t\ttruncateToWidth(\n\t\t\t\t\t`  ${theme.fg(\"muted\", label)} · ${theme.fg(\"dim\", `${formatFleetTokens(tokens)} · ↓/← to inspect`)}`,\n\t\t\t\t\twidth,\n\t\t\t\t),\n\t\t\t];\n\t\t}\n\t\tconst roster = this.rosterKeys();\n\t\tconst selectedIndex = Math.max(0, roster.indexOf(this.selectedKey));\n\t\tconst lines = [truncateToWidth(`  ${theme.fg(\"dim\", \"↑↓/jk select · enter inspect · esc back\")}`, width), \"\"];\n\t\tlines.push(truncateToWidth(`  ${this.bullet(0, selectedIndex, theme)} main`, width));\n\n\t\tconst tree = fleetTreeRows(this.entries);\n\t\tconst selectedTreeIndex = Math.max(\n\t\t\t0,\n\t\t\ttree.findIndex((row) => row.kind === \"owner\" && row.entry.key === this.selectedKey),\n\t\t);\n\t\tconst visibleCount = Math.min(this.maxAgentRows, tree.length);\n\t\tconst start = selectedTreeIndex < visibleCount ? 0 : selectedTreeIndex - visibleCount + 1;\n\t\tconst hiddenBelow = tree.length - (start + visibleCount);\n\t\tif (start > 0) lines.push(rightAlign(\"\", theme.fg(\"dim\", `↑ ${start} more`), width));\n\t\tfor (let index = start; index < start + visibleCount; index++) {\n\t\t\tconst row = tree[index]!;\n\t\t\tif (row.kind === \"owner\") {\n\t\t\t\tconst ownerIndex = this.entries.findIndex((entry) => entry.key === row.entry.key);\n\t\t\t\tlines.push(this.renderEntry(ownerIndex + 1, selectedIndex, row.entry, width, theme));\n\t\t\t} else {\n\t\t\t\tlines.push(this.renderNestedRow(row.row, row.last, width, theme));\n\t\t\t}\n\t\t}\n\t\tif (hiddenBelow > 0) lines.push(rightAlign(\"\", theme.fg(\"dim\", `↓ ${hiddenBelow} more`), width));\n\t\treturn lines;\n\t}\n\n\tprivate renderEntry(\n\t\trosterIndex: number,\n\t\tselectedIndex: number,\n\t\tentry: FleetStatusEntry,\n\t\twidth: number,\n\t\ttheme: Theme,\n\t): string {\n\t\tconst description = entry.description?.replace(/\\s+/g, \" \").trim();\n\t\tconst agent = entry.modelThinking ? `${entry.agent} (${entry.modelThinking})` : entry.agent;\n\t\tconst left = `  ${this.bullet(rosterIndex, selectedIndex, theme)} ${theme.fg(\"muted\", agent)} · ${entry.state}${description ? `  ${description}` : \"\"}`;\n\t\tconst elapsed = Date.now() - entry.startedAt;\n\t\tconst right = theme.fg(\"dim\", `${formatFleetElapsed(elapsed)} · ${formatFleetTokens(entry.tokens)}`);\n\t\treturn rightAlign(left, right, width);\n\t}\n\n\tprivate renderNestedRow(row: FleetNestedRow, last: boolean, width: number, theme: Theme): string {\n\t\tconst marker = last ? \"└─\" : \"├─\";\n\t\tif (row.overflow !== undefined)\n\t\t\treturn truncateToWidth(`    ${marker} ${theme.fg(\"dim\", `+${row.overflow} nested leaves`)}`, width);\n\t\tconst modelThinking = row.modelThinking ? ` (${row.modelThinking})` : \"\";\n\t\tconst activity = row.activity ? ` · ${row.activity}` : \"\";\n\t\tconst left = `    ${marker} ${nestedStatusGlyph(row.state, theme)} ${theme.fg(\"muted\", `${row.name}${modelThinking}`)} · ${row.state}${activity}`;\n\t\tconst elapsed = row.startedAt !== undefined ? ` · ${formatFleetElapsed(Date.now() - row.startedAt)}` : \"\";\n\t\treturn truncateToWidth(`${left}${theme.fg(\"dim\", elapsed)}`, width);\n\t}\n\n\tprivate bullet(rosterIndex: number, selectedIndex: number, theme: Theme): string {\n\t\treturn rosterIndex === selectedIndex ? theme.fg(\"accent\", \">\") : \" \";\n\t}\n\n\tprivate rosterKeys(): string[] {\n\t\treturn [\"main\", ...this.entries.map((entry) => entry.key)];\n\t}\n\n\tprivate clampSelection(): void {\n\t\tif (!this.rosterKeys().includes(this.selectedKey)) this.selectedKey = \"main\";\n\t}\n\n\tprivate deactivate(): void {\n\t\tthis.active = false;\n\t\tthis.selectedKey = \"main\";\n\t\tthis.refresh();\n\t}\n\n\tprivate editorHasFocus(): boolean {\n\t\t// pi-tui exposes focus mutation but no focus getter, so inspect the focused\n\t\t// component structurally. instanceof is unreliable across jiti module boundaries.\n\t\tconst focused = (this.tui as unknown as { focusedComponent?: unknown } | undefined)?.focusedComponent;\n\t\tif (!focused || typeof focused !== \"object\") return false;\n\t\tconst candidate = focused as Partial<EditorComponent>;\n\t\treturn (\n\t\t\ttypeof candidate.render === \"function\" &&\n\t\t\ttypeof candidate.invalidate === \"function\" &&\n\t\t\ttypeof candidate.handleInput === \"function\" &&\n\t\t\ttypeof candidate.getText === \"function\" &&\n\t\t\ttypeof candidate.setText === \"function\"\n\t\t);\n\t}\n\n\tprivate getRenderKey(): string {\n\t\tconst now = Date.now();\n\t\treturn JSON.stringify({\n\t\t\tactive: this.active,\n\t\t\tselected: this.selectedKey,\n\t\t\tinspectorOpen: this.inspectorOpen,\n\t\t\tentries: this.entries.map((entry) =>\n\t\t\t\tthis.active\n\t\t\t\t\t? [\n\t\t\t\t\t\t\tentry.key,\n\t\t\t\t\t\t\tentry.agent,\n\t\t\t\t\t\t\tentry.state,\n\t\t\t\t\t\t\tentry.modelThinking,\n\t\t\t\t\t\t\tentry.description,\n\t\t\t\t\t\t\tMath.round((now - entry.startedAt) / 1000),\n\t\t\t\t\t\t\tentry.tokens,\n\t\t\t\t\t\t\tentry.nestedChildren?.map((child) => [\n\t\t\t\t\t\t\t\tchild.id,\n\t\t\t\t\t\t\t\tchild.state,\n\t\t\t\t\t\t\t\tchild.model,\n\t\t\t\t\t\t\t\tchild.thinking,\n\t\t\t\t\t\t\t\tchild.lastUpdate,\n\t\t\t\t\t\t\t\tchild.steps?.map((step) => [\n\t\t\t\t\t\t\t\t\tstep.agent,\n\t\t\t\t\t\t\t\t\tstep.status,\n\t\t\t\t\t\t\t\t\tstep.model,\n\t\t\t\t\t\t\t\t\tstep.thinking,\n\t\t\t\t\t\t\t\t\tstep.lastActivityAt,\n\t\t\t\t\t\t\t\t]),\n\t\t\t\t\t\t\t]),\n\t\t\t\t\t\t]\n\t\t\t\t\t: [entry.key, entry.state, entry.tokens],\n\t\t\t),\n\t\t});\n\t}\n\n\tprivate getActiveUiContext(): ExtensionContext | undefined {\n\t\tconst ctx = this.ctx;\n\t\tif (!ctx) return undefined;\n\t\ttry {\n\t\t\treturn ctx.hasUI ? ctx : undefined;\n\t\t} catch (error) {\n\t\t\tif (!isStaleExtensionContextError(error)) throw error;\n\t\t\tthis.clearUiRegistration();\n\t\t\treturn undefined;\n\t\t}\n\t}\n\n\tprivate clearUiRegistration(): void {\n\t\tif (this.timer) clearInterval(this.timer);\n\t\tthis.timer = undefined;\n\n\t\tconst inputUnsubscribe = this.inputUnsubscribe;\n\t\tconst ui = this.ui;\n\t\tconst widgetRegistered = this.widgetRegistered;\n\t\tthis.inputUnsubscribe = undefined;\n\t\tthis.ctx = undefined;\n\t\tthis.ui = undefined;\n\t\tthis.widgetRegistered = false;\n\t\tthis.tui = undefined;\n\n\t\tconst cleanupErrors: unknown[] = [];\n\t\ttry {\n\t\t\tinputUnsubscribe?.();\n\t\t} catch (error) {\n\t\t\tif (!isStaleExtensionContextError(error)) cleanupErrors.push(error);\n\t\t}\n\t\tif (ui && widgetRegistered) {\n\t\t\ttry {\n\t\t\t\tui.setWidget(FLEET_STATUS_WIDGET_KEY, undefined);\n\t\t\t} catch (error) {\n\t\t\t\tif (!isStaleExtensionContextError(error)) cleanupErrors.push(error);\n\t\t\t}\n\t\t}\n\t\tif (cleanupErrors.length === 1) throw cleanupErrors[0];\n\t\tif (cleanupErrors.length > 1) {\n\t\t\tthrow new AggregateError(cleanupErrors, \"Failed to clean up FleetView UI registration\");\n\t\t}\n\t}\n}\n"]}