import type { LucidClientLike, LucidRawRunner } from '../stores/lucid.js';
import type { Actor } from '../types.js';
import type { PoppyContext, PoppyEvent, PoppyEventBody, PoppyResponder, PoppyStatus } from './protocol.js';
/**
 * A Poppy conversation as this surface keeps it. It belongs to a Personal Agent (`clientId`) and
 * one of its Users (`userId`); once it used an account here, to that account too (`accountRef`,
 * §7.2). The agent side of it is an agent thread (`threadId`) — a Direct Conversation shares its
 * parent's thread, which is how the Company Agent knows what was said in either (§7.10).
 */
export interface PoppyConversationRecord {
    id: string;
    clientId: string;
    userId: string;
    accountRef: string | null;
    agentName: string;
    threadId: string | null;
    /** Set on a Direct Conversation: the conversation it was opened from. */
    parentId: string | null;
    /** On a parent: its open Direct Conversation. */
    openDirectId: string | null;
    status: PoppyStatus;
    responder: PoppyResponder;
    /** The User's context as last shared — fields a message leaves out keep their value (§7.4). */
    context: PoppyContext;
    /** The run whose reply is being written, while there is one. */
    activeRunId: string | null;
    /**
     * The position of the last message event a turn has taken up. The event log IS the turn queue:
     * user messages after it are what the next turn answers — on whichever replica picks it up.
     */
    turnSeq: number;
    /** Who the latest request came as — what the next turn runs as. */
    grant: PoppyGrant | null;
    /** Events at or below this sequence number were pruned: a cursor there is `cursor_expired`. */
    prunedSeq: number;
    createdAt: number;
    updatedAt: number;
}
/** The identity a conversation's turns run as: that of its latest request (§7.11). */
export interface PoppyGrant {
    actor: Actor;
    /** The token's account scopes; empty when signed out. */
    scopes: string[];
    signedIn: boolean;
}
export type PoppyConversationPatch = Partial<Pick<PoppyConversationRecord, 'accountRef' | 'threadId' | 'openDirectId' | 'status' | 'responder' | 'context' | 'activeRunId' | 'turnSeq' | 'grant' | 'prunedSeq'>>;
/** An event with its position in the conversation. */
export interface StoredPoppyEvent {
    seq: number;
    event: PoppyEvent;
}
/** What an accepted message keeps for its retries (§7.3). */
export interface PoppyMessageReceipt {
    fingerprint: string;
    conversationId: string;
    /** The HTTP status and body the first delivery got. */
    response: {
        status: number;
        body: Record<string, unknown>;
    };
}
export type PoppyMessageClaim = {
    status: 'claimed';
} | ({
    status: 'existing';
} & PoppyMessageReceipt);
/**
 * Where conversations, their events and message receipts live. Events are append-only and read by
 * position, so a cursor replays exactly what came after it, on any replica.
 *
 * @experimental Tracks Personal Agent Protocol Draft 0.1 (https://personalagentprotocol.org/docs/spec),
 * a spec still in development: this API WILL change as the spec evolves — possibly in breaking
 * ways, outside semver majors while it is a draft.
 */
