/**
 * AgentTelegramComms — agent-to-agent Telegram comms primitive.
 *
 * Spec: docs/specs/MENTOR-LIVE-READINESS-SPEC.md §Fix 2a (approved 2026-05-27).
 *
 * A robust, recipient-knows-it's-from-an-agent channel any agent can use to message
 * another agent's bot over Telegram, with the indicator AND the anti-loop machinery as
 * first-class infra. Justin's framing: "robust infra that indicates the message is from
 * another agent… leverage infra to prevent the ping pong trap." The mentor is its first
 * consumer (see MentorOnboarding*), but nothing here is mentor-specific.
 *
 * Every message carries a VISIBLE marker prefix in the body so (a) humans can audit at a
 * glance and (b) the Telegram chat history alone reconstructs a round-trip when ledgers
 * are unavailable:
 *
 *   [a2a:from=<sender> to=<recipient> role=<role> id=<id> corr=<corr> ts=<unix-ms> v=1]
 *
 *   <body>
 *
 * This file is the PURE logic (marker parse/format + routing decision + cycle-detection
 * key). I/O (the actual Telegram send + the audit ledgers + the processed-id store) is
 * injected so every branch is unit-testable. See §Fix 2a for the routing matrix and the
 * anti-loop invariants this enforces.
 */
/** Current marker schema version. Bumps are explicit (reader-first rule — see spec). */
export declare const A2A_VERSION = 1;
export interface A2aMarker {
    from: string;
    to: string;
    role: string;
    id: string;
    corr: string;
    ts: number;
    v: number;
}
export interface A2aMessage extends A2aMarker {
    body: string;
}
export interface A2aSendFields {
    from: string;
    to: string;
    role: string;
    /** Stable id; minted by the caller/sender if absent. */
    id: string;
    /** Correlation id. Prompts self-correlate (corr === id); replies carry the prompt's id. */
    corr: string;
    ts: number;
}
/**
 * Result of parsing an inbound Telegram message body for an a2a marker.
 * - `no-marker`: not an agent message → caller falls through to normal user handling.
 * - `malformed`: marker-shaped but invalid → caller DROPS (security event), never falls
 *   through to user handling (spoof / broken-sender defense).
 */
export type ParseResult = {
    ok: true;
    msg: A2aMessage;
} | {
    ok: false;
    kind: 'no-marker';
} | {
    ok: false;
    kind: 'malformed';
    detail: string;
};
/** Format a marker + body for sending. The inverse of {@link parseMarker}. */
export declare function formatMarker(fields: A2aSendFields, body: string): string;
/** Parse an inbound message body. Strict — see {@link ParseResult}. */
export declare function parseMarker(raw: string): ParseResult;
/** Per-recipient routing configuration (spec §Fix 2a recipient + anti-loop). */
export interface RecipientConfig {
    /** This agent's own name (the `to` an inbound message must target). */
    localAgent: string;
    /** Known agents → their Telegram bot identity. Only senders here are trusted. */
    knownAgents: Record<string, {
        botId: string;
    }>;
    /**
     * Per-source role acceptance (round-2 adversarial F6): `{ fromAgent: allowedRoles[] }`.
     * A compromised/buggy known agent sending an unexpected role is dropped, even if that
     * role is accepted from a DIFFERENT source. Scoped admission.
     */
    acceptRoles: Record<string, string[]>;
    /** Replay window: reject markers whose ts is outside ±this from now. Default 24h. */
    skewWindowMs: number;
    /** Highest marker version this recipient understands. Newer → dead-letter, not crash. */
    maxVersion: number;
}
/**
 * Inbound message context the caller resolves from the Telegram update. The bot-identity
 * fields are the SPOOF DEFENSE: a human user typing a marker-shaped string has
 * `senderIsBot === false` and no `senderChatId` → dropped (round-2 adversarial F1).
 */
export interface IncomingContext {
    /** The raw message body (marker + body, or arbitrary user text). */
    raw: string;
    /** `from.is_bot` from the Telegram update. */
    senderIsBot: boolean;
    /** `sender_chat.id` from the Telegram update when present (group bot-as-channel relay).
     *  Its mere presence is part of the spoof defense (a human user has none). */
    senderChatId?: string;
    /** The effective sender bot identity = `sender_chat.id ?? from.id`. Matched against the
     *  allowlist's `botId`. The caller resolves this from the Telegram update. */
    senderBotId?: string;
    /** Wall-clock now (ms) — injected for testability. */
    now: number;
}
export type RouteDropReason = 'agent-marker-malformed' | 'agent-marker-stale-or-future' | 'agent-marker-spoofed-by-user' | 'agent-marker-wrong-recipient' | 'agent-marker-unsupported-version' | 'agent-marker-unknown' | 'agent-marker-duplicate' | 'agent-marker-role-not-allowed-from-source' | 'agent-marker-unexpected-role' | 'agent-marker-unknown-role';
export type RouteDecision = {
    action: 'fall-through';
} | {
    action: 'route';
    msg: A2aMessage;
} | {
    action: 'drop';
    reason: RouteDropReason;
    detail?: string;
    msg?: A2aMessage;
};
/**
 * The recipient routing matrix (spec §Fix 2a "Routing matrix"). PURE — every branch is a
 * decision the caller then acts on (route to a role-handler, or drop + write an audit
 * row). `knownRole` tells whether the role is one this recipient has a handler for at all;
 * `isProcessed` is the idempotency check against the processed-id store.
 */
