/**
 * Channel manager — lifecycle, persistence, and the inbound→agent→outbound pipeline.
 *
 * Connectors are registered once; the manager starts/stops them, persists their
 * config, decides which inbound messages warrant a reply, runs the agent loop to
 * produce that reply, and sends it back through the connector.
 */
import type { SqliteDatabase } from "../db/sqlite-shim.js";
import type { ToolRegistry } from "../tools/registry.js";
import type { AuditWriter } from "../services/audit.js";
import { type LLMConfig, type AgentMessage } from "../tools/agent-loop.js";
import type { Skill } from "../skills/index.js";
import type { ConsentManager, ConsentRequest, ConsentDecision } from "../security/consent-manager.js";
import type { ChannelConfig, ChannelConnector, ChannelStatus, ChannelStatusDetail, InboundMessage } from "./types.js";
/** Owner user context for channel agent turns — keys, backend, picked model, disabled tools. */
export interface ChannelAuthContext {
    userId?: string;
    apiKeys?: Record<string, string>;
    jaitBackend?: string;
    model?: string;
    /** Reasoning effort level for reasoning-capable models (minimal|low|medium|high). */
    reasoningEffort?: string | null;
    /** Tools the owner has disabled in settings — never sent to the LLM, never executed. */
    disabledTools?: Set<string>;
}
/** Produces an assistant reply for a conversation. Injectable for testing. */
export interface ReplyGenerator {
    generate(history: AgentMessage[], ctx: {
        channelId: string;
        sessionId: string;
        allowedTools: Set<string>;
    }): Promise<string>;
}
export interface ChannelManagerDeps {
    sqlite: SqliteDatabase;
    /** Resolves the LLM config used for channel replies. */
    resolveLLM: () => LLMConfig;
    toolRegistry?: ToolRegistry;
    audit?: AuditWriter;
    projectRoot?: string;
    /** Auth context for tool execution (per-user keys, backend, model). */
    auth?: ChannelAuthContext;
    /**
     * Resolves the owner user context (keys, backend, picked model, disabled
     * tools) for each turn. Preferred over the static `auth` — channels run
     * without request context, so this reads the owner's persisted settings live
     * so replies use the same provider/model/tool surface as the web chat.
     */
    resolveAuth?: () => ChannelAuthContext | undefined;
    /**
     * Resolves the enabled skills to inject into the channel agent's system
     * prompt — gives WhatsApp et al. the same skill catalogue as the web chat.
     */
    resolveSkills?: () => Skill[];
    /**
     * Consent manager — when present, mutating tools require an in-band yes/no
     * approval from the user before they run (the channel sends a prompt and waits
     * for the reply). Without it, channel tools auto-execute.
     */
    consentManager?: ConsentManager;
    /** Approval timeout (ms) before a consent prompt auto-denies. Default 5 min. */
    consentTimeoutMs?: number;
    /** System prompt for the channel agent. */
    systemPrompt?: string;
    /** Max conversation messages to retain (excluding the system prompt). */
    maxHistory?: number;
    /** Override reply generation (defaults to the agent loop). */
    replyGenerator?: ReplyGenerator;
    log?: (msg: string, ...args: unknown[]) => void;
}
interface ManagedChannel {
    connector: ChannelConnector;
    status: ChannelStatus;
    detail: ChannelStatusDetail;
}
/** Normalize a WhatsApp-style id to its bare number for allowlist comparison. */
export declare function normalizeSenderId(id: string): string;
/** Decide whether the agent should reply to an inbound message. */
export declare function shouldRespond(msg: InboundMessage, config: ChannelConfig): boolean;
export declare class ChannelManager {
    private readonly deps;
    private readonly channels;
    /** Per-conversation message history (keyed by `${channelId}:${conversationId}`). */
    private readonly histories;
    /** Per-conversation serialization so messages are answered in order. */
    private readonly locks;
    /** sessionId → conversation target, so consent prompts reach the right chat. */
    private readonly sessionTargets;
    /** conversationKey → pending consent requestId (one outstanding approval per chat). */
    private readonly consentByConversation;
    /** requestId → conversation, to clear mappings once a decision lands. */
    private readonly consentTargets;
    private readonly replyGenerator;
    constructor(deps: ChannelManagerDeps);
    private log;
    private ensureTable;
    getConfig(id: string): ChannelConfig;
    setConfig(id: string, patch: Partial<ChannelConfig>): ChannelConfig;
    register(connector: ChannelConnector): void;
    /** Remove a connector from the available channel list. Config is preserved. */
    unregister(id: string): Promise<void>;
    list(): {
        id: string;
        label: string;
        status: ChannelStatus;
        enabled: boolean;
        qr: string | null;
        config: ChannelConfig;
    }[];
    get(id: string): ManagedChannel | undefined;
    /** Start a connector and persist enabled=true. */
    start(id: string): Promise<void>;
    /** Stop a connector and persist enabled=false. */
    stop(id: string): Promise<void>;
    /** Start every connector whose persisted config has enabled=true. */
    startEnabled(): Promise<void>;
    dispose(): Promise<void>;
    private handleInbound;
    private replyTo;
    /**
     * Called (via the ConsentManager `onRequest` bridge) when a tool in a channel
     * session needs approval. Sends an in-band yes/no prompt to the originating
     * chat and records the pending request so a reply can resolve it.
     */
    handleConsentRequest(request: ConsentRequest): void;
    /**
     * Called (via the ConsentManager `onDecision` bridge) when any consent
     * request resolves. Clears channel-side mappings and, on timeout, lets the
     * user know the action was skipped.
     */
    handleConsentDecision(decision: ConsentDecision): void;
    /**
     * If the conversation has an outstanding approval, interpret this inbound
     * message as the yes/no answer and resolve the consent request. Returns true
     * when the message was consumed as a consent reply (and must NOT start a turn).
     */
    private tryResolveConsent;
}
export declare class AgentLoopReplyGenerator implements ReplyGenerator {
    private readonly deps;
    constructor(deps: ChannelManagerDeps);
    generate(history: AgentMessage[], ctx: {
        channelId: string;
        sessionId: string;
        allowedTools: Set<string>;
    }): Promise<string>;
}
export {};
//# sourceMappingURL=manager.d.ts.map