/** * Shared types for the multi-channel messaging system. */ export type ChannelType = 'whatsapp' | 'telegram' | 'alexa'; export type SenderRole = 'admin' | 'customer' | 'assistant'; export interface ChannelConfig { enabled: boolean; /** 'channel' = just talk to me (self-chat only), 'business' = admin/customer mode, 'assistant' = personal assistant in conversations */ mode: 'channel' | 'business' | 'assistant'; /** Phone numbers with admin access (owner, secretary, etc.) — business mode only */ admins?: string[]; /** Active skill for customer-facing mode (folder name in workspace/skills/) */ skill?: string; /** Opt-in: process messages in group chats (default false). Channel mode ignores this. */ allowGroups?: boolean; /** Assistant mode only. When false (default) ONLY the account owner (fromMe) can trigger * the agent with `@botname`. When true, ANYONE who tags the bot (DM or group) can drive it. * DANGER: the triggerer gains full control of an agent that can run Bash, edit files, etc. * Enable only for fully trusted shared use. See the WhatsApp SKILL.md disclaimer. */ allowOthersToTrigger?: boolean; } export interface InboundMessageAttachment { /** 'image' → inline vision block; 'file' → any document the agent reads from disk. */ type: 'image' | 'file'; mediaType: string; data: string; // base64 /** Original filename when the channel provides one (WhatsApp/Telegram documents). */ name?: string; } export interface InboundMessage { channel: ChannelType; /** Sender identifier (phone number) */ sender: string; /** Sender display name if available */ senderName?: string; /** Resolved role of the sender */ role: SenderRole; /** Message text content (may include enriched context for assistant triggers) */ text: string; /** Display-friendly text for DB/chat UI (when text contains enriched agent context) */ displayText?: string; /** Raw sender JID (channel-specific format, used for replies) */ rawSender: string; /** Image attachments */ attachments?: InboundMessageAttachment[]; /** Original channel-native message key (Baileys WAMessageKey for WhatsApp). Opaque here. */ inboundKey?: unknown; /** True when the chat is a group (for assistant-mode group routing). */ isGroup?: boolean; } export interface OutboundMessage { channel: ChannelType; to: string; text: string; } export interface ChannelStatus { channel: ChannelType; connected: boolean; /** Additional info like phone number, QR state, etc. */ info?: Record; } /** * Per-turn routing target. * * Every user message pushed into a live conversation carries one of these so the * agent's response goes back to the surface that triggered it — no FIFO ambiguity, * no channel-bleed across concurrent turns. * * Created by the surface that pushes the user message (chat-WS or channel handler), * consumed by the manager when the corresponding `bot:response` fires, discarded if * the turn ends without a response (error / empty turn). */ export interface RoutingTarget { /** Which surface triggered this turn. Drives whether the WA reply carries a "🤖 Bot:" prefix. * 'workspace' is a dashboard surface like 'chat' (broadcast-driven, optional WA self-chat mirror) * but isolated for telemetry / future per-surface routing. */ surface: 'chat' | 'whatsapp' | 'alexa' | 'telegram' | 'workspace'; /** WhatsApp JID to deliver the reply to. * - 'whatsapp' surface → the originating chat JID (group or peer). * - 'chat' surface → optionally the user's own number (self-chat mirror), or undefined. * When undefined, no WhatsApp send happens — the reply only reaches the dashboard via broadcast. */ waSendTo?: string; /** Telegram chat id to deliver the reply to ('telegram' surface). */ telegramChatId?: string; /** Telegram message id of the inbound that triggered this turn (for reply-to / reactions). */ telegramMessageId?: number; isGroup?: boolean; isSelfChat?: boolean; /** When set, the assistant's reply is appended to this customer buffer (assistant-mode context). */ assistantBufferKey?: string; /** Original inbound WA message key — kept opaque here, used by the channel to react/quote. */ inboundKey?: unknown; /** Unix ms when this target was pushed — used to detect stale queue entries. */ pushedAt?: number; } export interface ChannelProvider { readonly type: ChannelType; /** Start the channel connection (may trigger QR flow) */ connect(): Promise; /** Disconnect and clean up */ disconnect(): Promise; /** Send a text message */ sendMessage(to: string, text: string): Promise; /** Get current connection status */ getStatus(): ChannelStatus; /** Get current QR code data (base64 SVG) or null if not in QR state */ getQrCode(): string | null; /** Whether auth credentials exist (previously connected) */ hasCredentials(): boolean; }