@ai-matrx/messaging 0.15.14 → 0.15.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.cts CHANGED
@@ -552,6 +552,49 @@ declare const MESSAGING_EVENTS: {
552
552
  readonly actionSettled: "mx.action.settled";
553
553
  };
554
554
 
555
+ /**
556
+ * ONE error classification for the whole package (C22).
557
+ *
558
+ * The banned alternative is the host catching a PostgREST error and deciding
559
+ * for itself whether it was an auth blip, a permission denial, or a real bug —
560
+ * which is how the same three-branch `if` ends up copy-pasted into every
561
+ * consumer and drifts. Everything this package throws is a `MessagingError`
562
+ * with a stable `code`, and the two that hosts genuinely treat differently
563
+ * (`session-unavailable`, `forbidden`) say so in the type.
564
+ *
565
+ * The incident behind `session-unavailable`: a DM reader mounted before the
566
+ * access token existed, every RPC came back `42501`, and the app captured 909
567
+ * errors in 0.6 seconds. A missing session is a NORMAL lifecycle moment — it
568
+ * warns and retries; it is never a red error.
569
+ */
570
+ type MessagingErrorCode = "session-unavailable" | "contention" | "forbidden" | "not-found" | "conflict" | "transport" | "invalid-response" | "write-not-applied" | "misconfigured"
571
+ /** A NEW conversation needs an organization to land in and none is selected. */
572
+ | "organization-required" | "unknown";
573
+ declare class MessagingError extends Error {
574
+ readonly code: MessagingErrorCode;
575
+ /** The remedy. Nothing fails silently, and nothing fails without saying what to do. */
576
+ readonly remedy: string;
577
+ readonly cause?: unknown;
578
+ constructor(code: MessagingErrorCode, message: string, remedy: string, cause?: unknown);
579
+ /** True when re-running the same call is the fix. */
580
+ get isRetryable(): boolean;
581
+ }
582
+ interface PostgrestErrorLike {
583
+ message: string;
584
+ code?: string | undefined;
585
+ details?: string | null | undefined;
586
+ }
587
+ /**
588
+ * THE ONE PLACE a raw PostgREST/transport error becomes a typed one. Called at
589
+ * every boundary in `repository.ts`; nothing else in the package inspects a raw
590
+ * error, and no consumer should ever have to.
591
+ */
592
+ declare function normalizeMessagingError(error: PostgrestErrorLike | Error | unknown, operation: string): MessagingError;
593
+ /** A response whose SHAPE is wrong — the ingress boundary, not the wire. */
594
+ declare function invalidResponse(operation: string, detail: string): MessagingError;
595
+ /** A mutation that PostgREST accepted syntactically but applied to no row. */
596
+ declare function writeNotApplied(operation: string, detail: string): MessagingError;
597
+
555
598
  /**
556
599
  * THE OUTBOX — a message you typed is NEVER lost (R2, table-stakes law).
557
600
  *
@@ -640,47 +683,6 @@ interface Outbox {
640
683
  }
641
684
  declare function createOutbox(options: OutboxOptions): Outbox;
642
685
 
643
- /**
644
- * ONE error classification for the whole package (C22).
645
- *
646
- * The banned alternative is the host catching a PostgREST error and deciding
647
- * for itself whether it was an auth blip, a permission denial, or a real bug —
648
- * which is how the same three-branch `if` ends up copy-pasted into every
649
- * consumer and drifts. Everything this package throws is a `MessagingError`
650
- * with a stable `code`, and the two that hosts genuinely treat differently
651
- * (`session-unavailable`, `forbidden`) say so in the type.
652
- *
653
- * The incident behind `session-unavailable`: a DM reader mounted before the
654
- * access token existed, every RPC came back `42501`, and the app captured 909
655
- * errors in 0.6 seconds. A missing session is a NORMAL lifecycle moment — it
656
- * warns and retries; it is never a red error.
657
- */
658
- type MessagingErrorCode = "session-unavailable" | "contention" | "forbidden" | "not-found" | "conflict" | "transport" | "invalid-response" | "misconfigured"
659
- /** A NEW conversation needs an organization to land in and none is selected. */
660
- | "organization-required" | "unknown";
661
- declare class MessagingError extends Error {
662
- readonly code: MessagingErrorCode;
663
- /** The remedy. Nothing fails silently, and nothing fails without saying what to do. */
664
- readonly remedy: string;
665
- readonly cause?: unknown;
666
- constructor(code: MessagingErrorCode, message: string, remedy: string, cause?: unknown);
667
- /** True when re-running the same call is the fix. */
668
- get isRetryable(): boolean;
669
- }
670
- interface PostgrestErrorLike {
671
- message: string;
672
- code?: string | undefined;
673
- details?: string | null | undefined;
674
- }
675
- /**
676
- * THE ONE PLACE a raw PostgREST/transport error becomes a typed one. Called at
677
- * every boundary in `repository.ts`; nothing else in the package inspects a raw
678
- * error, and no consumer should ever have to.
679
- */
680
- declare function normalizeMessagingError(error: PostgrestErrorLike | Error | unknown, operation: string): MessagingError;
681
- /** A response whose SHAPE is wrong — the ingress boundary, not the wire. */
682
- declare function invalidResponse(operation: string, detail: string): MessagingError;
683
-
684
686
  /**
685
687
  * THE STRUCTURAL SUPABASE SHAPE.
686
688
  *
@@ -712,6 +714,7 @@ interface PostgrestLikeResponse<T> {
712
714
  code?: string;
713
715
  details?: string | null;
714
716
  } | null;
717
+ count?: number | null;
715
718
  }
716
719
  interface PostgrestFilterLike<TRow> extends PromiseLike<PostgrestLikeResponse<TRow[]>> {
717
720
  eq(column: string, value: unknown): PostgrestFilterLike<TRow>;
@@ -725,12 +728,17 @@ interface PostgrestFilterLike<TRow> extends PromiseLike<PostgrestLikeResponse<TR
725
728
  ascending?: boolean;
726
729
  }): PostgrestFilterLike<TRow>;
727
730
  limit(count: number): PostgrestFilterLike<TRow>;
728
- select(columns?: string): PostgrestFilterLike<TRow>;
731
+ range(from: number, to: number): PostgrestFilterLike<TRow>;
732
+ select(columns?: string, options?: {
733
+ count?: "exact";
734
+ }): PostgrestFilterLike<TRow>;
729
735
  single(): PromiseLike<PostgrestLikeResponse<TRow>>;
730
736
  maybeSingle(): PromiseLike<PostgrestLikeResponse<TRow>>;
731
737
  }
732
738
  interface PostgrestTableLike<TRow> {
733
- select(columns?: string): PostgrestFilterLike<TRow>;
739
+ select(columns?: string, options?: {
740
+ count?: "exact";
741
+ }): PostgrestFilterLike<TRow>;
734
742
  insert(values: unknown): PostgrestFilterLike<TRow>;
735
743
  update(values: unknown): PostgrestFilterLike<TRow>;
736
744
  upsert(values: unknown, options?: {
@@ -1023,6 +1031,11 @@ interface MessagingRepository {
1023
1031
  exact: boolean;
1024
1032
  }>;
1025
1033
  getConversation(id: ConversationId): Promise<Conversation>;
1034
+ /**
1035
+ * One conversation's complete inbox/header row, independent of inbox paging.
1036
+ * Deep links use this door when their conversation is not in the loaded page.
1037
+ */
1038
+ getConversationSummary(id: ConversationId): Promise<ConversationSummary>;
1026
1039
  listMessages(conversationId: ConversationId, args?: {
1027
1040
  limit?: number;
1028
1041
  cursor?: MessageCursor | null;
@@ -1037,7 +1050,8 @@ interface MessagingRepository {
1037
1050
  insertMessage(draft: DraftMessage, clientMessageId: ClientMessageId): Promise<Message>;
1038
1051
  editMessage(id: MessageId, content: string): Promise<Message>;
1039
1052
  deleteMessage(id: MessageId, forEveryone: boolean): Promise<void>;
1040
- markRead(conversationId: ConversationId, at?: string): Promise<void>;
1053
+ /** Persists the cutoff, then returns the server's remaining unread count. */
1054
+ markRead(conversationId: ConversationId, at: string): Promise<number>;
1041
1055
  setConversationFlags(conversationId: ConversationId, flags: {
1042
1056
  isMuted?: boolean;
1043
1057
  isArchived?: boolean;
@@ -1078,10 +1092,10 @@ declare function createMessagingRepository(options: RepositoryOptions): Messagin
1078
1092
  * 4. **Order is `created_at` then `id`.** The unique tiebreaker is not
1079
1093
  * decoration: two messages in the same millisecond otherwise swap places on
1080
1094
  * every re-render.
1081
- * 5. **The active conversation's unread count is forced to zero.** A stale
1082
- * server count must never re-badge a conversation the user is reading — a
1083
- * safety net the origin needed in three separate places, so it lives in one
1084
- * place here.
1095
+ * 5. **Navigation is not a read receipt.** Active-conversation state never
1096
+ * clears unread counts. Only a confirmed participant write may do that, and
1097
+ * overlapping confirmations apply monotonically so one baseline is never
1098
+ * subtracted twice.
1085
1099
  */
1086
1100
 
1087
1101
  /**
@@ -1111,6 +1125,12 @@ interface ConversationThread {
1111
1125
  }
1112
1126
  interface MessagingSnapshot {
1113
1127
  readonly conversations: readonly ConversationSummary[];
1128
+ /**
1129
+ * Summaries known by id, including a directly opened conversation outside
1130
+ * the current filtered inbox page. Header lookup uses this map; inbox
1131
+ * rendering continues to use `conversations` exactly as returned by its page.
1132
+ */
1133
+ readonly conversationSummaries: ReadonlyMap<ConversationId, ConversationSummary>;
1114
1134
  readonly hasMoreConversations: boolean;
1115
1135
  /**
1116
1136
  * True once the inbox has actually been READ at least once.
@@ -1169,6 +1189,7 @@ interface MessagingStore {
1169
1189
  } | null): void;
1170
1190
  appendConversations(items: readonly ConversationSummary[], hasMore: boolean): void;
1171
1191
  upsertConversation(item: ConversationSummary): void;
1192
+ setConversationSummary(item: ConversationSummary): void;
1172
1193
  removeConversation(id: ConversationId): void;
1173
1194
  setActiveConversation(id: ConversationId | null): void;
1174
1195
  /** Record (or clear, with `null`) why a conversation could not be read. */
@@ -1176,12 +1197,19 @@ interface MessagingStore {
1176
1197
  setThread(id: ConversationId, messages: readonly Message[], args?: {
1177
1198
  hasMoreOlder?: boolean;
1178
1199
  }): void;
1200
+ clearThread(id: ConversationId): void;
1179
1201
  prependOlder(id: ConversationId, messages: readonly Message[], hasMoreOlder: boolean): void;
1180
1202
  /** The one door every incoming message uses. Returns what actually happened. */
1181
1203
  ingest(message: Message): "added" | "merged" | "dropped-stale" | "dropped-duplicate";
1182
1204
  ingestMany(messages: readonly Message[]): void;
1183
1205
  removeMessage(conversationId: ConversationId, messageId: MessageId): void;
1184
- markConversationRead(id: ConversationId): void;
1206
+ captureConversationRead(id: ConversationId, at: string): ConversationReadCutoff;
1207
+ /**
1208
+ * Apply a confirmed read through the exact cutoff persisted by the repository.
1209
+ * `unreadCount` is the portion visible when that write began; arrivals after
1210
+ * `at` remain unread when the confirmation comes back.
1211
+ */
1212
+ markConversationRead(id: ConversationId, cutoff: ConversationReadCutoff, canonicalUnreadCount?: number): void;
1185
1213
  /**
1186
1214
  * Move a conversation's preview + sort position to reflect a message, with NO
1187
1215
  * refetch. Sending a message must not cost a conversation-list RPC — at a
@@ -1193,6 +1221,12 @@ interface MessagingStore {
1193
1221
  }): void;
1194
1222
  setUnreadCount(id: ConversationId, count: number): void;
1195
1223
  }
1224
+ interface ConversationReadCutoff {
1225
+ readonly at: string;
1226
+ readonly unreadCount: number;
1227
+ /** Per-conversation unread-state revision observed when this read began. */
1228
+ readonly revision: number;
1229
+ }
1196
1230
  declare function createMessagingStore(): MessagingStore;
1197
1231
  /**
1198
1232
  * The optimistic row a composer shows the instant Enter is pressed. It carries
@@ -1232,6 +1266,12 @@ interface EngineDiagnostic {
1232
1266
  readonly level: "info" | "warn" | "error";
1233
1267
  readonly message: string;
1234
1268
  readonly remedy?: string | undefined;
1269
+ /** Stable machine-readable metadata for host diagnostic adapters. */
1270
+ readonly code?: MessagingErrorCode | undefined;
1271
+ readonly operation?: string | undefined;
1272
+ readonly isRetryable?: boolean | undefined;
1273
+ readonly error?: MessagingError | undefined;
1274
+ readonly conversationId?: ConversationId | undefined;
1235
1275
  }
1236
1276
  interface MessagingEngineOptions {
1237
1277
  repository: MessagingRepository;
@@ -1424,4 +1464,4 @@ declare function summarizeText(content: string, maxLength?: number): string;
1424
1464
  /** Serialize picked references into a fence the platform's other readers accept. */
1425
1465
  declare function composeFence(references: readonly MatrxReference[]): string;
1426
1466
 
1427
- export { type ActionChoice, type ActionContext, type ActionHandler, type ActionOutcome, type ActionReceipt, type ActionRegistry, type ActorPresentation, type AiCallArgs, type AiCapability, type AiResult, type Attachment, type ClientMessageId, type Conversation, type ConversationAiContext, type ConversationCursor, type ConversationId, type ConversationReadFailure, type ConversationSummary, type ConversationThread, type ConversationType, DEFAULT_MESSAGING_ARCHIVE_FILTER, type DeliveryState, type DraftMessage, type EngineDiagnostic, type JsonObject, type JsonValue, MESSAGING_EVENTS, MESSAGING_RPC_SCHEMA, MESSAGING_SCHEMA, type MatrxReference, type Message, type MessageAction, type MessageCursor, type MessageGroup, type MessageId, type MessageKind, type MessagingAgentIdentity, type MessagingAgents, type MessagingAi, type MessagingAiOptions, type MessagingArchiveFilter, type MessagingEngine, type MessagingEngineOptions, MessagingError, type MessagingErrorCode, type MessagingIdentity, type MessagingRepository, type MessagingSnapshot, type MessagingStore, type MessagingSupabaseClient, type MessagingSupabaseInternal, type OrganizationId, type Outbox, type OutboxEntry, type OutboxOptions, type OutboxStorage, type Page, type Participant, type ParticipantRole, type PostgrestFilterLike, type PostgrestLikeResponse, type PostgrestTableLike, RPCS, type ReadCache, type ReadCacheOptions, type RepositoryOptions, SERVER_SESSION_IDENTITY, type SchemaLike, type SessionIdentity, type SessionIdentitySnapshot, type SessionResolver, type SupabaseLike, TABLES, type TextSegment, type UserId, type UserSummary, asClientMessageId, asConversationId, asMessageId, asOrganizationId, asUserId, composeFence, conversationContextVariables, conversationTopic, createActionRegistry, createMemoryOutboxStorage, createMessagingAi, createMessagingEngine, createMessagingRepository, createMessagingStore, createOutbox, createReadCache, createSessionIdentity, createWebOutboxStorage, effectiveActorKey, extractReferences, formatConversationTime, formatDateSeparator, formatLastSeen, formatMessageTime, formatTypists, groupMessages, inboxTopic, invalidResponse, isSameDay, messagingClientId, normalizeMessagingError, optimisticMessage, participantNames, projectConversationSummary, projectMessage, projectMessageAction, projectParticipantRole, projectUserSummary, resolveActor, splitText, summarizeText, toMessagingArchiveFilter, unreadCutoff };
1467
+ export { type ActionChoice, type ActionContext, type ActionHandler, type ActionOutcome, type ActionReceipt, type ActionRegistry, type ActorPresentation, type AiCallArgs, type AiCapability, type AiResult, type Attachment, type ClientMessageId, type Conversation, type ConversationAiContext, type ConversationCursor, type ConversationId, type ConversationReadCutoff, type ConversationReadFailure, type ConversationSummary, type ConversationThread, type ConversationType, DEFAULT_MESSAGING_ARCHIVE_FILTER, type DeliveryState, type DraftMessage, type EngineDiagnostic, type JsonObject, type JsonValue, MESSAGING_EVENTS, MESSAGING_RPC_SCHEMA, MESSAGING_SCHEMA, type MatrxReference, type Message, type MessageAction, type MessageCursor, type MessageGroup, type MessageId, type MessageKind, type MessagingAgentIdentity, type MessagingAgents, type MessagingAi, type MessagingAiOptions, type MessagingArchiveFilter, type MessagingEngine, type MessagingEngineOptions, MessagingError, type MessagingErrorCode, type MessagingIdentity, type MessagingRepository, type MessagingSnapshot, type MessagingStore, type MessagingSupabaseClient, type MessagingSupabaseInternal, type OrganizationId, type Outbox, type OutboxEntry, type OutboxOptions, type OutboxStorage, type Page, type Participant, type ParticipantRole, type PostgrestFilterLike, type PostgrestLikeResponse, type PostgrestTableLike, RPCS, type ReadCache, type ReadCacheOptions, type RepositoryOptions, SERVER_SESSION_IDENTITY, type SchemaLike, type SessionIdentity, type SessionIdentitySnapshot, type SessionResolver, type SupabaseLike, TABLES, type TextSegment, type UserId, type UserSummary, asClientMessageId, asConversationId, asMessageId, asOrganizationId, asUserId, composeFence, conversationContextVariables, conversationTopic, createActionRegistry, createMemoryOutboxStorage, createMessagingAi, createMessagingEngine, createMessagingRepository, createMessagingStore, createOutbox, createReadCache, createSessionIdentity, createWebOutboxStorage, effectiveActorKey, extractReferences, formatConversationTime, formatDateSeparator, formatLastSeen, formatMessageTime, formatTypists, groupMessages, inboxTopic, invalidResponse, isSameDay, messagingClientId, normalizeMessagingError, optimisticMessage, participantNames, projectConversationSummary, projectMessage, projectMessageAction, projectParticipantRole, projectUserSummary, resolveActor, splitText, summarizeText, toMessagingArchiveFilter, unreadCutoff, writeNotApplied };
package/dist/index.d.ts CHANGED
@@ -552,6 +552,49 @@ declare const MESSAGING_EVENTS: {
552
552
  readonly actionSettled: "mx.action.settled";
553
553
  };
554
554
 
555
+ /**
556
+ * ONE error classification for the whole package (C22).
557
+ *
558
+ * The banned alternative is the host catching a PostgREST error and deciding
559
+ * for itself whether it was an auth blip, a permission denial, or a real bug —
560
+ * which is how the same three-branch `if` ends up copy-pasted into every
561
+ * consumer and drifts. Everything this package throws is a `MessagingError`
562
+ * with a stable `code`, and the two that hosts genuinely treat differently
563
+ * (`session-unavailable`, `forbidden`) say so in the type.
564
+ *
565
+ * The incident behind `session-unavailable`: a DM reader mounted before the
566
+ * access token existed, every RPC came back `42501`, and the app captured 909
567
+ * errors in 0.6 seconds. A missing session is a NORMAL lifecycle moment — it
568
+ * warns and retries; it is never a red error.
569
+ */
570
+ type MessagingErrorCode = "session-unavailable" | "contention" | "forbidden" | "not-found" | "conflict" | "transport" | "invalid-response" | "write-not-applied" | "misconfigured"
571
+ /** A NEW conversation needs an organization to land in and none is selected. */
572
+ | "organization-required" | "unknown";
573
+ declare class MessagingError extends Error {
574
+ readonly code: MessagingErrorCode;
575
+ /** The remedy. Nothing fails silently, and nothing fails without saying what to do. */
576
+ readonly remedy: string;
577
+ readonly cause?: unknown;
578
+ constructor(code: MessagingErrorCode, message: string, remedy: string, cause?: unknown);
579
+ /** True when re-running the same call is the fix. */
580
+ get isRetryable(): boolean;
581
+ }
582
+ interface PostgrestErrorLike {
583
+ message: string;
584
+ code?: string | undefined;
585
+ details?: string | null | undefined;
586
+ }
587
+ /**
588
+ * THE ONE PLACE a raw PostgREST/transport error becomes a typed one. Called at
589
+ * every boundary in `repository.ts`; nothing else in the package inspects a raw
590
+ * error, and no consumer should ever have to.
591
+ */
592
+ declare function normalizeMessagingError(error: PostgrestErrorLike | Error | unknown, operation: string): MessagingError;
593
+ /** A response whose SHAPE is wrong — the ingress boundary, not the wire. */
594
+ declare function invalidResponse(operation: string, detail: string): MessagingError;
595
+ /** A mutation that PostgREST accepted syntactically but applied to no row. */
596
+ declare function writeNotApplied(operation: string, detail: string): MessagingError;
597
+
555
598
  /**
556
599
  * THE OUTBOX — a message you typed is NEVER lost (R2, table-stakes law).
557
600
  *
@@ -640,47 +683,6 @@ interface Outbox {
640
683
  }
641
684
  declare function createOutbox(options: OutboxOptions): Outbox;
642
685
 
643
- /**
644
- * ONE error classification for the whole package (C22).
645
- *
646
- * The banned alternative is the host catching a PostgREST error and deciding
647
- * for itself whether it was an auth blip, a permission denial, or a real bug —
648
- * which is how the same three-branch `if` ends up copy-pasted into every
649
- * consumer and drifts. Everything this package throws is a `MessagingError`
650
- * with a stable `code`, and the two that hosts genuinely treat differently
651
- * (`session-unavailable`, `forbidden`) say so in the type.
652
- *
653
- * The incident behind `session-unavailable`: a DM reader mounted before the
654
- * access token existed, every RPC came back `42501`, and the app captured 909
655
- * errors in 0.6 seconds. A missing session is a NORMAL lifecycle moment — it
656
- * warns and retries; it is never a red error.
657
- */
658
- type MessagingErrorCode = "session-unavailable" | "contention" | "forbidden" | "not-found" | "conflict" | "transport" | "invalid-response" | "misconfigured"
659
- /** A NEW conversation needs an organization to land in and none is selected. */
660
- | "organization-required" | "unknown";
661
- declare class MessagingError extends Error {
662
- readonly code: MessagingErrorCode;
663
- /** The remedy. Nothing fails silently, and nothing fails without saying what to do. */
664
- readonly remedy: string;
665
- readonly cause?: unknown;
666
- constructor(code: MessagingErrorCode, message: string, remedy: string, cause?: unknown);
667
- /** True when re-running the same call is the fix. */
668
- get isRetryable(): boolean;
669
- }
670
- interface PostgrestErrorLike {
671
- message: string;
672
- code?: string | undefined;
673
- details?: string | null | undefined;
674
- }
675
- /**
676
- * THE ONE PLACE a raw PostgREST/transport error becomes a typed one. Called at
677
- * every boundary in `repository.ts`; nothing else in the package inspects a raw
678
- * error, and no consumer should ever have to.
679
- */
680
- declare function normalizeMessagingError(error: PostgrestErrorLike | Error | unknown, operation: string): MessagingError;
681
- /** A response whose SHAPE is wrong — the ingress boundary, not the wire. */
682
- declare function invalidResponse(operation: string, detail: string): MessagingError;
683
-
684
686
  /**
685
687
  * THE STRUCTURAL SUPABASE SHAPE.
686
688
  *
@@ -712,6 +714,7 @@ interface PostgrestLikeResponse<T> {
712
714
  code?: string;
713
715
  details?: string | null;
714
716
  } | null;
717
+ count?: number | null;
715
718
  }
716
719
  interface PostgrestFilterLike<TRow> extends PromiseLike<PostgrestLikeResponse<TRow[]>> {
717
720
  eq(column: string, value: unknown): PostgrestFilterLike<TRow>;
@@ -725,12 +728,17 @@ interface PostgrestFilterLike<TRow> extends PromiseLike<PostgrestLikeResponse<TR
725
728
  ascending?: boolean;
726
729
  }): PostgrestFilterLike<TRow>;
727
730
  limit(count: number): PostgrestFilterLike<TRow>;
728
- select(columns?: string): PostgrestFilterLike<TRow>;
731
+ range(from: number, to: number): PostgrestFilterLike<TRow>;
732
+ select(columns?: string, options?: {
733
+ count?: "exact";
734
+ }): PostgrestFilterLike<TRow>;
729
735
  single(): PromiseLike<PostgrestLikeResponse<TRow>>;
730
736
  maybeSingle(): PromiseLike<PostgrestLikeResponse<TRow>>;
731
737
  }
732
738
  interface PostgrestTableLike<TRow> {
733
- select(columns?: string): PostgrestFilterLike<TRow>;
739
+ select(columns?: string, options?: {
740
+ count?: "exact";
741
+ }): PostgrestFilterLike<TRow>;
734
742
  insert(values: unknown): PostgrestFilterLike<TRow>;
735
743
  update(values: unknown): PostgrestFilterLike<TRow>;
736
744
  upsert(values: unknown, options?: {
@@ -1023,6 +1031,11 @@ interface MessagingRepository {
1023
1031
  exact: boolean;
1024
1032
  }>;
1025
1033
  getConversation(id: ConversationId): Promise<Conversation>;
1034
+ /**
1035
+ * One conversation's complete inbox/header row, independent of inbox paging.
1036
+ * Deep links use this door when their conversation is not in the loaded page.
1037
+ */
1038
+ getConversationSummary(id: ConversationId): Promise<ConversationSummary>;
1026
1039
  listMessages(conversationId: ConversationId, args?: {
1027
1040
  limit?: number;
1028
1041
  cursor?: MessageCursor | null;
@@ -1037,7 +1050,8 @@ interface MessagingRepository {
1037
1050
  insertMessage(draft: DraftMessage, clientMessageId: ClientMessageId): Promise<Message>;
1038
1051
  editMessage(id: MessageId, content: string): Promise<Message>;
1039
1052
  deleteMessage(id: MessageId, forEveryone: boolean): Promise<void>;
1040
- markRead(conversationId: ConversationId, at?: string): Promise<void>;
1053
+ /** Persists the cutoff, then returns the server's remaining unread count. */
1054
+ markRead(conversationId: ConversationId, at: string): Promise<number>;
1041
1055
  setConversationFlags(conversationId: ConversationId, flags: {
1042
1056
  isMuted?: boolean;
1043
1057
  isArchived?: boolean;
@@ -1078,10 +1092,10 @@ declare function createMessagingRepository(options: RepositoryOptions): Messagin
1078
1092
  * 4. **Order is `created_at` then `id`.** The unique tiebreaker is not
1079
1093
  * decoration: two messages in the same millisecond otherwise swap places on
1080
1094
  * every re-render.
1081
- * 5. **The active conversation's unread count is forced to zero.** A stale
1082
- * server count must never re-badge a conversation the user is reading — a
1083
- * safety net the origin needed in three separate places, so it lives in one
1084
- * place here.
1095
+ * 5. **Navigation is not a read receipt.** Active-conversation state never
1096
+ * clears unread counts. Only a confirmed participant write may do that, and
1097
+ * overlapping confirmations apply monotonically so one baseline is never
1098
+ * subtracted twice.
1085
1099
  */
1086
1100
 
1087
1101
  /**
@@ -1111,6 +1125,12 @@ interface ConversationThread {
1111
1125
  }
1112
1126
  interface MessagingSnapshot {
1113
1127
  readonly conversations: readonly ConversationSummary[];
1128
+ /**
1129
+ * Summaries known by id, including a directly opened conversation outside
1130
+ * the current filtered inbox page. Header lookup uses this map; inbox
1131
+ * rendering continues to use `conversations` exactly as returned by its page.
1132
+ */
1133
+ readonly conversationSummaries: ReadonlyMap<ConversationId, ConversationSummary>;
1114
1134
  readonly hasMoreConversations: boolean;
1115
1135
  /**
1116
1136
  * True once the inbox has actually been READ at least once.
@@ -1169,6 +1189,7 @@ interface MessagingStore {
1169
1189
  } | null): void;
1170
1190
  appendConversations(items: readonly ConversationSummary[], hasMore: boolean): void;
1171
1191
  upsertConversation(item: ConversationSummary): void;
1192
+ setConversationSummary(item: ConversationSummary): void;
1172
1193
  removeConversation(id: ConversationId): void;
1173
1194
  setActiveConversation(id: ConversationId | null): void;
1174
1195
  /** Record (or clear, with `null`) why a conversation could not be read. */
@@ -1176,12 +1197,19 @@ interface MessagingStore {
1176
1197
  setThread(id: ConversationId, messages: readonly Message[], args?: {
1177
1198
  hasMoreOlder?: boolean;
1178
1199
  }): void;
1200
+ clearThread(id: ConversationId): void;
1179
1201
  prependOlder(id: ConversationId, messages: readonly Message[], hasMoreOlder: boolean): void;
1180
1202
  /** The one door every incoming message uses. Returns what actually happened. */
1181
1203
  ingest(message: Message): "added" | "merged" | "dropped-stale" | "dropped-duplicate";
1182
1204
  ingestMany(messages: readonly Message[]): void;
1183
1205
  removeMessage(conversationId: ConversationId, messageId: MessageId): void;
1184
- markConversationRead(id: ConversationId): void;
1206
+ captureConversationRead(id: ConversationId, at: string): ConversationReadCutoff;
1207
+ /**
1208
+ * Apply a confirmed read through the exact cutoff persisted by the repository.
1209
+ * `unreadCount` is the portion visible when that write began; arrivals after
1210
+ * `at` remain unread when the confirmation comes back.
1211
+ */
1212
+ markConversationRead(id: ConversationId, cutoff: ConversationReadCutoff, canonicalUnreadCount?: number): void;
1185
1213
  /**
1186
1214
  * Move a conversation's preview + sort position to reflect a message, with NO
1187
1215
  * refetch. Sending a message must not cost a conversation-list RPC — at a
@@ -1193,6 +1221,12 @@ interface MessagingStore {
1193
1221
  }): void;
1194
1222
  setUnreadCount(id: ConversationId, count: number): void;
1195
1223
  }
1224
+ interface ConversationReadCutoff {
1225
+ readonly at: string;
1226
+ readonly unreadCount: number;
1227
+ /** Per-conversation unread-state revision observed when this read began. */
1228
+ readonly revision: number;
1229
+ }
1196
1230
  declare function createMessagingStore(): MessagingStore;
1197
1231
  /**
1198
1232
  * The optimistic row a composer shows the instant Enter is pressed. It carries
@@ -1232,6 +1266,12 @@ interface EngineDiagnostic {
1232
1266
  readonly level: "info" | "warn" | "error";
1233
1267
  readonly message: string;
1234
1268
  readonly remedy?: string | undefined;
1269
+ /** Stable machine-readable metadata for host diagnostic adapters. */
1270
+ readonly code?: MessagingErrorCode | undefined;
1271
+ readonly operation?: string | undefined;
1272
+ readonly isRetryable?: boolean | undefined;
1273
+ readonly error?: MessagingError | undefined;
1274
+ readonly conversationId?: ConversationId | undefined;
1235
1275
  }
1236
1276
  interface MessagingEngineOptions {
1237
1277
  repository: MessagingRepository;
@@ -1424,4 +1464,4 @@ declare function summarizeText(content: string, maxLength?: number): string;
1424
1464
  /** Serialize picked references into a fence the platform's other readers accept. */
1425
1465
  declare function composeFence(references: readonly MatrxReference[]): string;
1426
1466
 
1427
- export { type ActionChoice, type ActionContext, type ActionHandler, type ActionOutcome, type ActionReceipt, type ActionRegistry, type ActorPresentation, type AiCallArgs, type AiCapability, type AiResult, type Attachment, type ClientMessageId, type Conversation, type ConversationAiContext, type ConversationCursor, type ConversationId, type ConversationReadFailure, type ConversationSummary, type ConversationThread, type ConversationType, DEFAULT_MESSAGING_ARCHIVE_FILTER, type DeliveryState, type DraftMessage, type EngineDiagnostic, type JsonObject, type JsonValue, MESSAGING_EVENTS, MESSAGING_RPC_SCHEMA, MESSAGING_SCHEMA, type MatrxReference, type Message, type MessageAction, type MessageCursor, type MessageGroup, type MessageId, type MessageKind, type MessagingAgentIdentity, type MessagingAgents, type MessagingAi, type MessagingAiOptions, type MessagingArchiveFilter, type MessagingEngine, type MessagingEngineOptions, MessagingError, type MessagingErrorCode, type MessagingIdentity, type MessagingRepository, type MessagingSnapshot, type MessagingStore, type MessagingSupabaseClient, type MessagingSupabaseInternal, type OrganizationId, type Outbox, type OutboxEntry, type OutboxOptions, type OutboxStorage, type Page, type Participant, type ParticipantRole, type PostgrestFilterLike, type PostgrestLikeResponse, type PostgrestTableLike, RPCS, type ReadCache, type ReadCacheOptions, type RepositoryOptions, SERVER_SESSION_IDENTITY, type SchemaLike, type SessionIdentity, type SessionIdentitySnapshot, type SessionResolver, type SupabaseLike, TABLES, type TextSegment, type UserId, type UserSummary, asClientMessageId, asConversationId, asMessageId, asOrganizationId, asUserId, composeFence, conversationContextVariables, conversationTopic, createActionRegistry, createMemoryOutboxStorage, createMessagingAi, createMessagingEngine, createMessagingRepository, createMessagingStore, createOutbox, createReadCache, createSessionIdentity, createWebOutboxStorage, effectiveActorKey, extractReferences, formatConversationTime, formatDateSeparator, formatLastSeen, formatMessageTime, formatTypists, groupMessages, inboxTopic, invalidResponse, isSameDay, messagingClientId, normalizeMessagingError, optimisticMessage, participantNames, projectConversationSummary, projectMessage, projectMessageAction, projectParticipantRole, projectUserSummary, resolveActor, splitText, summarizeText, toMessagingArchiveFilter, unreadCutoff };
1467
+ export { type ActionChoice, type ActionContext, type ActionHandler, type ActionOutcome, type ActionReceipt, type ActionRegistry, type ActorPresentation, type AiCallArgs, type AiCapability, type AiResult, type Attachment, type ClientMessageId, type Conversation, type ConversationAiContext, type ConversationCursor, type ConversationId, type ConversationReadCutoff, type ConversationReadFailure, type ConversationSummary, type ConversationThread, type ConversationType, DEFAULT_MESSAGING_ARCHIVE_FILTER, type DeliveryState, type DraftMessage, type EngineDiagnostic, type JsonObject, type JsonValue, MESSAGING_EVENTS, MESSAGING_RPC_SCHEMA, MESSAGING_SCHEMA, type MatrxReference, type Message, type MessageAction, type MessageCursor, type MessageGroup, type MessageId, type MessageKind, type MessagingAgentIdentity, type MessagingAgents, type MessagingAi, type MessagingAiOptions, type MessagingArchiveFilter, type MessagingEngine, type MessagingEngineOptions, MessagingError, type MessagingErrorCode, type MessagingIdentity, type MessagingRepository, type MessagingSnapshot, type MessagingStore, type MessagingSupabaseClient, type MessagingSupabaseInternal, type OrganizationId, type Outbox, type OutboxEntry, type OutboxOptions, type OutboxStorage, type Page, type Participant, type ParticipantRole, type PostgrestFilterLike, type PostgrestLikeResponse, type PostgrestTableLike, RPCS, type ReadCache, type ReadCacheOptions, type RepositoryOptions, SERVER_SESSION_IDENTITY, type SchemaLike, type SessionIdentity, type SessionIdentitySnapshot, type SessionResolver, type SupabaseLike, TABLES, type TextSegment, type UserId, type UserSummary, asClientMessageId, asConversationId, asMessageId, asOrganizationId, asUserId, composeFence, conversationContextVariables, conversationTopic, createActionRegistry, createMemoryOutboxStorage, createMessagingAi, createMessagingEngine, createMessagingRepository, createMessagingStore, createOutbox, createReadCache, createSessionIdentity, createWebOutboxStorage, effectiveActorKey, extractReferences, formatConversationTime, formatDateSeparator, formatLastSeen, formatMessageTime, formatTypists, groupMessages, inboxTopic, invalidResponse, isSameDay, messagingClientId, normalizeMessagingError, optimisticMessage, participantNames, projectConversationSummary, projectMessage, projectMessageAction, projectParticipantRole, projectUserSummary, resolveActor, splitText, summarizeText, toMessagingArchiveFilter, unreadCutoff, writeNotApplied };