import type { KeybindingsManager, Theme } from "@earendil-works/pi-coding-agent"; /** * Target-scoped conversation queue for the focused conversation view. * * When pi-queue-steer is installed and loaded, a versioned pi.events * request/capability handshake hands this adapter a bridge over the * extension's actual queue-state machinery (DeliveryQueue / QueueEditSession) * AND its actual shared execution-outline renderer (QueueTimelineWidget plus * the real inline-editor line extractor) — no imitation, no hard dependency: * the extension is discovered only through the shared bus. With no compatible * listener, the adapter falls back to Pi's native queue display labels and * colors ("Steering:" / "Follow-up:", dim) and never claims extension parity. * * Delivery ownership rules: * - every queue is bound to one targetId; rows and edits never leak to Main * or to another target (dispatch goes only through the injected per-target * `send` route); * - `submit` acknowledges route acceptance only: a resolved send marks the * row `dispatched` — immutable, no longer editable (there is no remote * retract API) — and still visible until native delivery is confirmed; * - rows retire through `syncSnapshot`, reconciled against the native * reader agent's complete-message snapshot (read-only messages/streaming/ * tools source). Retirement keys on stable native message identities plus * a pre-dispatch baseline and live-append frontier, with duplicate * counting — trimmed text alone is NOT identity: identical text already in * history, or historical pages loaded later, can never fake consumption; * - a failed send restores the exact remaining rows to the timeline head in * order; * - Main-only control actions (/ commands, ! bash) are rejected at stage * time — they are not queueable from a focused conversation; * - a run ending (settled, aborted, errored) is NOT a retirement event: * follow-ups exist precisely to start the next run, and a route ack cannot * prove consumption, so only `retireAll` (target removed/unreachable) or * an identity match retires rows; * - `dispose({ retain })` gives Main explicit cleanup/retention semantics on * target switches: retention parks the rows (the extension bridge retains * them per targetId for re-adoption), the default clears them and releases * the extension's retained state. */ export declare const QUEUE_STEER_CONVERSATION_QUEUE_REQUEST_EVENT = "queue-steer:conversation-queue:request:v1"; type ConversationQueueLane = "steer" | "followUp"; type ConversationQueueRowState = "queued" | "dispatched"; interface ConversationQueueRow { id: string; lane: ConversationQueueLane; text: string; state: ConversationQueueRowState; paused: boolean; /** Current row-editing selection (rendered live through the shared editor). */ selected: boolean; /** Marked for removal inside the active editing session; save deletes it. */ removed: boolean; } /** Structural shape of the extension's real `DeliveryQueue` rows. */ interface ConversationQueueBridgeRow { id: string; lane: ConversationQueueLane; text: string; images: unknown[]; sequence: number; paused?: boolean; } /** Structural subset of the extension's real `DeliveryQueue`. */ interface ConversationQueueBridgeQueue { enqueue(lane: ConversationQueueLane, text: string, images?: readonly unknown[]): ConversationQueueBridgeRow; snapshot(): ConversationQueueBridgeRow[]; peek(): ConversationQueueBridgeRow | undefined; get(id: string): ConversationQueueBridgeRow | undefined; remove(id: string): ConversationQueueBridgeRow | undefined; prepend(item: ConversationQueueBridgeRow): void; prependMany(items: readonly ConversationQueueBridgeRow[]): void; update(id: string, text: string): boolean; setLane(id: string, lane: ConversationQueueLane): boolean; setPaused(id: string, paused: boolean): boolean; clear(): void; readonly length: number; } /** Structural subset of the extension's real `QueueEditSession`. */ interface ConversationQueueBridgeEditSession { readonly selectedId: string; readonly composerDraft: string; capture(text: string): void; select(item: ConversationQueueBridgeRow, currentText: string): string; toggleRemoved(id: string): boolean | undefined; togglePaused(id: string): boolean | undefined; setLane(id: string, lane: ConversationQueueLane): ConversationQueueLane | undefined; commit(queue: ConversationQueueBridgeQueue, currentText: string): { updated: number; removed: number; moved: number; held: number; released: number; }; rollbackPositions(queue: ConversationQueueBridgeQueue): void; /** Draft decoration readers; present on the extension's real session. */ laneFor?(id: string): ConversationQueueLane | undefined; textFor?(id: string): string | undefined; pausedFor?(id: string): boolean | undefined; isRemoved?(id: string): boolean; } /** * Version 1 interop bridge served by a loaded pi-queue-steer over the * `QUEUE_STEER_CONVERSATION_QUEUE_REQUEST_EVENT` handshake. The renderer * members are optional so an older installed extension degrades gracefully * to the native fallback rendering instead of an imitation outline. */ export interface ConversationQueueBridgeV1 { version: 1; targetId: string; createQueue(): ConversationQueueBridgeQueue; createEditSession(item: ConversationQueueBridgeRow, composerDraft: string): ConversationQueueBridgeEditSession; isQueueableSubmission(text: string): boolean; laneLabel(lane: ConversationQueueLane): string; laneColor(lane: ConversationQueueLane): string; buildTimelineItems?(queue: ConversationQueueBridgeQueue, editSession: ConversationQueueBridgeEditSession | undefined, modes?: { steer: "all" | "one-at-a-time"; followUp: "all" | "one-at-a-time"; }): readonly unknown[]; createTimelineWidget?(options: { items: readonly unknown[]; editingId?: string; paused?: boolean; idle?: boolean; renderInlineEditor?: (width: number) => string[]; }, theme: Theme): { render(width: number): string[]; invalidate(): void; }; extractInlineEditorLines?(lines: readonly string[], paddingX?: number): string[]; } /** * One entry of the native reader agent's complete-message snapshot. `id` is * a stable native identity (session entry id, reader event identity, or the * actor envelope/message id); `historical` marks baseline/history entries — * including older transcript pages loaded later — which establish identity * but can never retire a queued row. */ export interface ConversationSnapshotEntry { id: string; text: string; historical?: boolean; } export interface ConversationQueueOptions { /** Shared pi.events bus (emit only; the handshake is synchronous claim/respond). */ piEvents: { emit(channel: string, data: unknown): void; }; targetId: string; targetName?: string; /** * Per-target dispatch route owned by Main; rejection restores the rows. A * resolved value may carry `messageId`/`id` (e.g. an actor envelope id) — * it becomes the row's expected native delivery identity. */ send: (message: string, delivery: ConversationQueueLane) => Promise; theme: Theme; /** Shared focused-view editor; row editing renders live through it. */ editor?: { getText(): string; setText(text: string): void; handleInput?(data: string): void; /** Full renderer, used for real inline row editing via the bridge. */ render?(width: number): string[]; /** Editor interior padding, forwarded to the inline line extractor. */ paddingX?: number; }; keybindings?: Pick; /** Target run state for the shared widget's stage label; default idle=false. */ isIdle?: () => boolean; onNotify?: (text: string, kind: "info" | "error") => void; requestRender?: () => void; } interface StageResult { ok: boolean; id?: string; error?: string; } export interface ConversationQueue { readonly targetId: string; /** "extension" when a real pi-queue-steer bridge was claimed, "native" otherwise. */ readonly mode: "extension" | "native"; readonly active: boolean; /** True while a row-editing session holds the shared editor. */ readonly editingActive: boolean; /** Stage text as a queue row for this target. Control input is rejected. */ stage(text: string, lane: ConversationQueueLane): StageResult; /** * Dispatch the contiguous same-lane unpauseed head batch through `send`. * Resolves true only when every row was accepted by the route; a failure * restores the unsent rows to the timeline head in order. Acceptance is * not consumption: accepted rows become immutable and stay visible until * `syncSnapshot` confirms native delivery identity. */ submit(lane: ConversationQueueLane): Promise; dispatch(text: string, lane: ConversationQueueLane): Promise; park(text: string, lane: ConversationQueueLane): StageResult; cancelEditing(): void; syncPending(pending: { steering: string[]; followUp: string[]; } | undefined): void; /** Decorated rows in timeline order (dispatched rows first, then queued). */ rows(): ConversationQueueRow[]; /** Queue list lines rendered above the editor; empty when nothing is held. */ render(width: number): string[]; /** * Row-editing input handling. Consumes keys only while an editing session * is active or when the dequeue binding starts one; everything else passes * back to Main (`false`). */ handleInput(data: string): boolean; /** * Reconcile against the native reader snapshot (read-only). Main feeds the * initial pre-dispatch snapshot as `historical` entries before the first * submit, then subsequent live snapshots. Retirement matches, first, a * dispatched row's expected native message identity (actor envelope id) or, * failing that, one live-append entry beyond the historical baseline with * the row's text — duplicate rows each consume their own frontier entry. * Historical entries and older pages never retire anything. */ syncSnapshot(entries: readonly ConversationSnapshotEntry[]): number; /** * The target is being removed/unreachable: retire every held row. This is * the only blanket retirement, and it must NOT be called merely because a * run ended or the target stopped — follow-ups are for the next run, and a * route ack cannot prove consumption. */ retireAll(reason?: string): number; /** Number of rows still held (queued + dispatched-but-unconfirmed). */ pendingCount(): number; dispose(options?: { retain?: boolean; }): void; } /** * Create one target-scoped conversation queue. Attempts the pi-queue-steer * interop handshake first (mode "extension"); with no claiming listener the * adapter serves Pi's native queue labels and colors (mode "native"). */ export declare function createConversationQueue(options: ConversationQueueOptions): ConversationQueue; export {}; //# sourceMappingURL=conversation-queue.d.ts.map