/**
 * ChatV2DispatcherService — deliver user-origin chat messages to the
 * bound agent session.
 *
 * Flow:
 *   HTTP POST /api/chat/channels/:id/messages
 *     → ChatV2Service.sendMessage (persist + dedupe)
 *     → controller broadcasts WS message frame (user bubble)
 *     → controller calls ChatV2Dispatcher.dispatchToAgent(channel, messageDTO)
 *     → sendMessageToAgent(agent_session, "[CHAT:<id>] <content>\n<hint>")
 *     → agent processes + calls `reply-chat` skill
 *     → skill POSTs back to /api/chat/channels/:id/messages as the agent
 *     → same controller path → WS message frame (agent bubble)
 *
 * This service is intentionally thin — a couple of dozen lines of glue
 * around the AgentRegistrationService's `sendMessageToAgent` primitive.
 * The dispatch prompt is extracted so tests can assert the format, which
 * is the contract the `reply-chat` skill relies on.
 *
 * @module services/chat-v2/chat-v2.dispatcher.service
 */
import type { ChatChannelDTO, ChatMessageDTO } from './types.js';
import type { ChatV2MentionResolver, MentionTarget } from './chat-v2.mention-resolver.js';
/**
 * Minimal surface the dispatcher needs from the agent-registration service.
 * Typed explicitly so tests can pass a stub without pulling in the full
 * AgentRegistrationService class.
 */
export interface AgentMessageSink {
    sendMessageToAgent(sessionName: string, message: string): Promise<{
        success: boolean;
        error?: string;
        message?: string;
        queued?: boolean;
    }>;
}
/** Result returned by `dispatchToAgent`. */
export interface DispatchResult {
    /** True when the runtime accepted the message (or queued it for delivery). */
    dispatched: boolean;
    /** Filled when `dispatched === false`. */
    error?: string;
}
/** One per-recipient outcome produced by `dispatchMessage`. */
export interface MentionDispatchOutcome {
    /** The mention target the dispatcher attempted to deliver to. */
    target: MentionTarget;
    /** Underlying delivery result from `AgentMessageSink.sendMessageToAgent`. */
    dispatched: boolean;
    /** Error message, if delivery failed. */
    error?: string;
}
/**
 * Phase B-2 (2026-05-17) — one per-member outcome for huddle dispatch.
 *
 * Huddles don't go through `MentionResolver` — their roster is read
 * directly from `chat_channel_members`. The outcome records whether
 * the member was @-mentioned (which controls `responseMode` in the
 * prompt) and whether the underlying sink call succeeded.
 */
export interface HuddleDispatchOutcome {
    /** Member's agent session name. */
    sessionName: string;
    /**
     * `'required'` when the member appeared in `message.mentions[]`
     * (they MUST reply); `'optional'` otherwise (they MAY reply if it
     * makes sense). Surfaced in the formatted prompt.
     */
    responseMode: 'required' | 'optional';
    /** Whether the prompt was successfully queued for the member. */
    dispatched: boolean;
    /** Error message, if delivery failed. */
    error?: string;
}
/**
 * Aggregate result of `dispatchMessage`. Reports the chosen routing
 * strategy and per-recipient outcomes so callers (and tests) can assert
 * exactly what fan-out happened without re-running the resolver.
 */
