/** * Single owner of the one blocking overlay (V2-071..076, PICK-002). * * Holds the overlay's data (picker options, confirm prompt, pager body) and * coordinates with `FocusController`'s context stack so opening while one is * already active is rejected (nested-action prevention) and closing restores * whichever base region had focus (focus restoration) — both for free from * `FocusController`'s existing single-slot design, not reimplemented here. */ import type { FocusController } from "./focus-controller.js"; import type { PickerOption } from "../rendering/picker-filter.js"; import type { ArtifactPagerSource } from "../rendering/artifact-pager-source.js"; export type ConfirmKind = "tool" | "pentest" | "reset" | "continue" | "plan" | "switch"; /** Rich outcome for plan-ready confirm (not a boolean y/n only). */ export type PlanConfirmResult = "implement" | "discard" | "suggest" | "dismiss"; export interface ConfirmRequest { readonly kind: ConfirmKind; readonly prompt: string; /** * Absolute or user path the operator can preview with `v` before approving * (used for fs.delete so they can inspect the file first). */ readonly viewPath?: string | undefined; } export interface PickerRowAction { readonly chord: string; readonly hint: string; } export interface PickerRequest { readonly title: string; readonly options: readonly PickerOption[]; readonly rowAction?: PickerRowAction | undefined; readonly searchDescription?: boolean | undefined; readonly twoLine?: boolean | undefined; /** * History-oriented chrome: larger panel, session badges, clearer filter * line, and description-aware search. */ readonly historyStyle?: boolean | undefined; } export interface SecretRequestView { readonly title: string; readonly prompt: string; /** * Show the typed value instead of bullets. For inputs that are not secret * but reuse this modal — a Modal endpoint URL or an Ollama host — where * masking a long URL just hides typos. */ readonly reveal?: boolean | undefined; } /** Multi-row engagement scope editor (/scope). */ export interface ScopeEditorRequest { /** Existing authorized targets (pre-fill). */ readonly initialTargets: readonly string[]; } /** One existing key shown masked in the multi-key editor (/set). */ export interface KeysEditorSlotView { readonly id: string; readonly masked: string; readonly disabled?: boolean | undefined; } /** Multi-row API key editor for a single LLM provider. */ export interface KeysEditorRequest { readonly provider: string; readonly initialKeys: readonly KeysEditorSlotView[]; /** Index of the currently-active (sticky) key for rotation. */ readonly activeIndex?: number | undefined; /** * Singular noun for what a row holds. Defaults to "API key"; endpoint editors * pass "endpoint URL" so the same overlay can manage base URLs. */ readonly itemLabel?: string | undefined; /** Short heading chip. Defaults to "KEYS". */ readonly heading?: string | undefined; } /** * Save rows: empty `value` + `slotId` keeps the stored secret; non-empty value * is a new/replacement plaintext key. Reset clears all keys for the provider. */ export type KeysEditorAnswer = { readonly action: "save"; readonly rows: readonly { slotId?: string; value: string; disabled?: boolean; }[]; readonly activeIndex?: number | undefined; } | { readonly action: "reset"; }; export interface PromptActionsRequest { readonly prompt: string; readonly onResend: () => void; } export type OverlayState = { readonly kind: "none"; } | { readonly kind: "picker"; readonly request: PickerRequest; readonly onSelect: (value: string) => void; readonly onRowAction?: ((value: string) => void) | undefined; } | { readonly kind: "confirm"; readonly request: ConfirmRequest; /** Boolean for tool/pentest/etc.; plan uses PlanConfirmResult via answerPlanConfirm. */ readonly resolve: (ok: boolean | PlanConfirmResult) => void; readonly onViewPlan?: (() => void) | undefined; /** Open file preview pager without resolving the confirm (fs.delete `v`). */ readonly onViewFile?: (() => void) | undefined; readonly planResolve?: ((result: PlanConfirmResult) => void) | undefined; } | { readonly kind: "secret"; readonly request: SecretRequestView; readonly resolve: (value: string | undefined) => void; } | { readonly kind: "scope-editor"; readonly request: ScopeEditorRequest; /** undefined = cancel; [] = clear/disable; non-empty = save targets. */ readonly resolve: (targets: string[] | undefined) => void; } | { readonly kind: "keys-editor"; readonly request: KeysEditorRequest; /** undefined = cancel. */ readonly resolve: (answer: KeysEditorAnswer | undefined) => void; } | { readonly kind: "prompt-actions"; readonly request: PromptActionsRequest; } | { readonly kind: "pager"; readonly title: string; readonly body: string; readonly source?: ArtifactPagerSource | undefined; /** Path for syntax highlighting in file-diff modals. */ readonly highlightPath?: string | undefined; /** * Markdown rendering: force for help/shortcuts/plan, auto for mixed * bodies, plain to disable. Default auto. */ readonly markdown?: "auto" | "force" | "plain" | undefined; } | { readonly kind: "jobs"; }; export type OverlayListener = () => void; export declare class OverlayController { private readonly focus; private state; /** Confirm suspended under a plan-detail pager (classic TUI: confirm chrome + pager overlay). */ private suspended; private readonly listeners; private closeFocus; constructor(focus: FocusController); getState(): OverlayState; isOpen(): boolean; subscribe(listener: OverlayListener): () => void; openPicker(request: PickerRequest, onSelect: (value: string) => void, onRowAction?: (value: string) => void): boolean; replacePickerOptions(options: readonly PickerOption[]): void; /** * Opens a pager. Allowed over an open plan confirm so "P" can show full * plan detail without resolving the confirm (F-021); closing the pager * restores the suspended confirm. Any other open overlay is still rejected. */ openPager(title: string, body: string, source?: ArtifactPagerSource, highlightPath?: string, markdown?: "auto" | "force" | "plain"): boolean; openJobs(): boolean; openPromptActions(request: PromptActionsRequest): boolean; /** Resolves `false` if a blocking overlay was already open rather than hanging. */ openConfirm(request: ConfirmRequest, onViewPlan?: () => void, onViewFile?: () => void): Promise; /** * Plan-ready confirm with implement / discard / suggest / dismiss. * Resolves `dismiss` if another overlay was already open. */ openPlanConfirm(request: ConfirmRequest, onViewPlan?: () => void): Promise; /** Resolves `undefined` if a blocking overlay was already open rather than hanging. */ openSecret(request: SecretRequestView): Promise; /** * Multi-input scope editor. Resolves: * - `undefined` cancel / overlay busy * - `[]` clear (scoping disabled) * - non-empty string[] save those targets */ openScopeEditor(request: ScopeEditorRequest): Promise; answerScope(targets: string[] | undefined): void; /** * Multi-row API key editor. Resolves: * - `undefined` cancel / overlay busy * - `{ action: "reset" }` clear all keys * - `{ action: "save", rows }` keep/replace/add keys */ openKeysEditor(request: KeysEditorRequest): Promise; answerKeys(answer: KeysEditorAnswer | undefined): void; answerConfirm(ok: boolean): void; answerPlanConfirm(result: PlanConfirmResult): void; answerSecret(value: string | undefined): void; /** * Dismiss a blocking secret (or confirm) so turn abort / Ctrl+C never leaves * a stuck password UI while the agent has already cancelled. * Returns true if something was dismissed. */ cancelBlockingPrompt(): boolean; /** The picker's own `onSelect` decides whether/when to close (e.g. a * provider pick may chain into a secret prompt instead of closing). */ selectPicker(value: string): void; actOnPickerRow(value: string): void; close(): void; dispose(): void; private activeConfirm; private open; private suspendUnder; private restoreSuspended; private forceClose; private notify; }