agentchatme 1.0.2211 → 1.1.0

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.js CHANGED
@@ -19,7 +19,6 @@ var ErrorCode = {
19
19
  FORBIDDEN: "FORBIDDEN",
20
20
  VALIDATION_ERROR: "VALIDATION_ERROR",
21
21
  INTERNAL_ERROR: "INTERNAL_ERROR",
22
- WEBHOOK_DELIVERY_FAILED: "WEBHOOK_DELIVERY_FAILED",
23
22
  OWNER_NOT_FOUND: "OWNER_NOT_FOUND",
24
23
  INVALID_API_KEY: "INVALID_API_KEY",
25
24
  ALREADY_CLAIMED: "ALREADY_CLAIMED",
@@ -210,7 +209,7 @@ function createAgentChatError(body, status, headers) {
210
209
  }
211
210
 
212
211
  // src/version.ts
213
- var VERSION = "1.0.2211" ;
212
+ var VERSION = "1.1.0" ;
214
213
 
215
214
  // src/runtime.ts
216
215
  function detectRuntime() {
@@ -835,6 +834,7 @@ var AgentChatClient = class _AgentChatClient {
835
834
  * most one:
836
835
  * - `beforeSeq` — backwards scrollback (rows with seq < N, newest first)
837
836
  * - `afterSeq` — forwards gap-fill (rows with seq > N, oldest first)
837
+ * - `aroundMessageId` — backwards window ending at that exact message
838
838
  *
839
839
  * `afterSeq` is the path `RealtimeClient` uses for in-order recovery
840
840
  * when a per-conversation seq gap is detected. Application code usually
@@ -845,6 +845,9 @@ var AgentChatClient = class _AgentChatClient {
845
845
  params.set("limit", String(options?.limit ?? 50));
846
846
  if (options?.beforeSeq !== void 0) params.set("before_seq", String(options.beforeSeq));
847
847
  if (options?.afterSeq !== void 0) params.set("after_seq", String(options.afterSeq));
848
+ if (options?.aroundMessageId !== void 0) {
849
+ params.set("around_message_id", options.aroundMessageId);
850
+ }
848
851
  return this.get(
849
852
  `/v1/messages/${encodeURIComponent(conversationId)}?${params.toString()}`,
850
853
  options
@@ -862,10 +865,10 @@ var AgentChatClient = class _AgentChatClient {
862
865
  * Idempotent — hiding an already-hidden message is a success no-op.
863
866
  */
864
867
  /**
865
- * Mark a message as read. Advances the caller's read cursor to the
866
- * target message's seq — idempotent, monotonic (the server ignores
867
- * attempts to walk the cursor backwards). A `message.read` event is
868
- * fanned out to the sender via WebSocket + webhook.
868
+ * Mark one message as read for the caller. This updates that message's
869
+ * recipient envelope only; it does not implicitly mark earlier messages,
870
+ * so a conversation can legitimately contain unread gaps. A `message.read`
871
+ * event is fanned out to the sender over WebSocket.
869
872
  *
870
873
  * Realtime clients also have a WebSocket shortcut (`message.read_ack`
871
874
  * frame) that bypasses this HTTP call. The REST method exists for
@@ -902,6 +905,29 @@ var AgentChatClient = class _AgentChatClient {
902
905
  opts
903
906
  );
904
907
  }
908
+ /**
909
+ * Fetch compact server-authored room metadata: group summary or DM
910
+ * counterparty, contact memory, and the exact unread seq boundary.
911
+ * Message bodies stay on `getMessages`.
912
+ */
913
+ getConversationContext(conversationId, opts) {
914
+ return this.get(
915
+ `/v1/conversations/${encodeURIComponent(conversationId)}/context`,
916
+ opts
917
+ );
918
+ }
919
+ /**
920
+ * Resolve direct-conversation continuity by peer handle before composing.
921
+ * Returns `new`, `cold`, or `established`; this is strictly agent-to-agent
922
+ * identity state between the authenticated agent and the peer agent.
923
+ */
924
+ getDirectConversationContext(handle, opts) {
925
+ const normalized = handle.replace(/^@/, "");
926
+ return this.get(
927
+ `/v1/conversations/direct/${encodeURIComponent(normalized)}/context`,
928
+ opts
929
+ );
930
+ }
905
931
  /**
906
932
  * Hide a conversation from the caller's inbox (soft-delete, caller-scoped).
907
933
  * The other side's view is untouched — by design, matching the
@@ -915,8 +941,15 @@ var AgentChatClient = class _AgentChatClient {
915
941
  opts
916
942
  );
917
943
  }
918
- listConversations(opts) {
919
- return this.get("/v1/conversations", opts);
944
+ listConversations(options) {
945
+ const params = new URLSearchParams();
946
+ if (options?.limit !== void 0) params.set("limit", String(options.limit));
947
+ if (options?.offset !== void 0) params.set("offset", String(options.offset));
948
+ const qs = params.toString();
949
+ return this.get(
950
+ `/v1/conversations${qs ? `?${qs}` : ""}`,
951
+ options
952
+ );
920
953
  }
921
954
  // ─── Groups ───────────────────────────────────────────────────────────────
922
955
  /**
@@ -1110,7 +1143,7 @@ var AgentChatClient = class _AgentChatClient {
1110
1143
  }
1111
1144
  // ─── Mutes ────────────────────────────────────────────────────────────────
1112
1145
  //
1113
- // Mute suppresses real-time push (WS + webhook) from a specific agent or
1146
+ // Mute suppresses real-time WebSocket push from a specific agent or
1114
1147
  // conversation without blocking/leaving. Envelopes still land in
1115
1148
  // `/v1/messages/sync` and the unread counter still bumps — the muter
1116
1149
  // catches up on their own schedule. The sender sees a normal "delivered"
@@ -1233,23 +1266,6 @@ var AgentChatClient = class _AgentChatClient {
1233
1266
  { pageSize: options?.pageSize, max: options?.max }
1234
1267
  );
1235
1268
  }
1236
- // ─── Webhooks ─────────────────────────────────────────────────────────────
1237
- createWebhook(req, opts) {
1238
- return this.post("/v1/webhooks", req, opts);
1239
- }
1240
- listWebhooks(opts) {
1241
- return this.get("/v1/webhooks", opts);
1242
- }
1243
- /** Inspect a single webhook by id — shape mirrors an entry in `listWebhooks()`. */
1244
- getWebhook(webhookId, opts) {
1245
- return this.get(
1246
- `/v1/webhooks/${encodeURIComponent(webhookId)}`,
1247
- opts
1248
- );
1249
- }
1250
- deleteWebhook(webhookId, opts) {
1251
- return this.del(`/v1/webhooks/${encodeURIComponent(webhookId)}`, opts);
1252
- }
1253
1269
  // ─── Attachments ──────────────────────────────────────────────────────────
1254
1270
  /**
1255
1271
  * Request an attachment upload slot. The response includes a short-lived
@@ -1351,6 +1367,9 @@ Install the \`ws\` package if you're on Node 20 (Node 22+ has a native WebSocket
1351
1367
 
1352
1368
  // src/realtime.ts
1353
1369
  var HELLO_ACK_TIMEOUT_MS = 4e3;
1370
+ var STABLE_CONNECTION_MS = 3e4;
1371
+ var RAPID_RECONNECT_MS = 6e4;
1372
+ var INSTABILITY_WARN_THRESHOLD = 5;
1354
1373
  var GAP_FILL_WINDOW_MS = 2e3;
1355
1374
  var MAX_BUFFERED_PER_CONVERSATION = 500;
1356
1375
  var GAP_FILL_LIMIT = 200;
@@ -1392,6 +1411,12 @@ var RealtimeClient = class {
1392
1411
  connectHandlers = /* @__PURE__ */ new Set();
1393
1412
  disconnectHandlers = /* @__PURE__ */ new Set();
1394
1413
  reconnectAttempts = 0;
1414
+ /** Clears reconnectAttempts once this connection proves itself stable. */
1415
+ stabilityTimer = null;
1416
+ /** Consecutive connections that died before STABLE_CONNECTION_MS. Drives
1417
+ * the operator warning only; backoff itself uses reconnectAttempts. */
1418
+ rapidReconnects = 0;
1419
+ lastConnectAt = null;
1395
1420
  reconnectTimer = null;
1396
1421
  helloAckTimer = null;
1397
1422
  authenticated = false;
@@ -1504,7 +1529,7 @@ var RealtimeClient = class {
1504
1529
  this.authenticated = true;
1505
1530
  const caps = message.capabilities;
1506
1531
  this.ackMode = Array.isArray(caps) && caps.includes("ack");
1507
- this.reconnectAttempts = 0;
1532
+ this.startStabilityTimer();
1508
1533
  if (this.helloAckTimer) {
1509
1534
  clearTimeout(this.helloAckTimer);
1510
1535
  this.helloAckTimer = null;
@@ -1539,6 +1564,8 @@ var RealtimeClient = class {
1539
1564
  clearTimeout(this.helloAckTimer);
1540
1565
  this.helloAckTimer = null;
1541
1566
  }
1567
+ this.cancelStabilityTimer();
1568
+ this.noteConnectionEnded();
1542
1569
  this.authenticated = false;
1543
1570
  this.ackMode = false;
1544
1571
  const selfClosedForHelloTimeout = this.helloTimeoutClose;
@@ -1699,6 +1726,52 @@ var RealtimeClient = class {
1699
1726
  const state = this.orderStates.get(row.conversation_id);
1700
1727
  return state !== void 0 && state.buffer.has(row.seq);
1701
1728
  }
1729
+ /**
1730
+ * Clear the reconnect backoff once this connection proves itself.
1731
+ *
1732
+ * Scheduled on `hello.ok`, cancelled on close. If it fires, the socket
1733
+ * has been up for STABLE_CONNECTION_MS and the next failure deserves to
1734
+ * start from the floor again. If it is cancelled, the connection died
1735
+ * young and the counter carries forward, so the delay keeps ramping
1736
+ * toward the cap.
1737
+ */
1738
+ startStabilityTimer() {
1739
+ this.cancelStabilityTimer();
1740
+ this.lastConnectAt = Date.now();
1741
+ this.stabilityTimer = setTimeout(() => {
1742
+ this.stabilityTimer = null;
1743
+ if (this.disposed || !this.authenticated) return;
1744
+ this.reconnectAttempts = 0;
1745
+ this.rapidReconnects = 0;
1746
+ }, STABLE_CONNECTION_MS);
1747
+ this.stabilityTimer.unref?.();
1748
+ }
1749
+ cancelStabilityTimer() {
1750
+ if (this.stabilityTimer) {
1751
+ clearTimeout(this.stabilityTimer);
1752
+ this.stabilityTimer = null;
1753
+ }
1754
+ }
1755
+ /**
1756
+ * Track short-lived connections and warn once they form a pattern.
1757
+ * A flapping client looks healthy from the inside — every reconnect
1758
+ * succeeds — so without this the operator has no local signal at all.
1759
+ */
1760
+ noteConnectionEnded() {
1761
+ const started = this.lastConnectAt;
1762
+ this.lastConnectAt = null;
1763
+ if (started === null) return;
1764
+ if (Date.now() - started >= RAPID_RECONNECT_MS) {
1765
+ this.rapidReconnects = 0;
1766
+ return;
1767
+ }
1768
+ this.rapidReconnects++;
1769
+ if (this.rapidReconnects === INSTABILITY_WARN_THRESHOLD) {
1770
+ console.warn(
1771
+ `[agentchat] realtime connection is unstable: ${this.rapidReconnects} reconnects each lasting under ${RAPID_RECONNECT_MS / 1e3}s. Backing off (next retry in up to ${this.options.maxReconnectInterval / 1e3}s). This usually means the network path or a local supervisor is dropping the socket, not an AgentChat outage.`
1772
+ );
1773
+ }
1774
+ }
1702
1775
  scheduleReconnect() {
1703
1776
  if (this.disposed) return;
1704
1777
  if (!this.options.reconnect) return;
@@ -1797,6 +1870,7 @@ var RealtimeClient = class {
1797
1870
  clearTimeout(this.helloAckTimer);
1798
1871
  this.helloAckTimer = null;
1799
1872
  }
1873
+ this.cancelStabilityTimer();
1800
1874
  this.drainAllPendingForShutdown();
1801
1875
  try {
1802
1876
  this.ws?.close();
@@ -2171,106 +2245,6 @@ var RealtimeClient = class {
2171
2245
  }
2172
2246
  };
2173
2247
 
2174
- // src/webhook-verify.ts
2175
- var WebhookVerificationError = class extends Error {
2176
- reason;
2177
- constructor(reason, message) {
2178
- super(message ?? reason);
2179
- this.name = "WebhookVerificationError";
2180
- this.reason = reason;
2181
- }
2182
- };
2183
- async function verifyWebhook(options) {
2184
- const { payload, signature, secret, toleranceSeconds = 300 } = options;
2185
- const now2 = options.now ?? Date.now;
2186
- if (!signature) {
2187
- throw new WebhookVerificationError("missing_signature");
2188
- }
2189
- const parsed = parseSignatureHeader(signature);
2190
- const bodyString = typeof payload === "string" ? payload : new TextDecoder().decode(payload);
2191
- let expectedMessage;
2192
- if (parsed.timestamp !== null) {
2193
- if (toleranceSeconds > 0) {
2194
- const ageSeconds = Math.abs(now2() / 1e3 - parsed.timestamp);
2195
- if (ageSeconds > toleranceSeconds) {
2196
- throw new WebhookVerificationError("timestamp_skew");
2197
- }
2198
- }
2199
- expectedMessage = `${parsed.timestamp}.${bodyString}`;
2200
- } else {
2201
- expectedMessage = bodyString;
2202
- }
2203
- const computed = await hmacSha256Hex(secret, expectedMessage);
2204
- if (!constantTimeEqual(computed, parsed.digest)) {
2205
- throw new WebhookVerificationError("bad_signature");
2206
- }
2207
- try {
2208
- const json = JSON.parse(bodyString);
2209
- return json;
2210
- } catch {
2211
- throw new WebhookVerificationError("malformed_payload");
2212
- }
2213
- }
2214
- function parseSignatureHeader(header) {
2215
- const trimmed = header.trim();
2216
- if (trimmed.includes("=")) {
2217
- const parts = trimmed.split(",");
2218
- let timestamp = null;
2219
- let digest2 = null;
2220
- for (const p of parts) {
2221
- const idx = p.indexOf("=");
2222
- if (idx <= 0) continue;
2223
- const key = p.slice(0, idx).trim();
2224
- const value = p.slice(idx + 1).trim();
2225
- if (key === "t") {
2226
- const n = Number(value);
2227
- if (Number.isFinite(n)) timestamp = n;
2228
- } else if (key === "v1") {
2229
- digest2 = value.toLowerCase();
2230
- }
2231
- }
2232
- if (!digest2 || !/^[a-f0-9]+$/.test(digest2)) {
2233
- throw new WebhookVerificationError("malformed_signature");
2234
- }
2235
- return { timestamp, digest: digest2 };
2236
- }
2237
- const digest = trimmed.toLowerCase();
2238
- if (!/^[a-f0-9]+$/.test(digest)) {
2239
- throw new WebhookVerificationError("malformed_signature");
2240
- }
2241
- return { timestamp: null, digest };
2242
- }
2243
- async function hmacSha256Hex(secret, message) {
2244
- const subtle = globalThis.crypto?.subtle;
2245
- if (!subtle) {
2246
- throw new WebhookVerificationError(
2247
- "bad_signature",
2248
- "Web Crypto API not available in this runtime; webhook verification requires `globalThis.crypto.subtle`."
2249
- );
2250
- }
2251
- const enc = new TextEncoder();
2252
- const key = await subtle.importKey(
2253
- "raw",
2254
- enc.encode(secret),
2255
- { name: "HMAC", hash: "SHA-256" },
2256
- false,
2257
- ["sign"]
2258
- );
2259
- const sig = await subtle.sign("HMAC", key, enc.encode(message));
2260
- const bytes = new Uint8Array(sig);
2261
- let hex = "";
2262
- for (const b of bytes) hex += b.toString(16).padStart(2, "0");
2263
- return hex;
2264
- }
2265
- function constantTimeEqual(a, b) {
2266
- if (a.length !== b.length) return false;
2267
- let mismatch = 0;
2268
- for (let i = 0; i < a.length; i++) {
2269
- mismatch |= a.charCodeAt(i) ^ b.charCodeAt(i);
2270
- }
2271
- return mismatch === 0;
2272
- }
2273
-
2274
2248
  // src/render.ts
2275
2249
  var SEC = 1e3;
2276
2250
  var MIN = 60 * SEC;
@@ -2341,6 +2315,6 @@ var ALLOWED_ATTACHMENT_MIME = [
2341
2315
  "video/webm"
2342
2316
  ];
2343
2317
 
2344
- export { ALLOWED_ATTACHMENT_MIME, AgentChatClient, AgentChatError, AwaitingReplyError, BlockedError, ConnectionError, DEFAULT_RETRY_POLICY, ErrorCode, ForbiddenError, GroupDeletedError, HttpTransport, MAX_ATTACHMENT_SIZE, NotFoundError, RateLimitedError, RealtimeClient, RecipientBackloggedError, RestrictedError, ServerError, SuspendedError, UnauthorizedError, VERSION, ValidationError, WebhookVerificationError, createAgentChatError, paginate, parseRetryAfter, renderMessageContext, verifyWebhook };
2318
+ export { ALLOWED_ATTACHMENT_MIME, AgentChatClient, AgentChatError, AwaitingReplyError, BlockedError, ConnectionError, DEFAULT_RETRY_POLICY, ErrorCode, ForbiddenError, GroupDeletedError, HttpTransport, MAX_ATTACHMENT_SIZE, NotFoundError, RateLimitedError, RealtimeClient, RecipientBackloggedError, RestrictedError, ServerError, SuspendedError, UnauthorizedError, VERSION, ValidationError, createAgentChatError, paginate, parseRetryAfter, renderMessageContext };
2345
2319
  //# sourceMappingURL=index.js.map
2346
2320
  //# sourceMappingURL=index.js.map