/**
 * Umbrella chat-routing source + pure resolution FSM (CAT.1a).
 *
 * Replaces the legacy per-project `chat-routing.json` + the in-worker
 * `RoutingIndex` Map (`src.legacy/chat/daemon/routing.ts`) — see
 * `docs/research/T-chat-as-terminal-CAT1-pre-research-2026-06-02.md` (loop
 * repo) for the full design + the two-worlds finding.
 *
 * One authoritative source on disk: `~/.opensquid/channels.json`, keyed by
 * UMBRELLA (loop + loop-engine + opensquid = ONE umbrella, topic 15), not by
 * per-cwd project_uuid. This structurally enforces the locked invariants:
 *
 *   - Border (#3): one (chat_id, topic_id) ↔ exactly one umbrella row, so a
 *     message in a topic reaches only that umbrella's session.
 *   - Umbrella ≤1:1 (#4): resolution is at the umbrella level via `members`
 *     (cwd prefixes) — it eliminates the `da96≡0742` mirror hack entirely
 *     (one umbrella row covers every member cwd; nothing to keep in sync).
 *   - General (#5): DM + the supergroup General thread + the "All" topic
 *     resolve to the reserved `'general'` id (the project-less session),
 *     never a project umbrella.
 *
 * Resolution is three PURE TOTAL functions — the deterministic routing the
 * track demands (the agent never picks a raw channel/tool; the FSM derives
 * the destination). No I/O in the resolvers; `loadChannelsConfig` is the only
 * disk read and is ENOENT/parse-tolerant (absent ⇒ null, never throws).
 *
 * Imports from: zod, node:fs/promises, ../runtime/paths.
 * Imported by: CAT.1b daemon (inbound routing), CAT.1c readers + MCP, tests.
 */
import { z } from 'zod';
export declare const GENERAL_UMBRELLA: "general";
/**
 * A resolved chat target: either an umbrella `id` or the reserved
 * `GENERAL_UMBRELLA` sentinel. Both are strings — the union would collapse
 * to `string`, so we model it as `string` and rely on `GENERAL_UMBRELLA` for
 * the sentinel comparison rather than a (redundant) literal-union type.
 */
export type UmbrellaTarget = string;
declare const TelegramTarget: z.ZodObject<{
    /** Supergroup/chat id (negative string for supergroups). */
    chat_id: z.ZodString;
    /** Forum topic id; omitted ⇒ the umbrella owns the bare-group key. */
    topic_id: z.ZodOptional<z.ZodNumber>;
}, "strict", z.ZodTypeAny, {
    chat_id: string;
    topic_id?: number | undefined;
}, {
    chat_id: string;
    topic_id?: number | undefined;
}>;
export type TelegramTarget = z.infer<typeof TelegramTarget>;
declare const UmbrellaRow: z.ZodObject<{
    /** Stable umbrella id (e.g. `loop`). MUST NOT be the reserved `general`. */
    id: z.ZodEffects<z.ZodString, string, string>;
    /** Absolute cwd prefixes that belong to this umbrella (cwd→umbrella). */
    members: z.ZodArray<z.ZodString, "many">;
    telegram: z.ZodOptional<z.ZodObject<{
        /** Supergroup/chat id (negative string for supergroups). */
        chat_id: z.ZodString;
        /** Forum topic id; omitted ⇒ the umbrella owns the bare-group key. */
        topic_id: z.ZodOptional<z.ZodNumber>;
    }, "strict", z.ZodTypeAny, {
        chat_id: string;
        topic_id?: number | undefined;
    }, {
        chat_id: string;
        topic_id?: number | undefined;
    }>>;
}, "strict", z.ZodTypeAny, {
    id: string;
    members: string[];
    telegram?: {
        chat_id: string;
        topic_id?: number | undefined;
    } | undefined;
}, {
    id: string;
    members: string[];
    telegram?: {
        chat_id: string;
        topic_id?: number | undefined;
    } | undefined;
}>;
export type UmbrellaRow = z.infer<typeof UmbrellaRow>;
declare const GeneralRow: z.ZodObject<{
    telegram: z.ZodOptional<z.ZodObject<{
        chat_id: z.ZodString;
        /** DM sender ids that route to the general session (invariant #5). */
        dm_user_ids: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
        /** When true, the bare-group (General thread / "All") routes here. */
        owns_general_thread: z.ZodOptional<z.ZodBoolean>;
    }, "strict", z.ZodTypeAny, {
        chat_id: string;
        dm_user_ids?: string[] | undefined;
        owns_general_thread?: boolean | undefined;
    }, {
        chat_id: string;
        dm_user_ids?: string[] | undefined;
        owns_general_thread?: boolean | undefined;
    }>>;
}, "strict", z.ZodTypeAny, {
    telegram?: {
        chat_id: string;
        dm_user_ids?: string[] | undefined;
        owns_general_thread?: boolean | undefined;
    } | undefined;
}, {
    telegram?: {
        chat_id: string;
        dm_user_ids?: string[] | undefined;
        owns_general_thread?: boolean | undefined;
    } | undefined;
}>;
export type GeneralRow = z.infer<typeof GeneralRow>;
/**
 * Who answers chat for the live umbrellas:
 *   - `session` (default) — chat MIRRORS the live MCP session: when a Claude
 *     Code session is open for the umbrella it claims the lease + its Stop-hook
 *     drive answers (in whatever mode that session runs — e.g. subscription).
 *   - `headless` — the live session does NOT claim the lease; the dedicated
 *     headless agent-bridge answers instead (even with no terminal open).
 * Changeable in `channels.json` per the "I should be able to change on
 * configuration" requirement; absent ⇒ `session`.
 */
