import type { Theme } from "@earendil-works/pi-coding-agent"; import type { Component, Focusable, TUI } from "@earendil-works/pi-tui"; import type { OrchestratorTree } from "../orchestrator-tree.js"; import type { AskRender, AskUserRequest } from "../ask-user-render.js"; import type { NodeViewModel } from "./view-model.js"; export interface DashboardState { /** Node ID the cursor is hovering on. */ cursorId: string; /** Orchestrator node IDs whose children are expanded in the tree. */ expanded: Set; /** Which panel receives keyboard focus. */ focusPanel: "tree" | "detail"; /** Whether the detail panel's prompt section is expanded. */ promptExpanded: boolean; /** Activity-log expansion (ctrl+o): false = clamp long entries to a small * row budget, true = render full content. Display policy lives entirely in * the view — the model stores log entries verbatim. */ logExpanded: boolean; /** Node ID targeted for cancellation confirmation, if active. */ cancelTargetId: string | null; /** Scroll offset for the detail panel (0 = top). */ detailScroll: number; /** Token-detail view (toggled with `d`): when true, token footers also show * cumulative cache-read (⇪). Default false — the headline leads with peak * context so cumulative cache-read (a per-turn re-read billing quantity that * balloons to tens of millions) doesn't masquerade as consumption. */ showTokenDetail: boolean; } /** * A subagent `ask_user` marshalled to the dashboard overlay (Plan 16 Slice 3). * The overlay renders the prompt in its own z-layer (pi's editor-slot dialogs * render beneath it), captures input, and resolves `resolve` with the answer. * `choiceIndex`/`textBuf` hold the transient interaction state for the panel. */ interface PendingAsk { req: AskUserRequest; resolve: (r: AskRender) => void; choiceIndex: number; textBuf: string; } export declare class DashboardController { private tree; private vm; private state; private onInvalidate?; private refreshTimer?; private disposed; private readonly readOnly; private _handlers; /** A subagent ask_user marshalled to this overlay, or null (Plan 16 Slice 3). */ private pendingAsk; constructor(tree: OrchestratorTree, initialCursorId?: string, opts?: { readOnly?: boolean; }); setOnInvalidate(cb: () => void): void; /** Rebuild the ViewModel from the live model. Called on every model event. */ private rebuildViewModel; /** Get a node from the current ViewModel. */ getNode(id: string): NodeViewModel | undefined; /** Get a node's children from the current ViewModel. */ getChildren(id: string): NodeViewModel[]; /** Get the progress (completed/total leaves) for a subtree. */ getSubtreeProgress(id: string): { completed: number; total: number; }; /** Aggregate token usage across all active roots. */ getAggregateUsage(): { input: number; output: number; cacheRead: number; context: number; }; /** Aggregate compression across all active roots. */ getAggregateCompression(): { calls: number; tokensSaved: number; }; /** Model/provider from the first running root, if any. */ getActiveModel(): { provider?: string; model?: string; } | undefined; /** Check whether any active root is running or cancelling. */ private hasRunningNodes; getVisibleNodes(): string[]; handleInput(data: string): void; private handleTreeInput; private handleDetailInput; /** * Present a subagent's ask_user in this overlay and resolve when the user * answers or dismisses. Registered with AskBroker by DashboardComponent so * broker asks route here while the dashboard is mounted, instead of pi's * editor-slot dialog (which renders beneath the overlay). Serialised upstream * by AskBroker; the extra guard here is defence in depth. */ presentAsk(req: AskUserRequest, signal?: AbortSignal): Promise; /** The active ask prompt (for the view), or null. */ getPendingAsk(): PendingAsk | null; /** Settle the active ask and clear it. */ private resolveAsk; /** * Input while an ask prompt is active. Enter/y/n resolve a confirm; ↑↓+Enter * a choice; typing + Enter a text answer. A dismissal never returns the * default — it resolves `{ ok: false }` so the tool reports isError, never a * fabricated answer (Iron Law 7; forge#114). ESC is NOT handled here — the * DashboardComponent intercepts ESC to close the whole overlay, which * disposes and cancels the ask (also a non-answer). */ private handleAskInput; private handleCancelConfirm; /** Cancel the node in the OrchestratorTree for immediate visual * feedback, and propagate cancellation through SessionRegistry * for actual pipeline abort. Reads from VM; writes to model. */ private cancelNodeAndSessions; private moveCursor; private activateCursor; private toggleExpand; private collapseCursor; /** Replay mode: read-only dashboards (archived trees) never cancel. */ isReadOnly(): boolean; private startCancel; private ensureAncestorsExpanded; autoExpandNewNode(id: string): void; private ensureRefreshTimer; private stopRefreshTimer; dispose(): void; getState(): DashboardState; } export declare class DashboardComponent implements Component, Focusable { /** IL3: Focusable — pi sets this to true when the overlay has * keyboard focus. Without this, arrow-key and Escape events are * swallowed at the overlay layer. (Lesson from config-TUI commit 07e886f.) */ focused: boolean; private controller; private theme; private tui; private done; constructor(controller: DashboardController, tui: TUI, theme: Theme, done: (result: null) => void); render(width: number): string[]; handleInput(data: string): void; invalidate(): void; dispose(): void; private renderTreePanel; private renderDetailPanel; /** Render a cancel-confirmation dialog overlaid on the dashboard. * The dashboard content stays visible above and below the dialog. * The outer frame borders (│ on each side) are preserved across the * dialog rows so the two-panel layout doesn't visually collapse. * The dialog is centered and accepts y/Enter to confirm or n/Esc to * dismiss. The overlay stays until the user decides — Esc only * dismisses the cancel prompt, not the dashboard. */ /** * Render an active ask_user prompt as a centered modal over the dashboard * (Plan 16 Slice 3). Mirrors overlayCancelConfirm's box + centering so the * frame and outer │ borders stay intact; body varies by input type. All * visible strings route through theme helpers (IL1); every row is clamped to * the dialog width (IL2/IL10 via truncateToWidth/visibleWidth). */ private overlayAsk; private overlayCancelConfirm; private formatMetrics; } export {};