import { z } from 'zod'; /** Bound recent dedupe window inside one conversation journal. */ export declare const OBSERVED_CONVERSATION_DEDUPE_RECENT = 2000; /** Cap retained journal archives per conversation (plus the live segment). */ export declare const OBSERVED_CONVERSATION_MAX_ARCHIVES = 4; /** Minimal local file descriptor for file-only / file-bearing messages. */ export declare const ObservedFileDescriptorSchema: z.ZodObject<{ id: z.ZodString; mimetype: z.ZodOptional; name: z.ZodOptional; }, z.core.$strip>; export type ObservedFileDescriptor = z.infer; export declare const ObservedConversationEntrySchema: z.ZodObject<{ botId: z.ZodOptional; channelId: z.ZodString; eventId: z.ZodString; files: z.ZodOptional; name: z.ZodOptional; }, z.core.$strip>>>; messageTs: z.ZodString; observedAt: z.ZodString; ordinal: z.ZodNumber; receivedAt: z.ZodString; surfaceId: z.ZodString; teamId: z.ZodString; text: z.ZodString; threadTs: z.ZodOptional; userId: z.ZodOptional; }, z.core.$strip>; export type ObservedConversationEntry = z.infer; /** Journal-tail metadata for one conversation (not the delivered cursor). */ export declare const ConversationIndexSchema: z.ZodObject<{ lastEventId: z.ZodString; lastMessageTs: z.ZodString; surfaceId: z.ZodString; tailOrdinal: z.ZodNumber; updatedAt: z.ZodString; }, z.core.$strip>; export type ConversationIndex = z.infer; /** * Delivered cursor record. * * - `deliveredOrdinal: null` → **confirmed absent** (never delivered into this * conversation; "no-cursor lands" applies only to this state). * - `deliveredOrdinal: 0` → present at origin (established, nothing past start). * - `deliveredOrdinal: N>0` → delivered through ordinal N inclusive. * * A missing cursor file reads as confirmed absent via empty(). */ export declare const ConversationCursorRecordSchema: z.ZodObject<{ deliveredOrdinal: z.ZodNullable; lastDeliveredEventId: z.ZodOptional; lastDeliveredMessageTs: z.ZodOptional; surfaceId: z.ZodString; updatedAt: z.ZodString; }, z.core.$strip>; export type ConversationCursorRecord = z.infer; export type ConversationCursorView = { status: 'absent'; surfaceId: string; } | { status: 'present'; deliveredOrdinal: number; lastDeliveredEventId?: string; lastDeliveredMessageTs?: string; surfaceId: string; updatedAt: string; }; export type AdvanceCursorExpected = { status: 'absent'; } | { status: 'present'; deliveredOrdinal: number; }; export type AdvanceCursorResult = { advanced: true; cursor: Extract; } | { advanced: false; reason: 'cas_mismatch' | 'regression' | 'beyond_tail'; cursor: ConversationCursorView; /** Reconciled journal tail when reason is beyond_tail (0 if no observations). */ tailOrdinal?: number; }; /** Agent-wide observation continuity (fail-closed signal for later send hold). */ export declare const ObservationContinuitySchema: z.ZodObject<{ lastFailureAt: z.ZodOptional; lastFailureEventId: z.ZodOptional; lastFailureMessage: z.ZodOptional; lastFailureSurfaceId: z.ZodOptional; lastSuccessAt: z.ZodOptional; status: z.ZodEnum<{ ok: "ok"; degraded: "degraded"; }>; updatedAt: z.ZodString; }, z.core.$strip>; export type ObservationContinuity = z.infer; export interface ObserveSlackMessageInput { botId?: string; channelId: string; files?: ObservedFileDescriptor[]; messageTs: string; /** ISO time derived from Slack ts when available. */ receivedAt?: string; teamId: string; text: string; /** Thread parent ts for replies only; omit for top-level / DM root. */ threadTs?: string; userId?: string; } export type ObserveSlackMessageResult = { appended: true; entry: ObservedConversationEntry; } | { appended: false; reason: 'duplicate'; entry?: ObservedConversationEntry; }; export declare function observedConversationsDir(agentId: string): string; /** Stable filesystem key for a surface id (no path separators / nulls). */ export declare function observedConversationFileStem(surfaceId: string): string; /** * Explicit top-level vs thread partition: only true replies (thread_ts set and * different from the message ts) join a thread surface. Channel parents and * DM roots stay on the unthreaded surface. */ export declare function conversationThreadTs(input: { messageTs: string; threadTs?: string; }): string | undefined; export declare function surfaceIdForObservation(input: { channelId: string; messageTs: string; teamId: string; threadTs?: string; }): string; export declare function cursorViewFromRecord(surfaceId: string, record: ConversationCursorRecord): ConversationCursorView; /** * Rebuild journal-tail index from retained journal rows when the index lags * (append-then-index-write crash recovery). */ export declare function reconcileIndexFromJournal(current: ConversationIndex, recent: ObservedConversationEntry[], surfaceId: string): ConversationIndex; export interface ObservedConversationStoreOptions { /** Journal rotation size; tests may pass a tiny value. */ maxBytes?: number; maxArchives?: number; } export declare class ObservedConversationStore { private readonly agentId; private readonly options; constructor(agentId: string, options?: ObservedConversationStoreOptions); /** * Record one Slack message observation. Idempotent on eventId within the * conversation. Reconciles a stale index from the journal before assigning * a new ordinal (crash recovery). */ observe(input: ObserveSlackMessageInput): Promise; getIndex(surfaceId: string): Promise; /** * Read journal-tail index, reconciling from the retained journal when the * stored index lags (same recovery path as observe). */ getIndexReconciled(surfaceId: string): Promise; /** * Consistent cursor-delivery snapshot for one surface. * * Runs under the same index lock as `observe`, so a concurrent append cannot * land between the captured tail and the journal rows. Candidates are filtered * through the captured tail (`afterOrdinal < ordinal <= capturedTail`); the * exact candidate population is `capturedTail − afterOrdinal` when the index * is present. * * Bounded read: under the lock we only `readTail(max(limit, dedupeWindow))` — * never a full retained-history parse (archives can be tens of MiB per surface). */ readCursorDeliverySnapshot(surfaceId: string, options?: { afterOrdinal?: number; limit?: number; }): Promise<{ index: ConversationIndex | undefined; candidates: ObservedConversationEntry[]; /** Captured reconciled tail (0 when empty). */ capturedTailOrdinal: number; }>; readJournal(surfaceId: string, options?: { afterOrdinal?: number; limit?: number; }): Promise; readTail(surfaceId: string, limit: number): Promise; getCursor(surfaceId: string): Promise; /** * Monotonic CAS advance of the delivered cursor. * * - `expected` must match the current absent/present+ordinal view. * - `nextDeliveredOrdinal` must be >= 0; if current is present, next must be * >= current.deliveredOrdinal (regression otherwise). * - Fail-closed against the journal: next must be <= reconciled journal tail. * The only allowed advance with no observed rows is present@0 (thread-root * establishment). Anything above tail returns `beyond_tail`. */ advanceCursor(input: { expected: AdvanceCursorExpected; lastDeliveredEventId?: string; lastDeliveredMessageTs?: string; nextDeliveredOrdinal: number; surfaceId: string; }): Promise; getContinuity(): Promise; markDegraded(input: { eventId?: string; message: string; surfaceId?: string; }): Promise; /** Best-effort: never mask the observation error with a continuity write error. */ private markDegradedSafe; /** Test/recovery helper: force-write journal-tail index without observing. */ writeIndexForTest(index: ConversationIndex): Promise; /** Test/recovery helper: force-write delivered cursor without advanceCursor CAS. */ writeCursorForTest(record: ConversationCursorRecord): Promise; private journal; private indexStore; private cursorStore; private continuityStore; private journalPath; private indexPath; private cursorPath; private continuityPath; } export declare function observedConversationStoreForAgent(agentId: string): ObservedConversationStore; //# sourceMappingURL=observed-conversation.store.d.ts.map