export declare const ResponderMode: z.ZodEnum<["session", "headless"]>;
export type ResponderMode = z.infer<typeof ResponderMode>;
export declare const ChannelsConfig: z.ZodObject<{
    v: z.ZodLiteral<1>;
    umbrellas: z.ZodArray<z.ZodObject<{
        /** Stable umbrella id (e.g. `loop`). MUST NOT be the reserved `general`. */
        id: z.ZodEffects<z.ZodString, string, string>;
        /** Absolute cwd prefixes that belong to this umbrella (cwd→umbrella). */
        members: z.ZodArray<z.ZodString, "many">;
        telegram: z.ZodOptional<z.ZodObject<{
            /** Supergroup/chat id (negative string for supergroups). */
            chat_id: z.ZodString;
            /** Forum topic id; omitted ⇒ the umbrella owns the bare-group key. */
            topic_id: z.ZodOptional<z.ZodNumber>;
        }, "strict", z.ZodTypeAny, {
            chat_id: string;
            topic_id?: number | undefined;
        }, {
            chat_id: string;
            topic_id?: number | undefined;
        }>>;
    }, "strict", z.ZodTypeAny, {
        id: string;
        members: string[];
        telegram?: {
            chat_id: string;
            topic_id?: number | undefined;
        } | undefined;
    }, {
        id: string;
        members: string[];
        telegram?: {
            chat_id: string;
            topic_id?: number | undefined;
        } | undefined;
    }>, "many">;
    general: z.ZodOptional<z.ZodObject<{
        telegram: z.ZodOptional<z.ZodObject<{
            chat_id: z.ZodString;
            /** DM sender ids that route to the general session (invariant #5). */
            dm_user_ids: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
            /** When true, the bare-group (General thread / "All") routes here. */
            owns_general_thread: z.ZodOptional<z.ZodBoolean>;
        }, "strict", z.ZodTypeAny, {
            chat_id: string;
            dm_user_ids?: string[] | undefined;
            owns_general_thread?: boolean | undefined;
        }, {
            chat_id: string;
            dm_user_ids?: string[] | undefined;
            owns_general_thread?: boolean | undefined;
        }>>;
    }, "strict", z.ZodTypeAny, {
        telegram?: {
            chat_id: string;
            dm_user_ids?: string[] | undefined;
            owns_general_thread?: boolean | undefined;
        } | undefined;
    }, {
        telegram?: {
            chat_id: string;
            dm_user_ids?: string[] | undefined;
            owns_general_thread?: boolean | undefined;
        } | undefined;
    }>>;
    responder: z.ZodOptional<z.ZodEnum<["session", "headless"]>>;
}, "strict", z.ZodTypeAny, {
    umbrellas: {
        id: string;
        members: string[];
        telegram?: {
            chat_id: string;
            topic_id?: number | undefined;
        } | undefined;
    }[];
    v: 1;
    general?: {
        telegram?: {
            chat_id: string;
            dm_user_ids?: string[] | undefined;
            owns_general_thread?: boolean | undefined;
        } | undefined;
    } | undefined;
    responder?: "session" | "headless" | undefined;
}, {
    umbrellas: {
        id: string;
        members: string[];
        telegram?: {
            chat_id: string;
            topic_id?: number | undefined;
        } | undefined;
    }[];
    v: 1;
    general?: {
        telegram?: {
            chat_id: string;
            dm_user_ids?: string[] | undefined;
            owns_general_thread?: boolean | undefined;
        } | undefined;
    } | undefined;
    responder?: "session" | "headless" | undefined;
}>;
export type ChannelsConfig = z.infer<typeof ChannelsConfig>;
/** On-disk path for the single authoritative routing source. */
export declare const channelsConfigPath: () => string;
/**
 * Load + validate `~/.opensquid/channels.json`. ENOENT, malformed JSON, or a
 * schema-invalid file all return null — a broken/absent config must never
 * crash the daemon (it degrades to "everything orphans"), matching the
 * fail-quiet posture of the legacy loader + `readInbox`.
 */
