export interface SelectorGroup {
    strategies: string[];
    textMatch?: string[];
}
export interface SelectorConfig {
    chatContainer: SelectorGroup;
    chatInput: SelectorGroup;
    chatTabList: SelectorGroup;
    /** Editor: open agent chats in auxiliary bar tab strip (not agents sidebar). */
    editorAuxiliaryTabList?: SelectorGroup;
    newChatButton: SelectorGroup;
    modeDropdown: SelectorGroup;
    modelDropdown: SelectorGroup;
    approveButton: SelectorGroup;
    rejectButton: SelectorGroup;
    submitButton: SelectorGroup;
    stopButton: SelectorGroup;
}
export interface ServerConfig {
    cdpUrl: string;
    serverHost: string;
    serverPort: number;
    pollIntervalMs: number;
    debounceMs: number;
    webappPassword: string;
    cursorProjectsDir: string;
    windowPollIntervalMs: number;
    /** OpenAI API key for POST /api/transcribe (Whisper). */
    openaiApiKey: string;
    openaiTranscribeModel: string;
    /** Poll CDP targets and follow foreground Cursor window. */
    focusSyncEnabled: boolean;
    focusSyncIntervalMs: number;
    /** VPS relay URL (wss://…). Empty = direct clients only. */
    relayUrl: string;
    relayToken: string;
    relayRoomId: string;
    /** macOS: prevent system sleep while bridge runs (`KEEP_AWAKE=0` to disable). */
    keepAwakeEnabled: boolean;
    /** Periodic upstream ping when `relayUrl` set; `0` = off. */
    relayKeepaliveMs: number;
    /** Client token from ~/.cursorconnect/identity.json (relay room pairing). */
    pairingClientToken?: string;
}
export interface ChatTab {
    id: string;
    title: string;
    composerId?: string;
    active?: boolean;
    isWorking?: boolean;
    hasUnread?: boolean;
    /** Sidebar orange dot — agent needs user input (e.g. AskQuestion). */
    needsAttention?: boolean;
}
export interface QuestionnaireOption {
    id: string;
    letter: string;
    label: string;
    selected?: boolean;
    /** Glass `questionnaire-freeform-option` (Other…). */
    freeform?: boolean;
    selectorPath: string;
}
export interface QuestionnaireQuestion {
    id: string;
    prompt: string;
    options: QuestionnaireOption[];
}
export interface QuestionnaireAction {
    label: string;
    type: 'skip' | 'continue' | 'other';
    selectorPath: string;
}
export interface PendingQuestionnaire {
    id: string;
    title: string;
    questions: QuestionnaireQuestion[];
    actions: QuestionnaireAction[];
    /** 0-based index from Glass pager (`N of M` in `.ui-tray-header__right`). */
    currentStepIndex?: number;
    totalSteps?: number;
    navPrevSelectorPath?: string;
    navNextSelectorPath?: string;
}
export type AgentTodoStatus = 'pending' | 'in_progress' | 'completed' | 'cancelled';
export interface AgentTodo {
    id: string;
    text: string;
    status: AgentTodoStatus;
}
export interface QueuedMessage {
    id: string;
    text: string;
    hasImage?: boolean;
    /** Attachment paths from queued row `img` (same as chat `images[]`). */
    images?: string[];
    /** Stable Cursor queue row id (`data-queue-item-id`). */
    queueItemId?: string;
    removeSelectorPath?: string;
}
/** Cursor “Build … / To-dos” plan card in chat (from `CreatePlan` JSONL or DOM). */
export interface ChatPlanBlock {
    buildTitle: string;
    todos: AgentTodo[];
    completedCount?: number;
    totalCount?: number;
}
export interface ChatMessage {
    id: string;
    role: 'user' | 'assistant' | 'system';
    text: string;
    html?: string;
    planBlock?: ChatPlanBlock;
    /** Absolute path or URL from DOM `image-pill-img` (served via GET /media/file). */
    images?: string[];
    /** Cursor `data-flat-index` when present; else extract sequence. */
    flatIndex?: number;
    /** Document order within one DOM extract (tie-break when flatIndex ties). */
    domSeq?: number;
}
/** Per-poll DOM extract stats — why rows were skipped (for missing-message debug). */
export interface MessageExtractDebug {
    at: number;
    flatNodes: number;
    extracted: number;
    dropped: {
        tool: number;
        userNoise: number;
        userEmpty: number;
        assistantWorking: number;
        assistantNotMeaningful: number;
        assistantNoMarkdown: number;
    };
    droppedSamples: {
        reason: string;
        preview: string;
        flatIndex?: string;
    }[];
    containerComposerId?: string;
    messagePreviews: {
        id: string;
        role: string;
        len: number;
        flatIndex?: number;
    }[];
}
export interface ApprovalAction {
    label: string;
    type: 'approve' | 'reject' | 'approve_all';
    selectorPath: string;
}
export interface PendingApproval {
    id: string;
    description: string;
    actions: ApprovalAction[];
}
export type CursorWindowKind = 'editor' | 'agents';
export interface CursorWindow {
    id: string;
    title: string;
    workspace?: string;
    wsUrl?: string;
    /** `agents` = dedicated Cursor Agents window; `editor` = code editor + auxiliary bar. */
    kind?: CursorWindowKind;
}
export interface WindowSnapshot {
    windowId: string;
    title: string;
    activeComposerId?: string;
    messageCount: number;
    lastPreview?: string;
    agentStatus?: string;
    /** DOM: active chat generating (composer Stop / status line). */
    agentWorking?: boolean;
    pendingApprovals: number;
    updatedAt: number;
    tabs?: ChatTab[];
    kind?: CursorWindowKind;
}
export interface CursorState {
    connected: boolean;
    workspace?: string;
    windows: CursorWindow[];
    activeWindowId?: string;
    windowSnapshots?: WindowSnapshot[];
    tabs: ChatTab[];
    activeTabId?: string;
    activeComposerId?: string;
    /** Auxiliary bar title — matches visible chat when switching tabs. */
    activeChatTitle?: string;
    /** Sidebar title → composer UUID (built while user switches chats). */
    composerIdByTitle?: Record<string, string>;
    messages: ChatMessage[];
    todos: AgentTodo[];
    /** Header count when list is collapsed ("To-dos 13"). */
    todosTotalCount?: number;
    /** "View all" in todo header — CDP click path. */
    todosViewAllSelectorPath?: string;
    queuedMessages: QueuedMessage[];
    pendingApprovals: PendingApproval[];
    /** Composer AskQuestion UI (Plan mode) — options A/B/C… + Skip/Continue. */
    pendingQuestionnaire?: PendingQuestionnaire;
    /** DOM: loading / Stop visible — request in progress */
    agentWorking?: boolean;
    /**
     * Bridge `ChatDisplayStore` ahead of last `agent:messages` for this agent.
     * App may show «Догружаем ответ…» until cleared.
     */
    lentaPendingByAgent?: Record<string, boolean>;
    agentStatus?: string;
    /** DOM status line when agentStatus is background_shell */
    agentStatusMessage?: string;
    contextPercent?: number;
    terminalCount?: number;
    mode?: string;
    model?: string;
    lastError?: string;
    /** Cursor sidebar: repos + chats in DOM order (mirrors desktop UI). */
    sidebarRepos?: RepoGroup[];
    /** Editor mode: open editor windows as folders, tabs as chats (see `cursorListMode`). */
    editorRepos?: RepoGroup[];
    /** Which chat list layout the app should show (`editor` vs agents window). */
    cursorListMode?: 'editor' | 'agents-window';
    /** Last DOM poll: extract/filter counters (sync debug). */
    messageDebug?: MessageExtractDebug;
    /** Composer input draft (contenteditable), when non-empty. */
    composerDraft?: string;
    /** Image paths in composer (not yet sent). */
    composerDraftImages?: string[];
    updatedAt: number;
}
export type CommandType = 'send_message' | 'switch_tab' | 'switch_window' | 'new_chat' | 'focus_agent' | 'approve' | 'reject' | 'approve_all' | 'start_multitasking' | 'remove_queued' | 'stop_agent' | 'click_selector';
export interface CommandPayload {
    id: string;
    type: CommandType;
    text?: string;
    tabTitle?: string;
    /** Editor CDP window id (from chat route / agents:index). */
    windowId?: string;
    agentId?: string;
    selectorPath?: string;
    /** Questionnaire toolbar: option | skip | continue (with click_selector). */
    questionnaireAction?: string;
    questionnaireLetter?: string;
    questionnaireLabel?: string;
    /** Custom answer for freeform / Other option before Continue. */
    questionnaireFreeform?: string;
    /** `1` — toggle off already-selected option (second click). */
    questionnaireDeselect?: string;
    /** When true, enqueue in Cursor instead of sending immediately (agent busy). */
    queue?: boolean | string;
    /** Comma-separated absolute paths on bridge (from POST /api/upload-image). */
    imagePaths?: string;
    /** Agents sidebar folder name for `new_chat` in that repo. */
    repoName?: string;
    /** Prefer CDP path from `RepoGroup.newChatSelectorPath`. */
    newChatSelectorPath?: string;
    /** `data-queue-item-id` from composer queue row (preferred for `remove_queued`). */
    queueItemId?: string;
}
export interface CommandResult {
    id: string;
    ok: boolean;
    error?: string;
    /** After stop_agent — text Cursor put back in the composer. */
    composerDraft?: string;
    /** After stop_agent — attachment paths from composer pills. */
    composerDraftImages?: string[];
    /** After `new_chat` — focused composer/tab in Cursor. */
    activeAgentId?: string;
    activeChatTitle?: string;
}
export interface AgentSummary {
    id: string;
    title: string;
    updatedAt: number;
    messageCount: number;
    preview?: string;
    isWorking?: boolean;
    hasUnread?: boolean;
    needsAttention?: boolean;
}
export interface RepoGroup {
    id: string;
    name: string;
    path: string;
    agents: AgentSummary[];
    /** CDP selector for folder «+» (new chat in this repo), when visible in Agents sidebar. */
    newChatSelectorPath?: string;
}
export interface AgentsIndex {
    repos: RepoGroup[];
    updatedAt: number;
    /** `sidebar` = порядок/названия из CDP; `jsonl` = архив на диске (не как в Cursor). */
    listSource?: 'sidebar' | 'jsonl';
}
export interface HistoryMessage {
    role: 'user' | 'assistant' | 'system';
    text: string;
    html?: string;
    planBlock?: ChatPlanBlock;
    /** Absolute paths from JSONL `<image_files>` (served via GET /media/file). */
    images?: string[];
    ts?: number;
}
export interface AgentHistory {
    agentId: string;
    /** Display-ready chat rows (normalized, deduped, filtered on bridge). */
    messages: ChatMessage[];
    updatedAt?: number;
    requestId?: string;
    totalMessages?: number;
    /** User turns in full lenta (for pagination). */
    totalTurns?: number;
    /** More turns exist above the loaded window. */
    hasMoreOlder?: boolean;
    /** Monotonic per agentId — stale snapshots should be ignored. */
    seq?: number;
}
/** Per-agent chat lenta. `state.messages` in state:patch is DOM/debug only. */
export interface AgentMessagesPayload {
    agentId: string;
    /** JSONL baseline rows (debug). */
    historyMessages: ChatMessage[];
    /** DOM overlay rows not yet in JSONL (debug). */
    liveMessages: ChatMessage[];
    /** Canonical UI lenta — merge + dedupe + sort on bridge. */
    messages: ChatMessage[];
    totalMessages?: number;
    totalTurns?: number;
    hasMoreOlder?: boolean;
    source: 'dom' | 'jsonl' | 'hybrid';
    updatedAt: number;
    seq: number;
    /** Live JSONL stream: merge `historyMessages` into client state (small payload). */
    append?: boolean;
}
export type ExtractedPageState = Omit<CursorState, 'connected' | 'windows' | 'activeWindowId' | 'windowSnapshots' | 'updatedAt'>;
