/** * Inline ask question component. * Shows a question with either selectable options or free-text input * directly in the conversation flow instead of as an overlay dialog. * * Supports three lifecycle phases: * 1. **Streaming** — Created early by handleToolInputStart with no args. * As partial JSON arrives via handleToolInputDelta, `updateArgs()` feeds * the question text and option labels into the bordered box progressively. * 2. **Active** — When handleAskQuestion fires, `activate()` wires up the * interactive SelectList / Input and the submit/cancel callbacks. * 3. **Answered** — After the user responds, the box freezes with ✓/✗ icons. */ import { Container, Input } from '@earendil-works/pi-tui'; import type { Focusable, TUI } from '@earendil-works/pi-tui'; import type { ChatSpacingKind } from './chat-spacing.js'; import { MultilineInput } from './multiline-input.js'; import { WrappingSelectList } from './wrapping-select-list.js'; /** * Selection mode for option prompts. `single_select` selects one option on Enter * (the default). `multi_select` lets the user toggle several options with Space and * confirm them together with Enter, returning all selected labels as an array. */ export type AskQuestionSelectionMode = 'single_select' | 'multi_select'; export interface AskQuestionInlineOptions { question: string; options?: Array<{ label: string; description?: string; }>; /** Controls whether options are single- or multi-select. Defaults to single_select. */ selectionMode?: AskQuestionSelectionMode; /** Format the text shown after an answer is selected. Defaults to `question → answer`. */ formatResult?: (answer: string) => string; /** If provided, determines whether an answer should be shown with error styling (red ✗). */ isNegativeAnswer?: (answer: string) => boolean; /** Allow submitting an empty string in free-text mode. */ allowEmptyInput?: boolean; /** Show the "Custom response..." option in select mode. Defaults to true. */ allowCustomResponse?: boolean; /** * Use a multiline editor for free-text input (Shift+Enter / \+Enter for new lines). * Defaults to false — most prompts ask for short answers like names, paths, or yes/no. * Enable for prompts that legitimately want paragraph-length replies (e.g. ask_user). */ multiline?: boolean; onSubmit: (answer: string) => void; /** * Called instead of `onSubmit` when the prompt is multi-select, with every selected * option label. Falls back to `onSubmit` with a comma-joined string when omitted. */ onSubmitMulti?: (answers: string[]) => void; onCancel: () => void; } /** * A renderable that wraps the ask-question content in a full bordered box * (┌─┐ / │ │ / └─┘) including the interactive SelectList or Input. */ export declare class AskQuestionBorderedBox { questionLines: string[]; private cachedLines?; private cachedWidth?; private cachedThemeGeneration?; private selectList?; private input?; private hintText; items: Array<{ label: string; description?: string; }>; private answered; private cancelled; private selectedValue?; /** Selected option labels when the box was answered in multi-select mode. */ private selectedValues?; private answerIsNegative; /** True when created during streaming, before activate() is called */ private streaming; constructor(questionLines: string[], hintText: string, items: Array<{ label: string; description?: string; }>, selectList?: WrappingSelectList, input?: Input | MultilineInput, streaming?: boolean); invalidate(): void; private dropCache; setInteractive(selectList?: WrappingSelectList, input?: Input | MultilineInput, hintText?: string): void; setAnswered(selectedValue: string, isNegative: boolean): void; setAnsweredMulti(selectedValues: string[]): void; setCancelled(): void; render(width: number): string[]; private renderSafe; private _render; } export declare class AskQuestionInlineComponent extends Container implements Focusable { private borderedBox; private selectList?; private input?; private tui?; private onSubmit?; private onSubmitMulti?; private onCancel?; private isNegativeAnswer?; private allowEmptyInput; private multiline; private allowCustomResponse; private multiSelect; private answered; /** * Create a pre-answered instance for rendering from chat history. * No interactive elements — just shows the question and the answer in the bordered box. */ static fromHistory(question: string, options: Array<{ label: string; description?: string; }> | undefined, answer: string, cancelled: boolean): AskQuestionInlineComponent; /** * Create a streaming instance for early rendering during tool input streaming. * Shows the bordered box with "…" indicator. Call updateArgs() as partial JSON * arrives, then activate() when the question event fires. */ static createStreaming(tui?: TUI): AskQuestionInlineComponent; private _focused; get focused(): boolean; set focused(value: boolean); render(width: number): string[]; /** * Private constructor — use static factories or the options constructor. */ constructor(options?: AskQuestionInlineOptions, _ui?: TUI); /** * Update the question text and options from streaming partial args. * Called during tool input delta streaming. */ updateArgs(args: unknown): void; /** * Activate the interactive elements (SelectList or Input) and wire up callbacks. * Called by handleAskQuestion when the question event fires after streaming. */ activate(options: { question: string; options?: Array<{ label: string; description?: string; }>; selectionMode?: AskQuestionSelectionMode; isNegativeAnswer?: (answer: string) => boolean; allowEmptyInput?: boolean; allowCustomResponse?: boolean; multiline?: boolean; tui?: TUI; onSubmit: (answer: string) => void; onSubmitMulti?: (answers: string[]) => void; onCancel: () => void; }): void; private static readonly CUSTOM_RESPONSE_VALUE; /** Hint line shown under an option list, tailored to single- vs multi-select. */ private selectHintText; private buildSelectMode; private switchToCustomInput; /** Whether this prompt should render a multiline editor (vs a single-line input). */ private useMultiline; private buildInputMode; answer(answer: string, isNegative?: boolean): void; private handleAnswer; private handleMultiAnswer; private handleCancel; /** * Retract the question without invoking any submit/cancel callback. Used when * the underlying tool suspension was cancelled (the suspended run died, e.g. * its snapshot failed to persist), so an answer could never be delivered. */ dismiss(): void; handleInput(data: string): void; getChatSpacingKind(): ChatSpacingKind; } //# sourceMappingURL=ask-question-inline.d.ts.map