export declare function loadChannelsConfig(): Promise<ChannelsConfig | null>;
/** Telegram fields a resolver needs from a normalized inbound message. */
export interface InboundTelegramKey {
    platform: 'telegram';
    /** Supergroup/chat id as a string. */
    chatId: string;
    /** Forum topic id, if the message carried one. */
    topicId?: number;
    /** Sender's user id (for DM resolution). */
    senderId: string;
    /** True iff this is a private chat (Telegram: `chat.id === from.id`). */
    direct: boolean;
}
/**
 * Resolve an inbound Telegram message to its umbrella (or the general
 * session, or null = orphan). Order of specificity:
 *
 *   1. DM (`direct`) whose sender is in `general.telegram.dm_user_ids`
 *      → `'general'` (#5). A DM never routes to a project umbrella.
 *   2. Supergroup + topic exactly matching one umbrella's
 *      `(chat_id, topic_id)` → that umbrella (#3, one topic ↔ one umbrella).
 *   3. Supergroup with no topic (or matching the general chat) when
 *      `general.telegram.owns_general_thread` → `'general'` (General/All).
 *   4. No match → null (orphan inbox).
 *
 * Deterministic: the (chat_id, topic_id) → umbrella map is a structural
 * 1:1, so there is no "later wins" collision to resolve at runtime.
 */
export declare function resolveInboundUmbrella(cfg: ChannelsConfig, key: InboundTelegramKey): UmbrellaTarget | null;
/**
 * Resolve an umbrella (or general) → its outbound Telegram target (reply-to-
 * source). The agent never chooses a channel; CAT.3 derives the destination
 * from the umbrella the turn belongs to. Returns null when the target has no
 * telegram binding (caller drops or falls back).
 */
export declare function resolveOutbound(cfg: ChannelsConfig, target: UmbrellaTarget): TelegramTarget | null;
/**
 * Resolve a session's cwd → its umbrella id (or general). Longest-prefix
 * match over every umbrella's `members`, so a nested member cwd wins over a
 * shorter sibling. Returns null when no umbrella claims the cwd (the session
 * has no chat binding). Replaces per-cwd `resolveProjectUuid` for chat.
 */
export declare function resolveUmbrellaForCwd(cfg: ChannelsConfig, cwd: string): UmbrellaTarget | null;
export {};
//# sourceMappingURL=routing.d.ts.map