export interface DispatchMessageResult {
    /**
     * - `'dm'`: legacy 1:1 path — used by `type='dm'` channels.
     * - `'channel-mentions'`: Phase C BE.3 fan-out — used by `type='channel'`
     *   channels with at least one resolved mention.
     * - `'skip'`: nothing was dispatched (non-user message, no recipients,
     *   archived/unbound channel, etc.). `reason` is filled.
     */
    strategy: 'dm' | 'channel-mentions' | 'huddle-broadcast' | 'skip';
    /** True when at least one underlying delivery succeeded. */
    dispatched: boolean;
    /** Filled when `strategy === 'skip'`. */
    reason?: string;
    /** Populated for `strategy === 'dm'`. */
    dmResult?: DispatchResult;
    /**
     * Populated for `strategy === 'channel-mentions'`. One entry per
     * resolved target. Empty when no mentions resolved.
     */
    mentionOutcomes?: MentionDispatchOutcome[];
    /**
     * Phase B-2 — populated for `strategy === 'huddle-broadcast'`. One
     * entry per huddle member. Mentioned members carry
     * `responseMode: 'required'`; the rest are `'optional'`.
     */
    huddleOutcomes?: HuddleDispatchOutcome[];
}
/** Constructor options for {@link ChatV2DispatcherService}. */
export interface ChatV2DispatcherOptions {
    agentSink: AgentMessageSink;
    /**
     * Override the prompt formatter for tests / future customization. The
     * default matches the `reply-chat` skill's parser exactly.
     */
    formatPrompt?: (args: FormatPromptArgs) => string;
    /**
     * Phase C BE.3 — resolver used when dispatching a `type='channel'`
     * message to its mentioned recipients. When omitted, channel-typed
     * messages skip dispatch entirely (preserves the BE.1+BE.2 contract
     * for callers that haven't wired the resolver yet, e.g. legacy unit
     * tests). The production composition root injects a resolver backed
     * by `StorageService.getTeams()`.
     */
    mentionResolver?: ChatV2MentionResolver;
    /**
     * Phase B-2 (2026-05-17) — roster lookup used when dispatching a
     * `type='huddle'` message to every member. Synchronous because the
     * production implementation is a direct SQLite read against
     * `chat_channel_members`. When omitted, huddle messages skip
     * dispatch (mirrors how `mentionResolver` gates `'channel'` rows).
     *
     * Returns the list of `agentSession` ids for the huddle's members.
     * Order doesn't matter — the dispatcher loops over the whole list.
     */
    huddleMembersFor?: (channelId: string) => readonly string[];
    /**
     * Activate-on-send hook. When a DM dispatch fails because the agent's
     * session doesn't exist (inactive), the dispatcher calls this to start the
     * agent, then retries delivery once. Returns true when the agent is now
     * active. User-initiated, so it bypasses the orchestrator wake-gate by
     * design. When omitted, inactive sends just queue (legacy behavior).
     */
    activateAgent?: (agentSession: string) => Promise<boolean>;
}
/** Inputs to the prompt formatter. */
export interface FormatPromptArgs {
    channelId: string;
    channelName: string;
    agentSession: string;
    senderId: string;
    content: string;
    clientMessageId?: string;
    /**
     * Phase B-2 — in a huddle, every member receives the prompt but
     * only the @-mentioned ones MUST reply. The default formatter
     * appends a "你必须回复" (mandatory) or "你可以选择是否回复" (optional)
     * tag so the agent's prompt-following heuristics can branch. Defaults
     * to `'required'` for DMs and `'channel'` (single-mention) dispatches
     * where every recipient was explicitly addressed.
     */
    responseMode?: 'required' | 'optional';
}
/**
 * Default prompt formatter the agent sees when a user sends in their
 * chat channel.
 *
 * Format is intentionally stable and tag-prefixed so the `reply-chat`
 * skill (and future tooling) can reliably extract the channelId without
 * regex ambiguity. The `回复:` hint line gives the agent a one-step
 * instruction on how to reply.
 */
export declare function defaultFormatPrompt(args: FormatPromptArgs): string;
/**
 * Dispatches user-origin chat messages to the agent session bound to the
 * channel. Invoked from the chat controller after `ChatV2Service.sendMessage`
 * returns.
 */
