import type { Agent } from "../agent/agent.js";
import type { CenterClient } from "./center-client.js";
export interface CenterFrontendOpts {
    agent: Agent;
    client: CenterClient;
    /** Stable worker id for logs and outbound messages — comes from bootstrap.extras.workerId. */
    workerId: string;
    /** dp-worker package version surfaced in the initial hello message. */
    version?: string;
    /** Override `console` if you want quieter logs in tests. */
    logger?: {
        info: (msg: string) => void;
        warn: (msg: string) => void;
        error: (msg: string) => void;
    };
}
export declare class CenterFrontend {
    private readonly agent;
    private readonly client;
    private readonly workerId;
    private readonly version;
    private readonly logger;
    /**
     * AsyncLocalStorage that carries the active chat scope. Sender/notifier
     * inspect `getStore()` and throw NoChatContext when absent — this lets
     * the Agent multiplexer skip our frontend for non-chat invocations
     * (e.g. scheduled task scope before/after M3, attachment.send from a
     * Telegram chat that hasn't routed through us).
     */
    private readonly chatScope;
    private inFlight;
    private configDirty;
    /**
     * Session-specific dirty flag (set on `config_invalidate { which:
     * includes "session" }`). Separate from `configDirty` so the
     * session swap path (re-bootstrap + Agent.swapSession) doesn't fire
     * for unrelated config changes like LLM key rotation.
     */
    private sessionDirty;
    /** Single-flight guard so two task_dispatches don't race the swap. */
    private swapInFlight;
    private detachers;
    private heartbeatTimer;
    private currentStatus;
    private currentTaskId;
    constructor(opts: CenterFrontendOpts);
    /** Wire up Agent registration + center-client message routing. */
    start(): Promise<void>;
    /** Tear down: deregister listeners and unhook from Agent. */
    stop(): Promise<void>;
    /** True when center has signalled a config change and we should re-bootstrap before the next task. */
    isConfigDirty(): boolean;
    /** Clear the dirty flag once the caller has performed a fresh bootstrap. */
    clearConfigDirty(): void;
    private sendHello;
    private reportRuntimeStatus;
    /** True when a session-class config_invalidate has been received and not yet applied. */
    isSessionDirty(): boolean;
    /**
     * Re-bootstrap from center and apply the new session_blob via
     * Agent.swapSession. No-op if sessionDirty is false. Single-flighted
     * so a burst of dispatches doesn't fire concurrent swaps.
     *
     * Called inline from handleTaskDispatch / handleChatUserMessage
     * BEFORE the task or chat actually runs — that way the freshest
     * session is in place for the new work. The BrowserLock inside
     * Agent.swapSession serializes against the task that's about to
     * acquire it, so any contention resolves naturally.
     */
    swapSessionIfDirty(): Promise<void>;
    private handleIncoming;
    private handleTaskDispatch;
    /**
     * Run the user's chat through the Agent streaming path and emit the
     * full M6 event sequence:
     *
     *   1. (optional) `chat_message_queued` if the BrowserLock is held —
     *      tells the UI "worker busy, waiting in line".
     *   2. `chat_message_chunk` per text delta, in order.
     *   3. A terminal `chat_message_chunk` with done=true (empty delta) so
     *      the UI knows streaming is over before the persisted row lands.
     *   4. `chat_message_finalized` with the full collected content — center
     *      persists this in chat_messages with the assistant's UUID.
     *
     * The assistant message gets its own UUID (`assistantMessageId`); the
     * user's `payload.message_id` is the row center already persisted on
     * /chat/send and is NOT reused.
     */
    private handleChatUserMessage;
    private materializeChatContent;
    private handleCancelTask;
}
