/** * Maps a message the agent POSTED (`channelId` + Slack `ts`) back to the * conversation that produced it, so a later in-thread reply can be resolved to * that conversation and continue its history/session. * * Why this exists: a scheduled/proactive post (e.g. a daily digest) runs under a * synthetic conversationId (e.g. `scheduled-scan`) and posts via * `SlackSendMessage`, which registers no `slack:` conversation. When the user * replies, the Slack adapter derives `slack::` — an id with no * history. This index closes that gap: the producer records `(channel, ts) → * producing conversationId`; the consumer (inbound dispatch) looks it up and * aliases the reply onto the producing conversation. * * Storage is a JSONL file inside the run-artifact dir. Appenders, readers, * startup maintenance, recovery, and compaction serialize through one * owner-only SQLite coordinator. Compaction appends a durable recovery trailer * to the verified index inode, then rewrites that same inode through its pinned * descriptor; it never promotes or later reopens a source pathname. The index * and coordinator are opened no-follow and must remain current-owner, regular, * single-link files. These files are ignored by the `.summary.json` artifact * scanners (see `seen-conversations.ts`), so they never collide with run-artifact * tooling. */ export declare const POSTED_MESSAGE_INDEX_FILENAME = "posted-message-index.jsonl"; interface PostedMessageIndexCompactionHooks { readonly beforePrepare?: () => Promise; /** Test-only boundary after recovery bytes but before the commit footer. */ readonly afterPrepareBody?: () => Promise; readonly beforeReplace?: () => Promise; /** Test-only boundary after the final pathname checks and before pinned-FD rewrite. */ readonly afterPromotionChecks?: (indexPath: string) => Promise; /** Test-only boundary after the replacement prefix is durable but before truncate. */ readonly afterRewriteSync?: () => Promise; } export interface PostedMessageEntry { /** Slack channel/DM id the message was posted to. */ readonly channelId: string; /** Slack message timestamp returned by `chat.postMessage`. */ readonly ts: string; /** Producing conversationId, de-bucketed to its base form. */ readonly conversationId: string; /** ISO timestamp of when the entry was written. */ readonly writtenAt: string; /** * The SEND this post belongs to — the `ts` of its first message. Present only * on the trailing chunks of a message Slack forced us to split, so several * chunks of one send count as one claim on the producing conversation rather * than as competing posts. Absent (and therefore equal to `ts`) for every * ordinary single-message post. */ readonly groupId?: string; } /** The single index-file path both producer and consumer agree on. */ export declare function resolvePostedMessageIndexPath(artifactDir: string): string; /** Strip a trailing daily-rollover bucket so the stored id is the base producing id. */ export declare function basePostedConversationId(conversationId: string): string; /** * Record that `conversationId` posted a message at `(channelId, ts)`. Appenders in * both the adapter and its stdio child share a filesystem lock with compaction, so * a rewrite cannot discard a completed concurrent append. Once the cap is reached, * compaction drops a batch of oldest entries before appending; this keeps the file * at or below the cap without a full rewrite on every later send. * * Best-effort: a failed index write or lock acquisition must never fail the Slack * post, so this function swallows errors. The stored conversationId is de-bucketed * so the consumer can let the responder re-bucket to the reply's own day * (consistent with daily session rollover). */ export declare function appendPostedMessage(indexPath: string, entry: { channelId: string; ts: string; conversationId: string; groupId?: string; }, now?: () => Date, maxEntries?: number, compactionHooks?: PostedMessageIndexCompactionHooks): Promise; /** * Resolve the producing conversationId for a posted message, newest write wins. * Returns `undefined` when the file is missing, has no matching entry, or maps * several posts onto the matched conversation (an ambiguous alias is no alias — * see below), so the caller falls back to the default (a fresh `slack:` * conversation) — no regression. */ export declare function lookupProducingConversation(indexPath: string, channelId: string, ts: string): Promise; /** * Bound file growth by rewriting the index with only the newest `maxEntries` * (by write time, de-duped to the newest entry per `channel+ts`). Slack-driver * startup invokes this exact routine, and every appender also uses the same * unlocked implementation after taking this cross-process lock. Best-effort. */ export declare function compactPostedMessageIndex(indexPath: string, maxEntries?: number, hooks?: PostedMessageIndexCompactionHooks): Promise; export {}; //# sourceMappingURL=posted-message-index.d.ts.map