export interface PoppyStore {
    createConversation(record: PoppyConversationRecord): Promise<void>;
    getConversation(id: string): Promise<PoppyConversationRecord | null>;
    updateConversation(id: string, patch: PoppyConversationPatch): Promise<void>;
    /**
     * Append an event, numbered after the conversation's last. With `key`, at most one event per key
     * is ever appended: a second append answers the first event with `created: false` — what keeps a
     * re-read turn from writing its reply twice.
     */
    appendEvent(conversationId: string, body: PoppyEventBody, options?: {
        key?: string;
    }): Promise<{
        event: StoredPoppyEvent;
        created: boolean;
    }>;
    /** Events after `afterSeq`, in order, at most `limit`. */
    listEvents(conversationId: string, afterSeq: number, limit: number): Promise<StoredPoppyEvent[]>;
    /** The position of an event, `null` when the conversation has no such event (any more). */
    findEventSeq(conversationId: string, eventId: string): Promise<number | null>;
    /** Drop the events created before `before` (epoch ms), keeping at least the last one. */
    pruneEvents(conversationId: string, before: number): Promise<void>;
    /** Claim `messageId` for `owner` — atomically, so two deliveries of one message are one. */
    claimMessage(owner: string, messageId: string, receipt: PoppyMessageReceipt): Promise<PoppyMessageClaim>;
    /** Drop a claim whose message could not be taken, so its retry is processed afresh. */
    releaseMessage(owner: string, messageId: string): Promise<void>;
    /**
     * Take (or renew) the right to write the reply of the conversation's active run for `ttlMs` —
     * `false` while another holder's lease is live. What lets any replica pick up a reply whose
     * writer died, without two writing it at once.
     */
    leaseReader(conversationId: string, holder: string, ttlMs: number): Promise<boolean>;
    releaseReader(conversationId: string, holder: string): Promise<void>;
}
/** `evt_<seq in base 36>_<random>`: unique, and its position survives the event being pruned. */
export declare function newEventId(seq: number): string;
/** The position an event id was minted at, or `null` for an id this surface never minted. */
export declare function eventIdSeq(id: string): number | null;
/** Process-local {@link PoppyStore} — tests and single-instance dev. */
export declare class InMemoryPoppyStore implements PoppyStore {
    #private;
    createConversation(record: PoppyConversationRecord): Promise<void>;
    getConversation(id: string): Promise<PoppyConversationRecord | null>;
    updateConversation(id: string, patch: PoppyConversationPatch): Promise<void>;
    appendEvent(conversationId: string, body: PoppyEventBody, options?: {
        key?: string;
    }): Promise<{
        event: {
            seq: number;
            event: PoppyEvent;
        };
        created: boolean;
    }>;
    listEvents(conversationId: string, afterSeq: number, limit: number): Promise<{
        seq: number;
        event: PoppyEvent;
    }[]>;
    findEventSeq(conversationId: string, eventId: string): Promise<number | null>;
    pruneEvents(conversationId: string, before: number): Promise<void>;
    claimMessage(owner: string, messageId: string, receipt: PoppyMessageReceipt): Promise<{
        fingerprint: string;
        conversationId: string;
        /** The HTTP status and body the first delivery got. */
        response: {
            status: number;
            body: Record<string, unknown>;
        };
        status: 'existing';
    } | {
        status: 'claimed';
    }>;
    releaseMessage(owner: string, messageId: string): Promise<void>;
    leaseReader(conversationId: string, holder: string, ttlMs: number): Promise<boolean>;
    releaseReader(conversationId: string, holder: string): Promise<void>;
}
export declare const POPPY_TABLES: {
    readonly conversations: 'agent_poppy_conversation';
    readonly events: 'agent_poppy_event';
    readonly messages: 'agent_poppy_message';
};
/**
 * `CREATE TABLE IF NOT EXISTS` DDL for the Poppy tables — the agent tables' portable dialect
 * (quoted identifiers, epoch-ms `BIGINT`, `TEXT` JSON; `forDialect` adapts it to MySQL). An event's
 * primary key is its position, so two appends racing for one position cannot both win; a message's
 * is its (owner, id), the idempotency lock of §7.3.
 */
export declare function poppyTableStatements(): string[];
/** Create the Poppy tables if missing. Idempotent. */
export declare function ensurePoppyTables(db: LucidRawRunner): Promise<void>;
export interface LucidPoppyStoreOptions {
    /** Create the tables on first use (`CREATE TABLE IF NOT EXISTS`). Default `true`. */
    autoCreateTables?: boolean;
}
/** {@link PoppyStore} over Lucid (any of SQLite / Postgres / MySQL). */
export declare class LucidPoppyStore implements PoppyStore {
    #private;
    private readonly db;
    constructor(db: LucidClientLike & LucidRawRunner, options?: LucidPoppyStoreOptions);
    /** Provision the tables (once). A failure is retried on the next call. */
    ready(): Promise<void>;
    createConversation(record: PoppyConversationRecord): Promise<void>;
    getConversation(id: string): Promise<PoppyConversationRecord | null>;
    updateConversation(id: string, patch: PoppyConversationPatch): Promise<void>;
    appendEvent(conversationId: string, body: PoppyEventBody, options?: {
        key?: string;
    }): Promise<{
        event: {
            seq: number;
            event: PoppyEvent;
        };
        created: boolean;
    }>;
    listEvents(conversationId: string, afterSeq: number, limit: number): Promise<{
        seq: number;
        event: PoppyEvent;
    }[]>;
    findEventSeq(conversationId: string, eventId: string): Promise<number | null>;
    pruneEvents(conversationId: string, before: number): Promise<void>;
    claimMessage(owner: string, messageId: string, receipt: PoppyMessageReceipt): Promise<{
        status: 'claimed';
        fingerprint?: never;
        conversationId?: never;
        response?: never;
    } | {
        status: 'existing';
        fingerprint: string;
        conversationId: string;
        response: PoppyMessageReceipt['response'];
    }>;
    releaseMessage(owner: string, messageId: string): Promise<void>;
    leaseReader(conversationId: string, holder: string, ttlMs: number): Promise<boolean>;
    releaseReader(conversationId: string, holder: string): Promise<void>;
}
//# sourceMappingURL=store.d.ts.map