import type { MessengerAdapter } from './types.js'; import { type OutboxKind, type OutboxPriority } from './outbox.js'; export type SinkKind = OutboxKind; export type SinkPriority = OutboxPriority; export interface SinkPayload { threadId: string; platform: string; channelId: string; /** For 'text': the message body (string). * For 'card': the card object (will be JSON-encoded for storage). * For 'typing': 'start' or 'stop'. */ payload: string | object; kind: SinkKind; /** Default 'normal'. 'high' tries a synchronous send first and falls * back to the queue only on failure — use for approval / restart * notices where 1s+ worker latency matters. */ priority?: SinkPriority; /** Link this delivery back to a jobs row (inline job / scheduled job). * Phase 2 will use this to mark jobs.status='delivered' when the row * reaches the IM. */ jobId?: number | null; /** Optional explicit thread key. Default is * `${platform}:${channelId}:${threadId}` (matches session.ts). */ threadKey?: string; /** Optional idempotency key forwarded to outbox.enqueue. When set, a * repeated deliver with the same key is a no-op (no duplicate row). Used by * reminder firing so a reminder resurrected from 'firing' after a crash * can't enqueue the same message twice. Only meaningful for the queued * (non-high-priority) path. */ dedupKey?: string; /** Optional already-resolved adapter. When provided, sink uses it * directly for the sync attempt instead of consulting the global * registry. Required by callers that maintain their own resolution * indirection (e.g. approval-router.install accepts a custom * resolveMessenger hook used by tests + multi-tenant setups). The * outbox row is still recorded with platform=; if the sync * attempt fails, the worker re-resolves via registry the normal way. */ adapter?: MessengerAdapter; /** * Origin of the message. Determines whether the viewer router gets to * truncate-and-link the payload. * - 'agent' → freeform LLM output. Long replies may route to the * web viewer; the IM gets a summary + link. * - 'system' (default) → operational confirmations, approval cards, * reminders, restart notices, error blurbs, command * outputs. ALWAYS inline, regardless of length — * truncating these would hide the actual answer (e.g. * a memo-creation confirmation, an approval prompt, an * error message). Default is 'system' so any new * callsite stays inline unless the dev opts in. */ source?: 'agent' | 'system'; } export interface SinkResult { /** Row id in the outbox table. -1 if persistence is unavailable AND the * sync fallback also failed (the rare double-failure case — caller has * no recourse beyond logging). */ outboxId: number; /** True = the message was sent inline before this call returned (only * possible when priority='high' and the sync attempt succeeded). * False = queued for the worker. */ immediate: boolean; } /** Resolve a platform name (e.g. 'wechat' / 'wechat-ilink' / 'feishu') to * the actual MessengerAdapter. Exact match first, then a fuzzy `${name}-*` * / `${name}_*` prefix walk so reminders.ts / scheduler / web callers can * pass the generic platform ('wechat') and still hit 'wechat-ilink'. Mirrors * the helper that already exists in reminders.ts:873 — kept in sink so the * rest of the codebase can stop caring about adapter naming. * * Exported for tests; production callers go through deliver(). */ export declare function resolveMessenger(platform: string): MessengerAdapter | undefined; export declare function deliver(p: SinkPayload): Promise; export declare function startWorker(): void; export declare function stopWorker(): void; /** Test hook — run one tick synchronously. */ export declare function _tickWorkerForTests(): Promise; export declare const sink: { deliver: typeof deliver; startWorker: typeof startWorker; stopWorker: typeof stopWorker; }; //# sourceMappingURL=message-sink.d.ts.map