import type { ConversationId, MessageId, ProfileId, SubjectId, TenantId, UserId } from './ids.js'; import type { ActionId } from './ids.js'; export interface ProductItem { title: string; subtitle?: string; price?: string; image?: string; url?: string; } export interface CardField { label: string; value: string; } /** A reference to an action a card/quick-reply can invoke. */ export interface InlineActionRef { actionId: ActionId; label: string; } export type MessageContent = { kind: 'text'; text: string; enc?: boolean; iv?: string; quickReplies?: string[]; citations?: { source: string; }[]; } | { kind: 'attachment'; url: string; mime: string; name?: string; size?: number; } | { kind: 'card'; title?: string; body?: string; fields?: CardField[]; actions?: InlineActionRef[]; } | { kind: 'form'; prompt: string; actionId: ActionId; prefill?: Record; } | { kind: 'system'; event: string; data?: Record; } | { kind: 'appointment'; title: string; startIso: string; endIso: string; location?: string; description?: string; googleUrl: string; icalUrl: string; confirmed?: boolean; } | { kind: 'products'; title?: string; items: ProductItem[]; }; export type SenderRole = 'guest' | 'agent' | 'system' | 'bot'; export interface Message { id: MessageId; conversationId: ConversationId; seq: number; senderId: UserId; senderRole: SenderRole; content: MessageContent; ts: number; replyToId?: MessageId; editedAt?: number; deletedAt?: number; reactions?: Record; internal?: boolean; } export interface Conversation { id: ConversationId; tenantId: TenantId; profileId: ProfileId; subjectId?: SubjectId; guestId: UserId; /** Host-supplied display info for an identified guest (widget `user` option). * Display metadata only — identity is still the token/guestId. */ guestName?: string; guestEmail?: string; guestAvatar?: string; guestMeta?: Record; /** True once the guest's identity has been proven by a signed ES256 JWT * against the chatroom's guestPublicKey (secure identity mode). */ guestVerified?: boolean; participants: UserId[]; assignedAgentId?: UserId; aiActive?: boolean; state: string; firstResponseAt?: number; csat?: number; lastSeq: number; tags?: string[]; /** Live sentiment of the guest's most recent message (best-effort, async). */ sentiment?: 'positive' | 'neutral' | 'frustrated'; /** -1 (very frustrated) .. +1 (very positive); paired with `sentiment`. */ sentimentScore?: number; /** Set once an SLA-breach escalation macro has fired, so it only fires once. */ slaEscalatedAt?: number; /** If set, the conversation is snoozed until this Unix ms timestamp. * Hidden from the inbox until the timestamp passes, then resurfaces. */ snoozedUntil?: number; /** Page URL where the widget was open when the conversation started. */ pageUrl?: string; /** Browser tab title at conversation start — gives agents context. */ pageTitle?: string; createdAt: number; updatedAt: number; } export interface Subject { id: SubjectId; tenantId: TenantId; title: string; state: string; fields: Record; /** URL of the page where the subject lives (e.g. the listing page URL). * Captured automatically by the widget and stored on first open. */ url?: string; createdAt: number; updatedAt: number; } /** States in which a conversation is no longer "open": it's done with, so inbox * / proactive sweeps skip it and load balancing frees the assigned agent. The * single source of truth for "is this conversation finished?". */ export declare const TERMINAL_STATES: ReadonlySet; export declare function isTerminalState(state: string): boolean; export type Channel = 'widget' | 'email' | 'sms' | 'whatsapp' | 'instagram' | 'kakao' | 'messenger' | 'line';