export declare class ChatV2DispatcherService {
    private readonly agentSink;
    private readonly formatPrompt;
    private readonly mentionResolver?;
    private readonly huddleMembersFor?;
    private readonly activateAgent?;
    private readonly logger;
    constructor(options: ChatV2DispatcherOptions);
    /**
     * Phase C BE.3 entry point — choose the routing strategy from
     * `channel.type` and the message's mentions, then fan out.
     *
     * Routing rules (SEALED §2.1):
     *   - `type='dm'` → existing 1:1 path via `dispatchToAgent`. Mentions
     *     in DMs are advisory only (the channel already binds to one
     *     agent); the dispatcher does not currently re-route them.
     *   - `type='channel'` → resolve `message.mentions` via the injected
     *     `mentionResolver`, then dispatch the formatted prompt to each
     *     resolved sessionName. No mentions → no dispatch (the message
     *     still persists; nobody is paged).
     *   - Agent-origin messages always skip dispatch (no self-loopback).
     *
     * Always resolves; per-recipient errors are captured in
     * {@link DispatchMessageResult.mentionOutcomes} and never thrown.
     *
     * @param channel - The channel DTO (provides `type`, name, agentSession).
     * @param message - The persisted message DTO (provides senderType, mentions).
     * @returns Aggregate result describing the strategy + outcomes.
     */
    dispatchMessage(channel: ChatChannelDTO, message: ChatMessageDTO): Promise<DispatchMessageResult>;
    /**
     * Phase B-2 — fan a user message out to every member of a huddle.
     *
     * Resolution path:
     *   1. Look up the roster via the injected `huddleMembersFor`. No
     *      resolver wired → return `strategy: 'skip'` (the message still
     *      persists; nobody is paged).
     *   2. Empty roster → same skip path. Should not happen since
     *      `createHuddle` rejects empty membership, but defensive.
     *   3. For each member, derive `responseMode` from whether their
     *      session appears in `message.mentions[]`. Format the prompt
     *      accordingly and call `sendMessageToAgent`.
     *
     * Always resolves; per-member errors are captured in
     * {@link DispatchMessageResult.huddleOutcomes} and never thrown.
     *
     * @param channel - The huddle channel.
     * @param message - The persisted user message.
     * @returns Aggregate result with one outcome per member.
     */
    private dispatchHuddleBroadcast;
    /**
     * Dispatch a `type='channel'` message to its resolved mentions.
     *
     * Skips entirely when:
     *   - No mention resolver is wired (return strategy=skip).
     *   - `mentions` is empty (return strategy=skip).
     *   - The resolver returns no targets (return strategy=channel-mentions
     *     with empty `mentionOutcomes`; `dispatched=false`).
     *
     * @param channel - The team channel.
     * @param message - The persisted user message.
     * @returns Aggregate result with one outcome per resolved recipient.
     */
    private dispatchChannelMentions;
    /**
     * Push a newly-persisted user message to the agent.
     *
     * Dispatch is fire-and-forget at the API-response level (the HTTP 201 has
     * already been serialized), but we still await the write so tests can
     * observe the outcome and the logger can record failures.
     *
     * Messages where `senderType !== "user"` are a no-op — we never loop an
     * agent's own reply back into its own PTY.
     *
     * @deprecated Issue #334 — `dispatchToAgent` is the legacy 1:1 entry
     * point. New callers MUST go through `dispatchMessage`, which routes
     * type='dm' messages here and adds the type='channel' (@mentions /
     * @team) path. Direct callers of `dispatchToAgent` silently bypass
     * the mention-resolution layer.
     *
     * Conversion: replace `dispatchToAgent(channel, message)` with
     * `dispatchMessage(channel, message)` and read the `dmResult` field
     * of the returned object. See chat-v2.dispatcher.service.ts:dispatchMessage
     * for the contract.
     *
     * @param channel - Channel DTO (provides `agentSession` + display name)
     * @param message - Persisted message DTO
     */
    dispatchToAgent(channel: ChatChannelDTO, message: ChatMessageDTO): Promise<DispatchResult>;
}
//# sourceMappingURL=chat-v2.dispatcher.service.d.ts.map