export declare function decideRoute(ctx: IncomingContext, cfg: RecipientConfig, deps: {
    isProcessed: (id: string) => boolean;
    knownRole: (role: string) => boolean;
}): RouteDecision;
/**
 * Cycle-detection key (spec §Fix 2a anti-loop #3): keyed on the full tuple so legitimate
 * unrelated traffic never collides. `corr` is always present (required in the marker), so
 * the key cannot collapse to `undefined`.
 */
export declare function cycleKey(fields: {
    fromBotId: string;
    toBotId: string;
    topicId: number | string;
    role: string;
    corr: string;
}): string;
/**
 * In-memory cycle detector. A send is refused (unless `cycleOk`) when the same key was
 * seen within `windowMs` — the structural guard against tight role↔reply ping-pong.
 */
export declare class CycleDetector {
    private readonly windowMs;
    private readonly recent;
    constructor(windowMs: number);
    /** Returns true if sending this key now would trip cycle-detection. */
    wouldTrip(key: string, now: number): boolean;
    /** Record a send/receive of this key at `now`. */
    mark(key: string, now: number): void;
    private evict;
}
export interface SendAgentMessageInput {
    fromAgent: string;
    toAgent: string;
    /** A role the caller is allowed to send (see allowedRoles). */
    role: string;
    /** Recipient bot's topic to write into. */
    toTopicId: number;
    /** Prompt content. Markdown is fine — the body is sent verbatim under the marker. */
    message: string;
    /** Optional: an existing stable id for this send (else a UUID is minted as a2aId). */
    id?: string;
    /** Optional: correlation id (for a reply, set to the prompt's id). Defaults to id. */
    correlationId?: string;
}
export interface SendAgentMessageDeps {
    /** Adapter-specific send. Must return a `messageId` on success. */
    send: (toTopicId: number, text: string) => Promise<{
        ok: true;
        messageId: string;
    } | {
        ok: false;
        error: unknown;
    }>;
    /** Append one JSONL row to the sent-audit ledger (caller chooses path/storage). */
    appendAudit: (row: SendAuditRow) => void;
    /** Wall-clock now (ms) — injected. */
    now: () => number;
    /** UUID minter — injected so tests are deterministic. */
    mintId: () => string;
    /** Allowed-outbound-roles for THIS caller (constructor-time list). The role-handler
     *  cannot send any role not in this list — refused at runtime, no marker formed. */
    allowedRoles: ReadonlySet<string>;
    /** The bot token to scrub from any error message before it leaves this module
     *  (round-2 adversarial F5: Telegram 401s sometimes echo the token). */
    botToken?: string;
    /** Telegram identifiers for the audit row + future cycle-detection wiring. */
    fromBotId: string;
    toBotId: string;
}
export interface SendAuditRow {
    localTs: string;
    direction: 'sent';
    fromAgent: string;
    toAgent: string;
    role: string;
    id: string;
    corr: string;
    ts: number;
    telegramFromBotId: string;
    telegramToBotId: string;
    topicId: number;
    result: 'ok' | 'failed' | 'role-refused';
    sentMessageId?: string;
    reason?: string;
}
export interface SendAgentMessageResult {
    ok: boolean;
    a2aId?: string;
    sentMessageId?: string;
    reason?: string;
}
/**
 * Send an agent-to-agent Telegram message. Formats the marker, calls the injected
 * adapter, writes the sent-audit row (always — even on refusal/failure), and scrubs the
 * bot token from any error surface before returning.
 *
 * Anti-loop invariant (spec §Fix 2a anti-loop #1): refuses any `role` not in the caller's
 * `allowedRoles` set at runtime — no marker is formed, no send is attempted, an audit row
 * is written with `result: 'role-refused'`. A role-handler that consumes role X cannot
 * therefore send role X (the import-surface lint is a backup; this is the runtime guard).
 */
export declare function sendAgentMessage(input: SendAgentMessageInput, deps: SendAgentMessageDeps): Promise<SendAgentMessageResult>;
/** Scrub the bot token from a string so it can't leak through logs / audit / DegradationReporter
 *  (round-2 adversarial F5: Telegram 401 response bodies sometimes echo the token). The
 *  token is fixed-length and high-entropy enough that a literal-string replace is enough;
 *  we strip the trailing path segment defensively in case only part is echoed. */
export declare function scrubToken(s: string, token?: string): string;
//# sourceMappingURL=AgentTelegramComms.d.ts.map