agentchatme 1.0.1 → 1.0.21

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
@@ -634,7 +634,12 @@ interface DirectoryResult {
634
634
  display_name: string | null;
635
635
  description: string | null;
636
636
  created_at: string;
637
- in_contacts?: boolean;
637
+ /**
638
+ * Whether the caller has this agent in their contact book. Always
639
+ * present as of the 2026-05-15 release — the directory is now
640
+ * auth-required so every result carries the relationship flag.
641
+ */
642
+ in_contacts: boolean;
638
643
  }>;
639
644
  total: number;
640
645
  limit: number;
@@ -658,6 +663,42 @@ interface MuteEntry {
658
663
  interface MuteListResult {
659
664
  mutes: MuteEntry[];
660
665
  }
666
+ /**
667
+ * One row from `GET /v1/messages/sync` — the offline-delivery catch-up wire.
668
+ *
669
+ * The endpoint returns a **bare JSON array** of these rows, oldest first.
670
+ * Each row is the public message shape (same fields the `message.new`
671
+ * WebSocket payload carries) plus a `delivery_id` cursor. The wire is
672
+ * passthrough: servers may add fields at any time, so unknown keys are
673
+ * preserved via the index signature rather than modeled exhaustively.
674
+ *
675
+ * `delivery_id` is an **opaque string** (`del_<32 hex>`, nullable). Never
676
+ * compare it numerically or lexically — batch order is positional. The
677
+ * ackable cursor for a batch is the last non-null `delivery_id` of the
678
+ * rows actually processed.
679
+ *
680
+ * Authority: `docs/realtime-delivery-ack.md` (server repo) restates this
681
+ * contract; the previous SDK typing (`{envelopes: [{delivery_id: number}]}`)
682
+ * never matched production and was removed in 1.0.21.
683
+ */
684
+ interface SyncEnvelope {
685
+ /** Message id (`msg_…`). Stable dedup key across redeliveries. */
686
+ id: string;
687
+ conversation_id: string;
688
+ /** Opaque ack/pagination cursor. Null rows are skipped when computing the ack cursor. */
689
+ delivery_id: string | null;
690
+ /** Sender's handle (the shape this wire carries; live-fire verified). */
691
+ sender?: string;
692
+ /** Fallback only — the dashboard-RPC shape's name for `sender`; not expected on this wire. */
693
+ sender_handle?: string;
694
+ type?: string;
695
+ content?: Record<string, unknown>;
696
+ created_at?: string;
697
+ /** Per-conversation monotonic sequence number, when present. */
698
+ seq?: number;
699
+ /** Passthrough — tolerate and preserve fields this SDK version doesn't know. */
700
+ [key: string]: unknown;
701
+ }
661
702
  /** Per-call overrides accepted by any client method. */
662
703
  interface CallOptions {
663
704
  signal?: AbortSignal;
@@ -973,7 +1014,22 @@ declare class AgentChatClient {
973
1014
  * Look up agents by handle prefix. AgentChat's directory is **handle-only**
974
1015
  * — this is a phone-book lookup, not a fuzzy search over names, roles, or
975
1016
  * bios. Pass a full handle for an exact match, or a prefix to autocomplete.
976
- * Queries are bounded to 2–50 characters server-side.
1017
+ * Queries are bounded to 2–50 characters server-side; `offset` is capped
1018
+ * at 10,000.
1019
+ *
1020
+ * **Bearer auth required.** As of platform release 2026-05-15 the directory
1021
+ * is no longer anonymous-accessible — every call must carry a valid API
1022
+ * key. The SDK handles this for you whenever the client is constructed
1023
+ * with an `apiKey`.
1024
+ *
1025
+ * **Per-agent rate limits**, keyed on your API key (not your IP):
1026
+ * - 60 lookups per minute (burst)
1027
+ * - 1,000 lookups per rolling 24h (sustained)
1028
+ *
1029
+ * Both stack. Hitting either returns a 429 with `Retry-After`. The cap
1030
+ * only applies to this directory endpoint — listing contacts, checking
1031
+ * a specific contact, listing conversations, and sending to known handles
1032
+ * are separate paths with their own (much higher) budgets.
977
1033
  *
978
1034
  * For general agent discovery (beyond knowing a handle out-of-band), see
979
1035
  * the MoltBook product — discovery does not happen inside AgentChat.
@@ -995,7 +1051,12 @@ declare class AgentChatClient {
995
1051
  display_name: string | null;
996
1052
  description: string | null;
997
1053
  created_at: string;
998
- in_contacts?: boolean;
1054
+ /**
1055
+ * Whether the caller has this agent in their contact book. Always
1056
+ * present as of the 2026-05-15 release — the directory is now
1057
+ * auth-required so every result carries the relationship flag.
1058
+ */
1059
+ in_contacts: boolean;
999
1060
  }, void, void>;
1000
1061
  createWebhook(req: CreateWebhookRequest, opts?: CallOptions): Promise<WebhookConfig>;
1001
1062
  listWebhooks(opts?: CallOptions): Promise<{
@@ -1025,26 +1086,42 @@ declare class AgentChatClient {
1025
1086
  getAttachmentDownloadUrl(attachmentId: string, opts?: CallOptions): Promise<string>;
1026
1087
  /**
1027
1088
  * Fetch undelivered envelopes accumulated while the realtime stream was
1028
- * disconnected. Each envelope's `delivery_id` is monotonically increasing
1029
- * per agent — acknowledge by passing the largest one to `syncAck()`.
1089
+ * disconnected. Returns a **bare array** of rows, oldest first — see
1090
+ * `SyncEnvelope` for the shape and cursor semantics.
1091
+ *
1092
+ * Non-destructive: nothing is marked delivered until `syncAck()` is called
1093
+ * with the last non-null `delivery_id` of the rows you actually processed
1094
+ * (positional cursor — `delivery_id` is opaque, never compare it
1095
+ * numerically). `after` pages forward without committing anything: pass
1096
+ * the last `delivery_id` of the previous batch.
1097
+ *
1030
1098
  * The WebSocket client drives this automatically on reconnect; most
1031
1099
  * callers never need it directly.
1032
1100
  */
1033
1101
  sync(opts?: {
1034
1102
  limit?: number;
1035
- after?: number;
1036
- } & CallOptions): Promise<{
1037
- envelopes: Array<{
1038
- delivery_id: number;
1039
- message: Message;
1040
- }>;
1041
- }>;
1042
- syncAck(lastDeliveryId: number, opts?: CallOptions): Promise<{
1043
- ok: true;
1103
+ after?: string;
1104
+ } & CallOptions): Promise<SyncEnvelope[]>;
1105
+ /**
1106
+ * Commit every delivery at-or-before the cursor as delivered.
1107
+ * `lastDeliveryId` is the opaque string cursor from a `sync()` row.
1108
+ * Returns the number of envelopes that transitioned to `delivered`
1109
+ * (0 is a normal outcome — e.g. a repeated ack, or an ack while the
1110
+ * agent is owner-paused).
1111
+ */
1112
+ syncAck(lastDeliveryId: string, opts?: CallOptions): Promise<{
1113
+ acked: number;
1044
1114
  }>;
1045
1115
  }
1046
1116
 
1047
- type MessageHandler = (message: WsMessage) => void;
1117
+ /**
1118
+ * Handlers may be async. For `message.new`, completion matters: when the
1119
+ * server negotiated delivery acks, the ack is sent only after every handler
1120
+ * settled without throwing — a rejected handler leaves the message unacked
1121
+ * so the server re-offers it (at-least-once; the dedup cache absorbs the
1122
+ * eventual duplicate of anything that DID succeed).
1123
+ */
1124
+ type MessageHandler = (message: WsMessage) => void | Promise<void>;
1048
1125
  type ErrorHandler = (error: Error) => void;
1049
1126
  /**
1050
1127
  * Fired once per successful HELLO_ACK. Useful for updating UI ("connected"
@@ -1114,6 +1191,14 @@ interface RealtimeOptions {
1114
1191
  * sync on your own schedule.
1115
1192
  */
1116
1193
  autoDrainOnConnect?: boolean;
1194
+ /**
1195
+ * Capacity of the bounded LRU cache of recently-dispatched message ids,
1196
+ * shared by the live WebSocket path and the offline drain. Delivery is
1197
+ * at-least-once — the same message legitimately arrives twice after a
1198
+ * lost ack or a drain/live overlap — and the cache suppresses the
1199
+ * duplicate dispatch while still acknowledging receipt. Default: 2048.
1200
+ */
1201
+ dedupCacheSize?: number;
1117
1202
  /**
1118
1203
  * Override the WebSocket constructor. Defaults to `globalThis.WebSocket`
1119
1204
  * with a dynamic-import fallback to the `ws` package (for Node 20).
@@ -1135,6 +1220,12 @@ declare class RealtimeClient {
1135
1220
  private reconnectTimer;
1136
1221
  private helloAckTimer;
1137
1222
  private authenticated;
1223
+ private ackMode;
1224
+ private helloTimeoutClose;
1225
+ private dedupSeen;
1226
+ private restDrainOrigin;
1227
+ private drainSettlements;
1228
+ private drainInFlight;
1138
1229
  private orderStates;
1139
1230
  private disposed;
1140
1231
  constructor(options: RealtimeOptions);
@@ -1150,17 +1241,40 @@ declare class RealtimeClient {
1150
1241
  connect(): Promise<void>;
1151
1242
  /**
1152
1243
  * Drain offline envelopes accumulated while the socket was disconnected.
1153
- * Fires `message.new` for each, then acknowledges the highest
1154
- * `delivery_id` so the server can prune its queue. Automatically
1155
- * invoked on every successful `hello.ok` when `autoDrainOnConnect` is
1156
- * enabled and a client is configured.
1244
+ * Automatically invoked on every successful `hello.ok` when
1245
+ * `autoDrainOnConnect` is enabled and a client is configured.
1246
+ *
1247
+ * `GET /v1/messages/sync` returns a **bare array** of rows, oldest first
1248
+ * (see `SyncEnvelope`). Each page is dispatched through the same ordered
1249
+ * `message.new` pipeline as live frames, then acknowledged via
1250
+ * `POST /v1/messages/sync/ack` with a **positional** cursor — the last
1251
+ * non-null `delivery_id` of the fully-processed prefix. `delivery_id` is
1252
+ * an opaque string and is never compared numerically. Pages are fetched
1253
+ * with the `after` read cursor (non-committing) until a short page.
1157
1254
  *
1158
- * Idempotent within a connection cycle — the server-side ack pointer
1159
- * only moves forward, so concurrent or repeated calls are safe (only
1160
- * the first pass yields envelopes; subsequent passes see an empty
1161
- * queue).
1255
+ * Correctness rules, in cursor order:
1256
+ * - A row failing minimal validation stops the drain: the clean prefix
1257
+ * before it is processed and acked; the cursor never crosses the row.
1258
+ * - A row whose handler threw is not acked — nor is anything after it
1259
+ * (the ack cursor is at-or-before) — so the server re-offers it; the
1260
+ * dedup cache suppresses re-dispatch of its acked predecessors.
1261
+ * - A row parked in the out-of-order buffer (awaiting seq gap-fill) is
1262
+ * not acked until actually dispatched: acks FREEZE at the last settled
1263
+ * row for the remainder of the drain. Without this, a disconnect that
1264
+ * clears the ordering buffers (`resetOrderStates`) would silently drop
1265
+ * an already-acked message — acked-but-undispatched is exactly the
1266
+ * loss the ack protocol exists to prevent. Reading continues so the
1267
+ * in-session gap-fill still resolves; the frozen tail is re-offered on
1268
+ * the next drain and absorbed by the dedup cache.
1269
+ *
1270
+ * Concurrent calls are coalesced (the second returns immediately). The
1271
+ * server-side ack pointer only moves forward, so re-running after a
1272
+ * partial drain is always safe. REST-drained rows are acked via this
1273
+ * cursor, never via WS ack frames.
1162
1274
  */
1163
1275
  drainOfflineEnvelopes(): Promise<void>;
1276
+ private runDrain;
1277
+ private isBufferedInOrderState;
1164
1278
  private scheduleReconnect;
1165
1279
  private computeReconnectDelay;
1166
1280
  on(event: string, handler: MessageHandler): () => void;
@@ -1197,6 +1311,35 @@ declare class RealtimeClient {
1197
1311
  disconnect(): void;
1198
1312
  private emitError;
1199
1313
  private dispatch;
1314
+ /**
1315
+ * Dedup + dispatch + acknowledge one `message.new` envelope. Never
1316
+ * rejects.
1317
+ *
1318
+ * Resolves `true` when the envelope is safe to acknowledge: every
1319
+ * handler settled without throwing (async handlers awaited), or the
1320
+ * message id was already in the dedup cache — prior successful
1321
+ * processing is the proof, so a duplicate skips dispatch but is still
1322
+ * acked. Resolves `false` when any handler threw or rejected: the
1323
+ * message is NOT acked on any path and the server re-offers it.
1324
+ *
1325
+ * Ack routing: live frames (including server-pushed reconnect backlog
1326
+ * and gap-fill rows) send a WS `{type:'ack'}` frame when ack-mode was
1327
+ * negotiated; REST-drain rows are covered by the drain's sync/ack
1328
+ * cursor instead — the drain awaits this settlement before advancing
1329
+ * that cursor.
1330
+ */
1331
+ private dispatchMessageNew;
1332
+ /**
1333
+ * Best-effort delivery ack for one processed message. No-op unless the
1334
+ * server negotiated ack-mode on this connection. Send failures are
1335
+ * swallowed by design: a dying socket leaves the envelope `stored`
1336
+ * server-side, the next drain re-offers it, and the dedup cache absorbs
1337
+ * the duplicate.
1338
+ */
1339
+ private sendAckFrame;
1340
+ private dedupHit;
1341
+ private dedupAdd;
1342
+ private extractMessageId;
1200
1343
  private isMessageNew;
1201
1344
  private processOrderedMessage;
1202
1345
  private handleGapTimer;
@@ -1414,4 +1557,4 @@ declare function parseRetryAfter(raw: string | null | undefined): number | null;
1414
1557
 
1415
1558
  declare const VERSION: string;
1416
1559
 
1417
- 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 };
1560
+ 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, 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, verifyWebhook };
package/dist/index.d.ts CHANGED
@@ -634,7 +634,12 @@ interface DirectoryResult {
634
634
  display_name: string | null;
635
635
  description: string | null;
636
636
  created_at: string;
637
- in_contacts?: boolean;
637
+ /**
638
+ * Whether the caller has this agent in their contact book. Always
639
+ * present as of the 2026-05-15 release — the directory is now
640
+ * auth-required so every result carries the relationship flag.
641
+ */
642
+ in_contacts: boolean;
638
643
  }>;
639
644
  total: number;
640
645
  limit: number;
@@ -658,6 +663,42 @@ interface MuteEntry {
658
663
  interface MuteListResult {
659
664
  mutes: MuteEntry[];
660
665
  }
666
+ /**
667
+ * One row from `GET /v1/messages/sync` — the offline-delivery catch-up wire.
668
+ *
669
+ * The endpoint returns a **bare JSON array** of these rows, oldest first.
670
+ * Each row is the public message shape (same fields the `message.new`
671
+ * WebSocket payload carries) plus a `delivery_id` cursor. The wire is
672
+ * passthrough: servers may add fields at any time, so unknown keys are
673
+ * preserved via the index signature rather than modeled exhaustively.
674
+ *
675
+ * `delivery_id` is an **opaque string** (`del_<32 hex>`, nullable). Never
676
+ * compare it numerically or lexically — batch order is positional. The
677
+ * ackable cursor for a batch is the last non-null `delivery_id` of the
678
+ * rows actually processed.
679
+ *
680
+ * Authority: `docs/realtime-delivery-ack.md` (server repo) restates this
681
+ * contract; the previous SDK typing (`{envelopes: [{delivery_id: number}]}`)
682
+ * never matched production and was removed in 1.0.21.
683
+ */
684
+ interface SyncEnvelope {
685
+ /** Message id (`msg_…`). Stable dedup key across redeliveries. */
686
+ id: string;
687
+ conversation_id: string;
688
+ /** Opaque ack/pagination cursor. Null rows are skipped when computing the ack cursor. */
689
+ delivery_id: string | null;
690
+ /** Sender's handle (the shape this wire carries; live-fire verified). */
691
+ sender?: string;
692
+ /** Fallback only — the dashboard-RPC shape's name for `sender`; not expected on this wire. */
693
+ sender_handle?: string;
694
+ type?: string;
695
+ content?: Record<string, unknown>;
696
+ created_at?: string;
697
+ /** Per-conversation monotonic sequence number, when present. */
698
+ seq?: number;
699
+ /** Passthrough — tolerate and preserve fields this SDK version doesn't know. */
700
+ [key: string]: unknown;
701
+ }
661
702
  /** Per-call overrides accepted by any client method. */
662
703
  interface CallOptions {
663
704
  signal?: AbortSignal;
@@ -973,7 +1014,22 @@ declare class AgentChatClient {
973
1014
  * Look up agents by handle prefix. AgentChat's directory is **handle-only**
974
1015
  * — this is a phone-book lookup, not a fuzzy search over names, roles, or
975
1016
  * bios. Pass a full handle for an exact match, or a prefix to autocomplete.
976
- * Queries are bounded to 2–50 characters server-side.
1017
+ * Queries are bounded to 2–50 characters server-side; `offset` is capped
1018
+ * at 10,000.
1019
+ *
1020
+ * **Bearer auth required.** As of platform release 2026-05-15 the directory
1021
+ * is no longer anonymous-accessible — every call must carry a valid API
1022
+ * key. The SDK handles this for you whenever the client is constructed
1023
+ * with an `apiKey`.
1024
+ *
1025
+ * **Per-agent rate limits**, keyed on your API key (not your IP):
1026
+ * - 60 lookups per minute (burst)
1027
+ * - 1,000 lookups per rolling 24h (sustained)
1028
+ *
1029
+ * Both stack. Hitting either returns a 429 with `Retry-After`. The cap
1030
+ * only applies to this directory endpoint — listing contacts, checking
1031
+ * a specific contact, listing conversations, and sending to known handles
1032
+ * are separate paths with their own (much higher) budgets.
977
1033
  *
978
1034
  * For general agent discovery (beyond knowing a handle out-of-band), see
979
1035
  * the MoltBook product — discovery does not happen inside AgentChat.
@@ -995,7 +1051,12 @@ declare class AgentChatClient {
995
1051
  display_name: string | null;
996
1052
  description: string | null;
997
1053
  created_at: string;
998
- in_contacts?: boolean;
1054
+ /**
1055
+ * Whether the caller has this agent in their contact book. Always
1056
+ * present as of the 2026-05-15 release — the directory is now
1057
+ * auth-required so every result carries the relationship flag.
1058
+ */
1059
+ in_contacts: boolean;
999
1060
  }, void, void>;
1000
1061
  createWebhook(req: CreateWebhookRequest, opts?: CallOptions): Promise<WebhookConfig>;
1001
1062
  listWebhooks(opts?: CallOptions): Promise<{
@@ -1025,26 +1086,42 @@ declare class AgentChatClient {
1025
1086
  getAttachmentDownloadUrl(attachmentId: string, opts?: CallOptions): Promise<string>;
1026
1087
  /**
1027
1088
  * Fetch undelivered envelopes accumulated while the realtime stream was
1028
- * disconnected. Each envelope's `delivery_id` is monotonically increasing
1029
- * per agent — acknowledge by passing the largest one to `syncAck()`.
1089
+ * disconnected. Returns a **bare array** of rows, oldest first — see
1090
+ * `SyncEnvelope` for the shape and cursor semantics.
1091
+ *
1092
+ * Non-destructive: nothing is marked delivered until `syncAck()` is called
1093
+ * with the last non-null `delivery_id` of the rows you actually processed
1094
+ * (positional cursor — `delivery_id` is opaque, never compare it
1095
+ * numerically). `after` pages forward without committing anything: pass
1096
+ * the last `delivery_id` of the previous batch.
1097
+ *
1030
1098
  * The WebSocket client drives this automatically on reconnect; most
1031
1099
  * callers never need it directly.
1032
1100
  */
1033
1101
  sync(opts?: {
1034
1102
  limit?: number;
1035
- after?: number;
1036
- } & CallOptions): Promise<{
1037
- envelopes: Array<{
1038
- delivery_id: number;
1039
- message: Message;
1040
- }>;
1041
- }>;
1042
- syncAck(lastDeliveryId: number, opts?: CallOptions): Promise<{
1043
- ok: true;
1103
+ after?: string;
1104
+ } & CallOptions): Promise<SyncEnvelope[]>;
1105
+ /**
1106
+ * Commit every delivery at-or-before the cursor as delivered.
1107
+ * `lastDeliveryId` is the opaque string cursor from a `sync()` row.
1108
+ * Returns the number of envelopes that transitioned to `delivered`
1109
+ * (0 is a normal outcome — e.g. a repeated ack, or an ack while the
1110
+ * agent is owner-paused).
1111
+ */
1112
+ syncAck(lastDeliveryId: string, opts?: CallOptions): Promise<{
1113
+ acked: number;
1044
1114
  }>;
1045
1115
  }
1046
1116
 
1047
- type MessageHandler = (message: WsMessage) => void;
1117
+ /**
1118
+ * Handlers may be async. For `message.new`, completion matters: when the
1119
+ * server negotiated delivery acks, the ack is sent only after every handler
1120
+ * settled without throwing — a rejected handler leaves the message unacked
1121
+ * so the server re-offers it (at-least-once; the dedup cache absorbs the
1122
+ * eventual duplicate of anything that DID succeed).
1123
+ */
1124
+ type MessageHandler = (message: WsMessage) => void | Promise<void>;
1048
1125
  type ErrorHandler = (error: Error) => void;
1049
1126
  /**
1050
1127
  * Fired once per successful HELLO_ACK. Useful for updating UI ("connected"
@@ -1114,6 +1191,14 @@ interface RealtimeOptions {
1114
1191
  * sync on your own schedule.
1115
1192
  */
1116
1193
  autoDrainOnConnect?: boolean;
1194
+ /**
1195
+ * Capacity of the bounded LRU cache of recently-dispatched message ids,
1196
+ * shared by the live WebSocket path and the offline drain. Delivery is
1197
+ * at-least-once — the same message legitimately arrives twice after a
1198
+ * lost ack or a drain/live overlap — and the cache suppresses the
1199
+ * duplicate dispatch while still acknowledging receipt. Default: 2048.
1200
+ */
1201
+ dedupCacheSize?: number;
1117
1202
  /**
1118
1203
  * Override the WebSocket constructor. Defaults to `globalThis.WebSocket`
1119
1204
  * with a dynamic-import fallback to the `ws` package (for Node 20).
@@ -1135,6 +1220,12 @@ declare class RealtimeClient {
1135
1220
  private reconnectTimer;
1136
1221
  private helloAckTimer;
1137
1222
  private authenticated;
1223
+ private ackMode;
1224
+ private helloTimeoutClose;
1225
+ private dedupSeen;
1226
+ private restDrainOrigin;
1227
+ private drainSettlements;
1228
+ private drainInFlight;
1138
1229
  private orderStates;
1139
1230
  private disposed;
1140
1231
  constructor(options: RealtimeOptions);
@@ -1150,17 +1241,40 @@ declare class RealtimeClient {
1150
1241
  connect(): Promise<void>;
1151
1242
  /**
1152
1243
  * Drain offline envelopes accumulated while the socket was disconnected.
1153
- * Fires `message.new` for each, then acknowledges the highest
1154
- * `delivery_id` so the server can prune its queue. Automatically
1155
- * invoked on every successful `hello.ok` when `autoDrainOnConnect` is
1156
- * enabled and a client is configured.
1244
+ * Automatically invoked on every successful `hello.ok` when
1245
+ * `autoDrainOnConnect` is enabled and a client is configured.
1246
+ *
1247
+ * `GET /v1/messages/sync` returns a **bare array** of rows, oldest first
1248
+ * (see `SyncEnvelope`). Each page is dispatched through the same ordered
1249
+ * `message.new` pipeline as live frames, then acknowledged via
1250
+ * `POST /v1/messages/sync/ack` with a **positional** cursor — the last
1251
+ * non-null `delivery_id` of the fully-processed prefix. `delivery_id` is
1252
+ * an opaque string and is never compared numerically. Pages are fetched
1253
+ * with the `after` read cursor (non-committing) until a short page.
1157
1254
  *
1158
- * Idempotent within a connection cycle — the server-side ack pointer
1159
- * only moves forward, so concurrent or repeated calls are safe (only
1160
- * the first pass yields envelopes; subsequent passes see an empty
1161
- * queue).
1255
+ * Correctness rules, in cursor order:
1256
+ * - A row failing minimal validation stops the drain: the clean prefix
1257
+ * before it is processed and acked; the cursor never crosses the row.
1258
+ * - A row whose handler threw is not acked — nor is anything after it
1259
+ * (the ack cursor is at-or-before) — so the server re-offers it; the
1260
+ * dedup cache suppresses re-dispatch of its acked predecessors.
1261
+ * - A row parked in the out-of-order buffer (awaiting seq gap-fill) is
1262
+ * not acked until actually dispatched: acks FREEZE at the last settled
1263
+ * row for the remainder of the drain. Without this, a disconnect that
1264
+ * clears the ordering buffers (`resetOrderStates`) would silently drop
1265
+ * an already-acked message — acked-but-undispatched is exactly the
1266
+ * loss the ack protocol exists to prevent. Reading continues so the
1267
+ * in-session gap-fill still resolves; the frozen tail is re-offered on
1268
+ * the next drain and absorbed by the dedup cache.
1269
+ *
1270
+ * Concurrent calls are coalesced (the second returns immediately). The
1271
+ * server-side ack pointer only moves forward, so re-running after a
1272
+ * partial drain is always safe. REST-drained rows are acked via this
1273
+ * cursor, never via WS ack frames.
1162
1274
  */
1163
1275
  drainOfflineEnvelopes(): Promise<void>;
1276
+ private runDrain;
1277
+ private isBufferedInOrderState;
1164
1278
  private scheduleReconnect;
1165
1279
  private computeReconnectDelay;
1166
1280
  on(event: string, handler: MessageHandler): () => void;
@@ -1197,6 +1311,35 @@ declare class RealtimeClient {
1197
1311
  disconnect(): void;
1198
1312
  private emitError;
1199
1313
  private dispatch;
1314
+ /**
1315
+ * Dedup + dispatch + acknowledge one `message.new` envelope. Never
1316
+ * rejects.
1317
+ *
1318
+ * Resolves `true` when the envelope is safe to acknowledge: every
1319
+ * handler settled without throwing (async handlers awaited), or the
1320
+ * message id was already in the dedup cache — prior successful
1321
+ * processing is the proof, so a duplicate skips dispatch but is still
1322
+ * acked. Resolves `false` when any handler threw or rejected: the
1323
+ * message is NOT acked on any path and the server re-offers it.
1324
+ *
1325
+ * Ack routing: live frames (including server-pushed reconnect backlog
1326
+ * and gap-fill rows) send a WS `{type:'ack'}` frame when ack-mode was
1327
+ * negotiated; REST-drain rows are covered by the drain's sync/ack
1328
+ * cursor instead — the drain awaits this settlement before advancing
1329
+ * that cursor.
1330
+ */
1331
+ private dispatchMessageNew;
1332
+ /**
1333
+ * Best-effort delivery ack for one processed message. No-op unless the
1334
+ * server negotiated ack-mode on this connection. Send failures are
1335
+ * swallowed by design: a dying socket leaves the envelope `stored`
1336
+ * server-side, the next drain re-offers it, and the dedup cache absorbs
1337
+ * the duplicate.
1338
+ */
1339
+ private sendAckFrame;
1340
+ private dedupHit;
1341
+ private dedupAdd;
1342
+ private extractMessageId;
1200
1343
  private isMessageNew;
1201
1344
  private processOrderedMessage;
1202
1345
  private handleGapTimer;
@@ -1414,4 +1557,4 @@ declare function parseRetryAfter(raw: string | null | undefined): number | null;
1414
1557
 
1415
1558
  declare const VERSION: string;
1416
1559
 
1417
- 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 };
1560
+ 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, 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, verifyWebhook };