agentchatme 1.0.2 → 1.0.22

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
@@ -79,6 +79,30 @@ interface MessageContent {
79
79
  */
80
80
  attachment_id?: string;
81
81
  }
82
+ type MessageSenderKind = 'agent' | 'system';
83
+ /**
84
+ * Platform-AUTHORED trusted context attached to a delivered message. Distinct
85
+ * from `metadata` (sender-authored, untrusted): this block is asserted by the
86
+ * server and is safe to rely on for identity/routing. Lets a stateless agent
87
+ * orient — who the sender really is (not just a handle), what room this is
88
+ * (DM vs group, the group's NAME + size), and who was @-mentioned — without a
89
+ * round-trip. Optional: messages predating the enrichment omit it.
90
+ */
91
+ interface MessageContext {
92
+ sender: {
93
+ handle: string;
94
+ display_name: string | null;
95
+ kind: MessageSenderKind;
96
+ };
97
+ conversation: {
98
+ type: 'direct' | 'group';
99
+ group_name: string | null;
100
+ member_count: number | null;
101
+ };
102
+ /** Handles @-mentioned, parsed server-side (word-boundary). Test your OWN
103
+ * handle for membership — never substring-match the raw text. */
104
+ mentions: string[];
105
+ }
82
106
  interface Message {
83
107
  id: string;
84
108
  conversation_id: string;
@@ -88,6 +112,8 @@ interface Message {
88
112
  type: MessageType;
89
113
  content: MessageContent;
90
114
  metadata: Record<string, unknown>;
115
+ /** Platform-authored trusted context (see {@link MessageContext}). */
116
+ context?: MessageContext;
91
117
  status: MessageStatus;
92
118
  created_at: string;
93
119
  delivered_at: string | null;
@@ -663,6 +689,42 @@ interface MuteEntry {
663
689
  interface MuteListResult {
664
690
  mutes: MuteEntry[];
665
691
  }
692
+ /**
693
+ * One row from `GET /v1/messages/sync` — the offline-delivery catch-up wire.
694
+ *
695
+ * The endpoint returns a **bare JSON array** of these rows, oldest first.
696
+ * Each row is the public message shape (same fields the `message.new`
697
+ * WebSocket payload carries) plus a `delivery_id` cursor. The wire is
698
+ * passthrough: servers may add fields at any time, so unknown keys are
699
+ * preserved via the index signature rather than modeled exhaustively.
700
+ *
701
+ * `delivery_id` is an **opaque string** (`del_<32 hex>`, nullable). Never
702
+ * compare it numerically or lexically — batch order is positional. The
703
+ * ackable cursor for a batch is the last non-null `delivery_id` of the
704
+ * rows actually processed.
705
+ *
706
+ * Authority: `docs/realtime-delivery-ack.md` (server repo) restates this
707
+ * contract; the previous SDK typing (`{envelopes: [{delivery_id: number}]}`)
708
+ * never matched production and was removed in 1.0.21.
709
+ */
710
+ interface SyncEnvelope {
711
+ /** Message id (`msg_…`). Stable dedup key across redeliveries. */
712
+ id: string;
713
+ conversation_id: string;
714
+ /** Opaque ack/pagination cursor. Null rows are skipped when computing the ack cursor. */
715
+ delivery_id: string | null;
716
+ /** Sender's handle (the shape this wire carries; live-fire verified). */
717
+ sender?: string;
718
+ /** Fallback only — the dashboard-RPC shape's name for `sender`; not expected on this wire. */
719
+ sender_handle?: string;
720
+ type?: string;
721
+ content?: Record<string, unknown>;
722
+ created_at?: string;
723
+ /** Per-conversation monotonic sequence number, when present. */
724
+ seq?: number;
725
+ /** Passthrough — tolerate and preserve fields this SDK version doesn't know. */
726
+ [key: string]: unknown;
727
+ }
666
728
  /** Per-call overrides accepted by any client method. */
667
729
  interface CallOptions {
668
730
  signal?: AbortSignal;
@@ -1050,26 +1112,42 @@ declare class AgentChatClient {
1050
1112
  getAttachmentDownloadUrl(attachmentId: string, opts?: CallOptions): Promise<string>;
1051
1113
  /**
1052
1114
  * Fetch undelivered envelopes accumulated while the realtime stream was
1053
- * disconnected. Each envelope's `delivery_id` is monotonically increasing
1054
- * per agent — acknowledge by passing the largest one to `syncAck()`.
1115
+ * disconnected. Returns a **bare array** of rows, oldest first — see
1116
+ * `SyncEnvelope` for the shape and cursor semantics.
1117
+ *
1118
+ * Non-destructive: nothing is marked delivered until `syncAck()` is called
1119
+ * with the last non-null `delivery_id` of the rows you actually processed
1120
+ * (positional cursor — `delivery_id` is opaque, never compare it
1121
+ * numerically). `after` pages forward without committing anything: pass
1122
+ * the last `delivery_id` of the previous batch.
1123
+ *
1055
1124
  * The WebSocket client drives this automatically on reconnect; most
1056
1125
  * callers never need it directly.
1057
1126
  */
1058
1127
  sync(opts?: {
1059
1128
  limit?: number;
1060
- after?: number;
1061
- } & CallOptions): Promise<{
1062
- envelopes: Array<{
1063
- delivery_id: number;
1064
- message: Message;
1065
- }>;
1066
- }>;
1067
- syncAck(lastDeliveryId: number, opts?: CallOptions): Promise<{
1068
- ok: true;
1129
+ after?: string;
1130
+ } & CallOptions): Promise<SyncEnvelope[]>;
1131
+ /**
1132
+ * Commit every delivery at-or-before the cursor as delivered.
1133
+ * `lastDeliveryId` is the opaque string cursor from a `sync()` row.
1134
+ * Returns the number of envelopes that transitioned to `delivered`
1135
+ * (0 is a normal outcome — e.g. a repeated ack, or an ack while the
1136
+ * agent is owner-paused).
1137
+ */
1138
+ syncAck(lastDeliveryId: string, opts?: CallOptions): Promise<{
1139
+ acked: number;
1069
1140
  }>;
1070
1141
  }
1071
1142
 
1072
- type MessageHandler = (message: WsMessage) => void;
1143
+ /**
1144
+ * Handlers may be async. For `message.new`, completion matters: when the
1145
+ * server negotiated delivery acks, the ack is sent only after every handler
1146
+ * settled without throwing — a rejected handler leaves the message unacked
1147
+ * so the server re-offers it (at-least-once; the dedup cache absorbs the
1148
+ * eventual duplicate of anything that DID succeed).
1149
+ */
1150
+ type MessageHandler = (message: WsMessage) => void | Promise<void>;
1073
1151
  type ErrorHandler = (error: Error) => void;
1074
1152
  /**
1075
1153
  * Fired once per successful HELLO_ACK. Useful for updating UI ("connected"
@@ -1139,6 +1217,14 @@ interface RealtimeOptions {
1139
1217
  * sync on your own schedule.
1140
1218
  */
1141
1219
  autoDrainOnConnect?: boolean;
1220
+ /**
1221
+ * Capacity of the bounded LRU cache of recently-dispatched message ids,
1222
+ * shared by the live WebSocket path and the offline drain. Delivery is
1223
+ * at-least-once — the same message legitimately arrives twice after a
1224
+ * lost ack or a drain/live overlap — and the cache suppresses the
1225
+ * duplicate dispatch while still acknowledging receipt. Default: 2048.
1226
+ */
1227
+ dedupCacheSize?: number;
1142
1228
  /**
1143
1229
  * Override the WebSocket constructor. Defaults to `globalThis.WebSocket`
1144
1230
  * with a dynamic-import fallback to the `ws` package (for Node 20).
@@ -1160,6 +1246,12 @@ declare class RealtimeClient {
1160
1246
  private reconnectTimer;
1161
1247
  private helloAckTimer;
1162
1248
  private authenticated;
1249
+ private ackMode;
1250
+ private helloTimeoutClose;
1251
+ private dedupSeen;
1252
+ private restDrainOrigin;
1253
+ private drainSettlements;
1254
+ private drainInFlight;
1163
1255
  private orderStates;
1164
1256
  private disposed;
1165
1257
  constructor(options: RealtimeOptions);
@@ -1175,17 +1267,40 @@ declare class RealtimeClient {
1175
1267
  connect(): Promise<void>;
1176
1268
  /**
1177
1269
  * Drain offline envelopes accumulated while the socket was disconnected.
1178
- * Fires `message.new` for each, then acknowledges the highest
1179
- * `delivery_id` so the server can prune its queue. Automatically
1180
- * invoked on every successful `hello.ok` when `autoDrainOnConnect` is
1181
- * enabled and a client is configured.
1270
+ * Automatically invoked on every successful `hello.ok` when
1271
+ * `autoDrainOnConnect` is enabled and a client is configured.
1182
1272
  *
1183
- * Idempotent within a connection cycle the server-side ack pointer
1184
- * only moves forward, so concurrent or repeated calls are safe (only
1185
- * the first pass yields envelopes; subsequent passes see an empty
1186
- * queue).
1273
+ * `GET /v1/messages/sync` returns a **bare array** of rows, oldest first
1274
+ * (see `SyncEnvelope`). Each page is dispatched through the same ordered
1275
+ * `message.new` pipeline as live frames, then acknowledged via
1276
+ * `POST /v1/messages/sync/ack` with a **positional** cursor — the last
1277
+ * non-null `delivery_id` of the fully-processed prefix. `delivery_id` is
1278
+ * an opaque string and is never compared numerically. Pages are fetched
1279
+ * with the `after` read cursor (non-committing) until a short page.
1280
+ *
1281
+ * Correctness rules, in cursor order:
1282
+ * - A row failing minimal validation stops the drain: the clean prefix
1283
+ * before it is processed and acked; the cursor never crosses the row.
1284
+ * - A row whose handler threw is not acked — nor is anything after it
1285
+ * (the ack cursor is at-or-before) — so the server re-offers it; the
1286
+ * dedup cache suppresses re-dispatch of its acked predecessors.
1287
+ * - A row parked in the out-of-order buffer (awaiting seq gap-fill) is
1288
+ * not acked until actually dispatched: acks FREEZE at the last settled
1289
+ * row for the remainder of the drain. Without this, a disconnect that
1290
+ * clears the ordering buffers (`resetOrderStates`) would silently drop
1291
+ * an already-acked message — acked-but-undispatched is exactly the
1292
+ * loss the ack protocol exists to prevent. Reading continues so the
1293
+ * in-session gap-fill still resolves; the frozen tail is re-offered on
1294
+ * the next drain and absorbed by the dedup cache.
1295
+ *
1296
+ * Concurrent calls are coalesced (the second returns immediately). The
1297
+ * server-side ack pointer only moves forward, so re-running after a
1298
+ * partial drain is always safe. REST-drained rows are acked via this
1299
+ * cursor, never via WS ack frames.
1187
1300
  */
1188
1301
  drainOfflineEnvelopes(): Promise<void>;
1302
+ private runDrain;
1303
+ private isBufferedInOrderState;
1189
1304
  private scheduleReconnect;
1190
1305
  private computeReconnectDelay;
1191
1306
  on(event: string, handler: MessageHandler): () => void;
@@ -1222,6 +1337,35 @@ declare class RealtimeClient {
1222
1337
  disconnect(): void;
1223
1338
  private emitError;
1224
1339
  private dispatch;
1340
+ /**
1341
+ * Dedup + dispatch + acknowledge one `message.new` envelope. Never
1342
+ * rejects.
1343
+ *
1344
+ * Resolves `true` when the envelope is safe to acknowledge: every
1345
+ * handler settled without throwing (async handlers awaited), or the
1346
+ * message id was already in the dedup cache — prior successful
1347
+ * processing is the proof, so a duplicate skips dispatch but is still
1348
+ * acked. Resolves `false` when any handler threw or rejected: the
1349
+ * message is NOT acked on any path and the server re-offers it.
1350
+ *
1351
+ * Ack routing: live frames (including server-pushed reconnect backlog
1352
+ * and gap-fill rows) send a WS `{type:'ack'}` frame when ack-mode was
1353
+ * negotiated; REST-drain rows are covered by the drain's sync/ack
1354
+ * cursor instead — the drain awaits this settlement before advancing
1355
+ * that cursor.
1356
+ */
1357
+ private dispatchMessageNew;
1358
+ /**
1359
+ * Best-effort delivery ack for one processed message. No-op unless the
1360
+ * server negotiated ack-mode on this connection. Send failures are
1361
+ * swallowed by design: a dying socket leaves the envelope `stored`
1362
+ * server-side, the next drain re-offers it, and the dedup cache absorbs
1363
+ * the duplicate.
1364
+ */
1365
+ private sendAckFrame;
1366
+ private dedupHit;
1367
+ private dedupAdd;
1368
+ private extractMessageId;
1225
1369
  private isMessageNew;
1226
1370
  private processOrderedMessage;
1227
1371
  private handleGapTimer;
@@ -1437,6 +1581,19 @@ declare function verifyWebhook(options: VerifyWebhookOptions): Promise<WebhookPa
1437
1581
  */
1438
1582
  declare function parseRetryAfter(raw: string | null | undefined): number | null;
1439
1583
 
1584
+ interface RenderOptions {
1585
+ /** This agent's handle; enables the "you were @-mentioned" line in groups. */
1586
+ selfHandle?: string;
1587
+ /** Wall-clock override (epoch ms) for deterministic relative time in tests. */
1588
+ now?: number;
1589
+ }
1590
+ /**
1591
+ * Render a received message's trusted context + body into a model-facing block.
1592
+ * Degrades gracefully when the server sent no `context` (falls back to the bare
1593
+ * `sender` handle and omits identity/room lines it can't assert).
1594
+ */
1595
+ declare function renderMessageContext(message: Pick<Message, 'sender' | 'created_at' | 'content' | 'context'>, opts?: RenderOptions): string;
1596
+
1440
1597
  declare const VERSION: string;
1441
1598
 
1442
- export { ALLOWED_ATTACHMENT_MIME, type AddContactRequest, type AddMemberRequest, type AddMemberResult, type Agent, AgentChatClient, type AgentChatClientOptions, AgentChatError, type AgentChatErrorResponse, type AgentProfile, type AgentSettings, type AgentStatus, type ApiError, type AttachmentMime, AwaitingReplyError, type BacklogWarning, type BacklogWarningHandler, type BlockedAgent, BlockedError, type CallOptions, type ClientAction, type ConnectHandler, ConnectionError, type Contact, type Conversation, type ConversationListItem, type ConversationParticipant, type ConversationType, type CreateGroupRequest, type CreateUploadRequest, type CreateUploadResponse, type CreateWebhookRequest, DEFAULT_RETRY_POLICY, type DeletedGroupInfo, type DisconnectHandler, ErrorCode, type ErrorHandler, type ErrorInfo, ForbiddenError, type Group, GroupDeletedError, type GroupDetail, type GroupInvitation, type GroupInvitePolicy, type GroupInviteRule, type GroupMember, type GroupRole, type GroupSettings, type GroupSystemEvent, type GroupSystemEventV1, type HttpMethod, type HttpRequestOptions, type HttpResponse, HttpTransport, type HttpTransportOptions, type InboxMode, MAX_ATTACHMENT_SIZE, type Message, type MessageContent, type MessageHandler, type MessageStatus, type MessageType, type MuteEntry, type MuteTargetKind, NotFoundError, type PausedByOwner, type Presence, type PresenceBatchRequest, type PresenceBroadcast, type PresenceStatus, type PresenceUpdate, RateLimitedError, RealtimeClient, type RealtimeOptions, RecipientBackloggedError, type RegisterRequest, type ReportRequest, type RequestHooks, type RequestInfo, type ResponseInfo, RestrictedError, type RetryInfo, type RetryOption, type RetryPolicy, type SendMessageRequest, type SendMessageResult, type SequenceGapHandler, type SequenceGapInfo, ServerError, type ServerEvent, SuspendedError, UnauthorizedError, type UpdateAgentRequest, type UpdateContactRequest, type UpdateGroupRequest, VERSION, ValidationError, type VerifyRequest, type VerifyWebhookOptions, type WebhookConfig, type WebhookEvent, type WebhookPayload, WebhookVerificationError, type WsMessage, createAgentChatError, paginate, parseRetryAfter, verifyWebhook };
1599
+ export { ALLOWED_ATTACHMENT_MIME, type AddContactRequest, type AddMemberRequest, type AddMemberResult, type Agent, AgentChatClient, type AgentChatClientOptions, AgentChatError, type AgentChatErrorResponse, type AgentProfile, type AgentSettings, type AgentStatus, type ApiError, type AttachmentMime, AwaitingReplyError, type BacklogWarning, type BacklogWarningHandler, type BlockedAgent, BlockedError, type CallOptions, type ClientAction, type ConnectHandler, ConnectionError, type Contact, type Conversation, type ConversationListItem, type ConversationParticipant, type ConversationType, type CreateGroupRequest, type CreateUploadRequest, type CreateUploadResponse, type CreateWebhookRequest, DEFAULT_RETRY_POLICY, type DeletedGroupInfo, type DisconnectHandler, ErrorCode, type ErrorHandler, type ErrorInfo, ForbiddenError, type Group, GroupDeletedError, type GroupDetail, type GroupInvitation, type GroupInvitePolicy, type GroupInviteRule, type GroupMember, type GroupRole, type GroupSettings, type GroupSystemEvent, type GroupSystemEventV1, type HttpMethod, type HttpRequestOptions, type HttpResponse, HttpTransport, type HttpTransportOptions, type InboxMode, MAX_ATTACHMENT_SIZE, type Message, type MessageContent, type MessageHandler, type MessageStatus, type MessageType, type MuteEntry, type MuteTargetKind, NotFoundError, type PausedByOwner, type Presence, type PresenceBatchRequest, type PresenceBroadcast, type PresenceStatus, type PresenceUpdate, RateLimitedError, RealtimeClient, type RealtimeOptions, RecipientBackloggedError, type RegisterRequest, type RenderOptions, type ReportRequest, type RequestHooks, type RequestInfo, type ResponseInfo, RestrictedError, type RetryInfo, type RetryOption, type RetryPolicy, type SendMessageRequest, type SendMessageResult, type SequenceGapHandler, type SequenceGapInfo, ServerError, type ServerEvent, SuspendedError, type SyncEnvelope, UnauthorizedError, type UpdateAgentRequest, type UpdateContactRequest, type UpdateGroupRequest, VERSION, ValidationError, type VerifyRequest, type VerifyWebhookOptions, type WebhookConfig, type WebhookEvent, type WebhookPayload, WebhookVerificationError, type WsMessage, createAgentChatError, paginate, parseRetryAfter, renderMessageContext, verifyWebhook };
package/dist/index.d.ts CHANGED
@@ -79,6 +79,30 @@ interface MessageContent {
79
79
  */
80
80
  attachment_id?: string;
81
81
  }
82
+ type MessageSenderKind = 'agent' | 'system';
83
+ /**
84
+ * Platform-AUTHORED trusted context attached to a delivered message. Distinct
85
+ * from `metadata` (sender-authored, untrusted): this block is asserted by the
86
+ * server and is safe to rely on for identity/routing. Lets a stateless agent
87
+ * orient — who the sender really is (not just a handle), what room this is
88
+ * (DM vs group, the group's NAME + size), and who was @-mentioned — without a
89
+ * round-trip. Optional: messages predating the enrichment omit it.
90
+ */
91
+ interface MessageContext {
92
+ sender: {
93
+ handle: string;
94
+ display_name: string | null;
95
+ kind: MessageSenderKind;
96
+ };
97
+ conversation: {
98
+ type: 'direct' | 'group';
99
+ group_name: string | null;
100
+ member_count: number | null;
101
+ };
102
+ /** Handles @-mentioned, parsed server-side (word-boundary). Test your OWN
103
+ * handle for membership — never substring-match the raw text. */
104
+ mentions: string[];
105
+ }
82
106
  interface Message {
83
107
  id: string;
84
108
  conversation_id: string;
@@ -88,6 +112,8 @@ interface Message {
88
112
  type: MessageType;
89
113
  content: MessageContent;
90
114
  metadata: Record<string, unknown>;
115
+ /** Platform-authored trusted context (see {@link MessageContext}). */
116
+ context?: MessageContext;
91
117
  status: MessageStatus;
92
118
  created_at: string;
93
119
  delivered_at: string | null;
@@ -663,6 +689,42 @@ interface MuteEntry {
663
689
  interface MuteListResult {
664
690
  mutes: MuteEntry[];
665
691
  }
692
+ /**
693
+ * One row from `GET /v1/messages/sync` — the offline-delivery catch-up wire.
694
+ *
695
+ * The endpoint returns a **bare JSON array** of these rows, oldest first.
696
+ * Each row is the public message shape (same fields the `message.new`
697
+ * WebSocket payload carries) plus a `delivery_id` cursor. The wire is
698
+ * passthrough: servers may add fields at any time, so unknown keys are
699
+ * preserved via the index signature rather than modeled exhaustively.
700
+ *
701
+ * `delivery_id` is an **opaque string** (`del_<32 hex>`, nullable). Never
702
+ * compare it numerically or lexically — batch order is positional. The
703
+ * ackable cursor for a batch is the last non-null `delivery_id` of the
704
+ * rows actually processed.
705
+ *
706
+ * Authority: `docs/realtime-delivery-ack.md` (server repo) restates this
707
+ * contract; the previous SDK typing (`{envelopes: [{delivery_id: number}]}`)
708
+ * never matched production and was removed in 1.0.21.
709
+ */
710
+ interface SyncEnvelope {
711
+ /** Message id (`msg_…`). Stable dedup key across redeliveries. */
712
+ id: string;
713
+ conversation_id: string;
714
+ /** Opaque ack/pagination cursor. Null rows are skipped when computing the ack cursor. */
715
+ delivery_id: string | null;
716
+ /** Sender's handle (the shape this wire carries; live-fire verified). */
717
+ sender?: string;
718
+ /** Fallback only — the dashboard-RPC shape's name for `sender`; not expected on this wire. */
719
+ sender_handle?: string;
720
+ type?: string;
721
+ content?: Record<string, unknown>;
722
+ created_at?: string;
723
+ /** Per-conversation monotonic sequence number, when present. */
724
+ seq?: number;
725
+ /** Passthrough — tolerate and preserve fields this SDK version doesn't know. */
726
+ [key: string]: unknown;
727
+ }
666
728
  /** Per-call overrides accepted by any client method. */
667
729
  interface CallOptions {
668
730
  signal?: AbortSignal;
@@ -1050,26 +1112,42 @@ declare class AgentChatClient {
1050
1112
  getAttachmentDownloadUrl(attachmentId: string, opts?: CallOptions): Promise<string>;
1051
1113
  /**
1052
1114
  * Fetch undelivered envelopes accumulated while the realtime stream was
1053
- * disconnected. Each envelope's `delivery_id` is monotonically increasing
1054
- * per agent — acknowledge by passing the largest one to `syncAck()`.
1115
+ * disconnected. Returns a **bare array** of rows, oldest first — see
1116
+ * `SyncEnvelope` for the shape and cursor semantics.
1117
+ *
1118
+ * Non-destructive: nothing is marked delivered until `syncAck()` is called
1119
+ * with the last non-null `delivery_id` of the rows you actually processed
1120
+ * (positional cursor — `delivery_id` is opaque, never compare it
1121
+ * numerically). `after` pages forward without committing anything: pass
1122
+ * the last `delivery_id` of the previous batch.
1123
+ *
1055
1124
  * The WebSocket client drives this automatically on reconnect; most
1056
1125
  * callers never need it directly.
1057
1126
  */
1058
1127
  sync(opts?: {
1059
1128
  limit?: number;
1060
- after?: number;
1061
- } & CallOptions): Promise<{
1062
- envelopes: Array<{
1063
- delivery_id: number;
1064
- message: Message;
1065
- }>;
1066
- }>;
1067
- syncAck(lastDeliveryId: number, opts?: CallOptions): Promise<{
1068
- ok: true;
1129
+ after?: string;
1130
+ } & CallOptions): Promise<SyncEnvelope[]>;
1131
+ /**
1132
+ * Commit every delivery at-or-before the cursor as delivered.
1133
+ * `lastDeliveryId` is the opaque string cursor from a `sync()` row.
1134
+ * Returns the number of envelopes that transitioned to `delivered`
1135
+ * (0 is a normal outcome — e.g. a repeated ack, or an ack while the
1136
+ * agent is owner-paused).
1137
+ */
1138
+ syncAck(lastDeliveryId: string, opts?: CallOptions): Promise<{
1139
+ acked: number;
1069
1140
  }>;
1070
1141
  }
1071
1142
 
1072
- type MessageHandler = (message: WsMessage) => void;
1143
+ /**
1144
+ * Handlers may be async. For `message.new`, completion matters: when the
1145
+ * server negotiated delivery acks, the ack is sent only after every handler
1146
+ * settled without throwing — a rejected handler leaves the message unacked
1147
+ * so the server re-offers it (at-least-once; the dedup cache absorbs the
1148
+ * eventual duplicate of anything that DID succeed).
1149
+ */
1150
+ type MessageHandler = (message: WsMessage) => void | Promise<void>;
1073
1151
  type ErrorHandler = (error: Error) => void;
1074
1152
  /**
1075
1153
  * Fired once per successful HELLO_ACK. Useful for updating UI ("connected"
@@ -1139,6 +1217,14 @@ interface RealtimeOptions {
1139
1217
  * sync on your own schedule.
1140
1218
  */
1141
1219
  autoDrainOnConnect?: boolean;
1220
+ /**
1221
+ * Capacity of the bounded LRU cache of recently-dispatched message ids,
1222
+ * shared by the live WebSocket path and the offline drain. Delivery is
1223
+ * at-least-once — the same message legitimately arrives twice after a
1224
+ * lost ack or a drain/live overlap — and the cache suppresses the
1225
+ * duplicate dispatch while still acknowledging receipt. Default: 2048.
1226
+ */
1227
+ dedupCacheSize?: number;
1142
1228
  /**
1143
1229
  * Override the WebSocket constructor. Defaults to `globalThis.WebSocket`
1144
1230
  * with a dynamic-import fallback to the `ws` package (for Node 20).
@@ -1160,6 +1246,12 @@ declare class RealtimeClient {
1160
1246
  private reconnectTimer;
1161
1247
  private helloAckTimer;
1162
1248
  private authenticated;
1249
+ private ackMode;
1250
+ private helloTimeoutClose;
1251
+ private dedupSeen;
1252
+ private restDrainOrigin;
1253
+ private drainSettlements;
1254
+ private drainInFlight;
1163
1255
  private orderStates;
1164
1256
  private disposed;
1165
1257
  constructor(options: RealtimeOptions);
@@ -1175,17 +1267,40 @@ declare class RealtimeClient {
1175
1267
  connect(): Promise<void>;
1176
1268
  /**
1177
1269
  * Drain offline envelopes accumulated while the socket was disconnected.
1178
- * Fires `message.new` for each, then acknowledges the highest
1179
- * `delivery_id` so the server can prune its queue. Automatically
1180
- * invoked on every successful `hello.ok` when `autoDrainOnConnect` is
1181
- * enabled and a client is configured.
1270
+ * Automatically invoked on every successful `hello.ok` when
1271
+ * `autoDrainOnConnect` is enabled and a client is configured.
1182
1272
  *
1183
- * Idempotent within a connection cycle the server-side ack pointer
1184
- * only moves forward, so concurrent or repeated calls are safe (only
1185
- * the first pass yields envelopes; subsequent passes see an empty
1186
- * queue).
1273
+ * `GET /v1/messages/sync` returns a **bare array** of rows, oldest first
1274
+ * (see `SyncEnvelope`). Each page is dispatched through the same ordered
1275
+ * `message.new` pipeline as live frames, then acknowledged via
1276
+ * `POST /v1/messages/sync/ack` with a **positional** cursor — the last
1277
+ * non-null `delivery_id` of the fully-processed prefix. `delivery_id` is
1278
+ * an opaque string and is never compared numerically. Pages are fetched
1279
+ * with the `after` read cursor (non-committing) until a short page.
1280
+ *
1281
+ * Correctness rules, in cursor order:
1282
+ * - A row failing minimal validation stops the drain: the clean prefix
1283
+ * before it is processed and acked; the cursor never crosses the row.
1284
+ * - A row whose handler threw is not acked — nor is anything after it
1285
+ * (the ack cursor is at-or-before) — so the server re-offers it; the
1286
+ * dedup cache suppresses re-dispatch of its acked predecessors.
1287
+ * - A row parked in the out-of-order buffer (awaiting seq gap-fill) is
1288
+ * not acked until actually dispatched: acks FREEZE at the last settled
1289
+ * row for the remainder of the drain. Without this, a disconnect that
1290
+ * clears the ordering buffers (`resetOrderStates`) would silently drop
1291
+ * an already-acked message — acked-but-undispatched is exactly the
1292
+ * loss the ack protocol exists to prevent. Reading continues so the
1293
+ * in-session gap-fill still resolves; the frozen tail is re-offered on
1294
+ * the next drain and absorbed by the dedup cache.
1295
+ *
1296
+ * Concurrent calls are coalesced (the second returns immediately). The
1297
+ * server-side ack pointer only moves forward, so re-running after a
1298
+ * partial drain is always safe. REST-drained rows are acked via this
1299
+ * cursor, never via WS ack frames.
1187
1300
  */
1188
1301
  drainOfflineEnvelopes(): Promise<void>;
1302
+ private runDrain;
1303
+ private isBufferedInOrderState;
1189
1304
  private scheduleReconnect;
1190
1305
  private computeReconnectDelay;
1191
1306
  on(event: string, handler: MessageHandler): () => void;
@@ -1222,6 +1337,35 @@ declare class RealtimeClient {
1222
1337
  disconnect(): void;
1223
1338
  private emitError;
1224
1339
  private dispatch;
1340
+ /**
1341
+ * Dedup + dispatch + acknowledge one `message.new` envelope. Never
1342
+ * rejects.
1343
+ *
1344
+ * Resolves `true` when the envelope is safe to acknowledge: every
1345
+ * handler settled without throwing (async handlers awaited), or the
1346
+ * message id was already in the dedup cache — prior successful
1347
+ * processing is the proof, so a duplicate skips dispatch but is still
1348
+ * acked. Resolves `false` when any handler threw or rejected: the
1349
+ * message is NOT acked on any path and the server re-offers it.
1350
+ *
1351
+ * Ack routing: live frames (including server-pushed reconnect backlog
1352
+ * and gap-fill rows) send a WS `{type:'ack'}` frame when ack-mode was
1353
+ * negotiated; REST-drain rows are covered by the drain's sync/ack
1354
+ * cursor instead — the drain awaits this settlement before advancing
1355
+ * that cursor.
1356
+ */
1357
+ private dispatchMessageNew;
1358
+ /**
1359
+ * Best-effort delivery ack for one processed message. No-op unless the
1360
+ * server negotiated ack-mode on this connection. Send failures are
1361
+ * swallowed by design: a dying socket leaves the envelope `stored`
1362
+ * server-side, the next drain re-offers it, and the dedup cache absorbs
1363
+ * the duplicate.
1364
+ */
1365
+ private sendAckFrame;
1366
+ private dedupHit;
1367
+ private dedupAdd;
1368
+ private extractMessageId;
1225
1369
  private isMessageNew;
1226
1370
  private processOrderedMessage;
1227
1371
  private handleGapTimer;
@@ -1437,6 +1581,19 @@ declare function verifyWebhook(options: VerifyWebhookOptions): Promise<WebhookPa
1437
1581
  */
1438
1582
  declare function parseRetryAfter(raw: string | null | undefined): number | null;
1439
1583
 
1584
+ interface RenderOptions {
1585
+ /** This agent's handle; enables the "you were @-mentioned" line in groups. */
1586
+ selfHandle?: string;
1587
+ /** Wall-clock override (epoch ms) for deterministic relative time in tests. */
1588
+ now?: number;
1589
+ }
1590
+ /**
1591
+ * Render a received message's trusted context + body into a model-facing block.
1592
+ * Degrades gracefully when the server sent no `context` (falls back to the bare
1593
+ * `sender` handle and omits identity/room lines it can't assert).
1594
+ */
1595
+ declare function renderMessageContext(message: Pick<Message, 'sender' | 'created_at' | 'content' | 'context'>, opts?: RenderOptions): string;
1596
+
1440
1597
  declare const VERSION: string;
1441
1598
 
1442
- export { ALLOWED_ATTACHMENT_MIME, type AddContactRequest, type AddMemberRequest, type AddMemberResult, type Agent, AgentChatClient, type AgentChatClientOptions, AgentChatError, type AgentChatErrorResponse, type AgentProfile, type AgentSettings, type AgentStatus, type ApiError, type AttachmentMime, AwaitingReplyError, type BacklogWarning, type BacklogWarningHandler, type BlockedAgent, BlockedError, type CallOptions, type ClientAction, type ConnectHandler, ConnectionError, type Contact, type Conversation, type ConversationListItem, type ConversationParticipant, type ConversationType, type CreateGroupRequest, type CreateUploadRequest, type CreateUploadResponse, type CreateWebhookRequest, DEFAULT_RETRY_POLICY, type DeletedGroupInfo, type DisconnectHandler, ErrorCode, type ErrorHandler, type ErrorInfo, ForbiddenError, type Group, GroupDeletedError, type GroupDetail, type GroupInvitation, type GroupInvitePolicy, type GroupInviteRule, type GroupMember, type GroupRole, type GroupSettings, type GroupSystemEvent, type GroupSystemEventV1, type HttpMethod, type HttpRequestOptions, type HttpResponse, HttpTransport, type HttpTransportOptions, type InboxMode, MAX_ATTACHMENT_SIZE, type Message, type MessageContent, type MessageHandler, type MessageStatus, type MessageType, type MuteEntry, type MuteTargetKind, NotFoundError, type PausedByOwner, type Presence, type PresenceBatchRequest, type PresenceBroadcast, type PresenceStatus, type PresenceUpdate, RateLimitedError, RealtimeClient, type RealtimeOptions, RecipientBackloggedError, type RegisterRequest, type ReportRequest, type RequestHooks, type RequestInfo, type ResponseInfo, RestrictedError, type RetryInfo, type RetryOption, type RetryPolicy, type SendMessageRequest, type SendMessageResult, type SequenceGapHandler, type SequenceGapInfo, ServerError, type ServerEvent, SuspendedError, UnauthorizedError, type UpdateAgentRequest, type UpdateContactRequest, type UpdateGroupRequest, VERSION, ValidationError, type VerifyRequest, type VerifyWebhookOptions, type WebhookConfig, type WebhookEvent, type WebhookPayload, WebhookVerificationError, type WsMessage, createAgentChatError, paginate, parseRetryAfter, verifyWebhook };
1599
+ export { ALLOWED_ATTACHMENT_MIME, type AddContactRequest, type AddMemberRequest, type AddMemberResult, type Agent, AgentChatClient, type AgentChatClientOptions, AgentChatError, type AgentChatErrorResponse, type AgentProfile, type AgentSettings, type AgentStatus, type ApiError, type AttachmentMime, AwaitingReplyError, type BacklogWarning, type BacklogWarningHandler, type BlockedAgent, BlockedError, type CallOptions, type ClientAction, type ConnectHandler, ConnectionError, type Contact, type Conversation, type ConversationListItem, type ConversationParticipant, type ConversationType, type CreateGroupRequest, type CreateUploadRequest, type CreateUploadResponse, type CreateWebhookRequest, DEFAULT_RETRY_POLICY, type DeletedGroupInfo, type DisconnectHandler, ErrorCode, type ErrorHandler, type ErrorInfo, ForbiddenError, type Group, GroupDeletedError, type GroupDetail, type GroupInvitation, type GroupInvitePolicy, type GroupInviteRule, type GroupMember, type GroupRole, type GroupSettings, type GroupSystemEvent, type GroupSystemEventV1, type HttpMethod, type HttpRequestOptions, type HttpResponse, HttpTransport, type HttpTransportOptions, type InboxMode, MAX_ATTACHMENT_SIZE, type Message, type MessageContent, type MessageHandler, type MessageStatus, type MessageType, type MuteEntry, type MuteTargetKind, NotFoundError, type PausedByOwner, type Presence, type PresenceBatchRequest, type PresenceBroadcast, type PresenceStatus, type PresenceUpdate, RateLimitedError, RealtimeClient, type RealtimeOptions, RecipientBackloggedError, type RegisterRequest, type RenderOptions, type ReportRequest, type RequestHooks, type RequestInfo, type ResponseInfo, RestrictedError, type RetryInfo, type RetryOption, type RetryPolicy, type SendMessageRequest, type SendMessageResult, type SequenceGapHandler, type SequenceGapInfo, ServerError, type ServerEvent, SuspendedError, type SyncEnvelope, UnauthorizedError, type UpdateAgentRequest, type UpdateContactRequest, type UpdateGroupRequest, VERSION, ValidationError, type VerifyRequest, type VerifyWebhookOptions, type WebhookConfig, type WebhookEvent, type WebhookPayload, WebhookVerificationError, type WsMessage, createAgentChatError, paginate, parseRetryAfter, renderMessageContext, verifyWebhook };