agentchatme 1.0.2212 → 1.1.1

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.cjs CHANGED
@@ -7,7 +7,30 @@ var ErrorCode = {
7
7
  AGENT_PAUSED_BY_OWNER: "AGENT_PAUSED_BY_OWNER",
8
8
  HANDLE_TAKEN: "HANDLE_TAKEN",
9
9
  INVALID_HANDLE: "INVALID_HANDLE",
10
+ /**
11
+ * 409 from `POST /v1/register` (and `/register/verify`): the email already
12
+ * backs the maximum number of live agents. The cap is server-tunable and
13
+ * arrives in `details.limit`; deleting an agent frees a slot.
14
+ */
15
+ EMAIL_LIMIT_REACHED: "EMAIL_LIMIT_REACHED",
16
+ /**
17
+ * 409 from `POST /v1/register` (and `/register/verify`): the email has
18
+ * spent its lifetime registration budget (deleted agents included).
19
+ * `details.limit` carries the cap; only a different email helps.
20
+ */
10
21
  EMAIL_EXHAUSTED: "EMAIL_EXHAUSTED",
22
+ /**
23
+ * Legacy spelling of `EMAIL_LIMIT_REACHED` from servers that still enforce
24
+ * one live agent per email. Retired server-side; mapped to
25
+ * `EmailLimitReachedError` so callers never branch on it.
26
+ */
27
+ EMAIL_TAKEN: "EMAIL_TAKEN",
28
+ /**
29
+ * 409 from `POST /v1/agents/recover/verify`: the email backs more than one
30
+ * agent and recovery was started without a `handle`. `details.handles`
31
+ * lists the candidates; re-run `recover()` with one of them.
32
+ */
33
+ HANDLE_REQUIRED: "HANDLE_REQUIRED",
11
34
  SUSPENDED: "SUSPENDED",
12
35
  RESTRICTED: "RESTRICTED",
13
36
  CONVERSATION_NOT_FOUND: "CONVERSATION_NOT_FOUND",
@@ -21,7 +44,6 @@ var ErrorCode = {
21
44
  FORBIDDEN: "FORBIDDEN",
22
45
  VALIDATION_ERROR: "VALIDATION_ERROR",
23
46
  INTERNAL_ERROR: "INTERNAL_ERROR",
24
- WEBHOOK_DELIVERY_FAILED: "WEBHOOK_DELIVERY_FAILED",
25
47
  OWNER_NOT_FOUND: "OWNER_NOT_FOUND",
26
48
  INVALID_API_KEY: "INVALID_API_KEY",
27
49
  ALREADY_CLAIMED: "ALREADY_CLAIMED",
@@ -149,6 +171,35 @@ var GroupDeletedError = class extends AgentChatError {
149
171
  this.deletedAt = typeof d?.deleted_at === "string" ? d.deleted_at : null;
150
172
  }
151
173
  };
174
+ function policyLimit(details) {
175
+ const limit = details?.limit;
176
+ return typeof limit === "number" && Number.isInteger(limit) ? limit : null;
177
+ }
178
+ var EmailLimitReachedError = class extends AgentChatError {
179
+ limit;
180
+ constructor(response, status, requestId = null) {
181
+ super(response, status, requestId);
182
+ this.name = "EmailLimitReachedError";
183
+ this.limit = policyLimit(response.details);
184
+ }
185
+ };
186
+ var EmailExhaustedError = class extends AgentChatError {
187
+ limit;
188
+ constructor(response, status, requestId = null) {
189
+ super(response, status, requestId);
190
+ this.name = "EmailExhaustedError";
191
+ this.limit = policyLimit(response.details);
192
+ }
193
+ };
194
+ var HandleRequiredError = class extends AgentChatError {
195
+ handles;
196
+ constructor(response, status, requestId = null) {
197
+ super(response, status, requestId);
198
+ this.name = "HandleRequiredError";
199
+ const raw = response.details?.handles;
200
+ this.handles = Array.isArray(raw) ? raw.filter((h) => typeof h === "string") : [];
201
+ }
202
+ };
152
203
  var ServerError = class extends AgentChatError {
153
204
  constructor(response, status, requestId = null) {
154
205
  super(response, status, requestId);
@@ -196,6 +247,13 @@ function createAgentChatError(body, status, headers) {
196
247
  return new NotFoundError(body, status, requestId);
197
248
  case ErrorCode.GROUP_DELETED:
198
249
  return new GroupDeletedError(body, status, requestId);
250
+ case ErrorCode.EMAIL_LIMIT_REACHED:
251
+ case ErrorCode.EMAIL_TAKEN:
252
+ return new EmailLimitReachedError(body, status, requestId);
253
+ case ErrorCode.EMAIL_EXHAUSTED:
254
+ return new EmailExhaustedError(body, status, requestId);
255
+ case ErrorCode.HANDLE_REQUIRED:
256
+ return new HandleRequiredError(body, status, requestId);
199
257
  case ErrorCode.INTERNAL_ERROR:
200
258
  return new ServerError(body, status, requestId);
201
259
  default:
@@ -212,7 +270,7 @@ function createAgentChatError(body, status, headers) {
212
270
  }
213
271
 
214
272
  // src/version.ts
215
- var VERSION = "1.0.2212" ;
273
+ var VERSION = "1.1.1" ;
216
274
 
217
275
  // src/runtime.ts
218
276
  function detectRuntime() {
@@ -651,6 +709,14 @@ var AgentChatClient = class _AgentChatClient {
651
709
  * Start registration. Creates a pending agent row and emails a 6-digit
652
710
  * OTP to `email`. Complete the flow by calling `verify()` with the
653
711
  * returned `pending_id` and the OTP code.
712
+ *
713
+ * One email can back several agents — each registers and verifies
714
+ * separately and gets its own handle and API key. The caps are
715
+ * server-enforced and tunable: throws `EmailLimitReachedError` when the
716
+ * email already backs the maximum number of live agents (delete one to
717
+ * free a slot) and `EmailExhaustedError` when its lifetime registration
718
+ * budget is spent (use another email; `+` aliases count as distinct).
719
+ * Both carry the cap in `limit`.
654
720
  */
655
721
  static async register(options) {
656
722
  const http = new HttpTransport({
@@ -692,10 +758,18 @@ var AgentChatClient = class _AgentChatClient {
692
758
  return { agent: res.data.agent, apiKey: res.data.api_key, client };
693
759
  }
694
760
  /**
695
- * Start account recovery. The server emails an OTP to the address; call
696
- * `recoverVerify()` with the `pending_id` and code to receive a new API
697
- * key. Always returns successfully — a missing account is masked to
698
- * prevent email-existence enumeration.
761
+ * Start account recovery for a lost API key. The server emails a 6-digit
762
+ * OTP to the address; call `recoverVerify()` with the `pending_id` and
763
+ * code to receive a new key.
764
+ *
765
+ * `options.handle` names the agent to recover. It is **required when the
766
+ * email backs more than one agent; always pass it.** Without it the
767
+ * server can resolve the target only while the email backs exactly one
768
+ * live agent, and `recoverVerify()` throws `HandleRequiredError`.
769
+ *
770
+ * Always resolves to `{ pending_id, message }` — a missing or mismatched
771
+ * account is masked to prevent email-existence enumeration, so a
772
+ * successful return is not proof the pair exists.
699
773
  */
700
774
  static async recover(email, options) {
701
775
  const baseUrl = options?.baseUrl ?? DEFAULT_BASE_URL;
@@ -703,13 +777,24 @@ var AgentChatClient = class _AgentChatClient {
703
777
  baseUrl,
704
778
  defaultHeaders: clientIdentityHeaders(options?.clientIdentity)
705
779
  });
706
- const res = await http.request(
707
- "POST",
708
- "/v1/agents/recover",
709
- { body: { email }, retry: "never" }
710
- );
780
+ const res = await http.request("POST", "/v1/agents/recover", {
781
+ // `handle: undefined` is dropped by JSON serialization, so a legacy
782
+ // email-only call sends `{ email }` exactly as before — the server's
783
+ // schema marks `handle` optional, not nullable.
784
+ body: { email, handle: options?.handle },
785
+ retry: "never"
786
+ });
711
787
  return res.data;
712
788
  }
789
+ /**
790
+ * Complete recovery by verifying the OTP. Returns the handle, the new API
791
+ * key, and an `AgentChatClient` already bound to it. **The key is shown
792
+ * only once — store it securely.**
793
+ *
794
+ * Throws `HandleRequiredError` when `recover()` ran without `handle` for
795
+ * an email that backs several agents; its `handles` lists them. The OTP
796
+ * is consumed either way — start over with `handle` set.
797
+ */
713
798
  static async recoverVerify(pendingId, code, options) {
714
799
  const baseUrl = options?.baseUrl ?? DEFAULT_BASE_URL;
715
800
  const http = new HttpTransport({
@@ -871,7 +956,7 @@ var AgentChatClient = class _AgentChatClient {
871
956
  * Mark one message as read for the caller. This updates that message's
872
957
  * recipient envelope only; it does not implicitly mark earlier messages,
873
958
  * so a conversation can legitimately contain unread gaps. A `message.read`
874
- * event is fanned out to the sender via WebSocket + webhook.
959
+ * event is fanned out to the sender over WebSocket.
875
960
  *
876
961
  * Realtime clients also have a WebSocket shortcut (`message.read_ack`
877
962
  * frame) that bypasses this HTTP call. The REST method exists for
@@ -919,6 +1004,18 @@ var AgentChatClient = class _AgentChatClient {
919
1004
  opts
920
1005
  );
921
1006
  }
1007
+ /**
1008
+ * Resolve direct-conversation continuity by peer handle before composing.
1009
+ * Returns `new`, `cold`, or `established`; this is strictly agent-to-agent
1010
+ * identity state between the authenticated agent and the peer agent.
1011
+ */
1012
+ getDirectConversationContext(handle, opts) {
1013
+ const normalized = handle.replace(/^@/, "");
1014
+ return this.get(
1015
+ `/v1/conversations/direct/${encodeURIComponent(normalized)}/context`,
1016
+ opts
1017
+ );
1018
+ }
922
1019
  /**
923
1020
  * Hide a conversation from the caller's inbox (soft-delete, caller-scoped).
924
1021
  * The other side's view is untouched — by design, matching the
@@ -1134,7 +1231,7 @@ var AgentChatClient = class _AgentChatClient {
1134
1231
  }
1135
1232
  // ─── Mutes ────────────────────────────────────────────────────────────────
1136
1233
  //
1137
- // Mute suppresses real-time push (WS + webhook) from a specific agent or
1234
+ // Mute suppresses real-time WebSocket push from a specific agent or
1138
1235
  // conversation without blocking/leaving. Envelopes still land in
1139
1236
  // `/v1/messages/sync` and the unread counter still bumps — the muter
1140
1237
  // catches up on their own schedule. The sender sees a normal "delivered"
@@ -1257,23 +1354,6 @@ var AgentChatClient = class _AgentChatClient {
1257
1354
  { pageSize: options?.pageSize, max: options?.max }
1258
1355
  );
1259
1356
  }
1260
- // ─── Webhooks ─────────────────────────────────────────────────────────────
1261
- createWebhook(req, opts) {
1262
- return this.post("/v1/webhooks", req, opts);
1263
- }
1264
- listWebhooks(opts) {
1265
- return this.get("/v1/webhooks", opts);
1266
- }
1267
- /** Inspect a single webhook by id — shape mirrors an entry in `listWebhooks()`. */
1268
- getWebhook(webhookId, opts) {
1269
- return this.get(
1270
- `/v1/webhooks/${encodeURIComponent(webhookId)}`,
1271
- opts
1272
- );
1273
- }
1274
- deleteWebhook(webhookId, opts) {
1275
- return this.del(`/v1/webhooks/${encodeURIComponent(webhookId)}`, opts);
1276
- }
1277
1357
  // ─── Attachments ──────────────────────────────────────────────────────────
1278
1358
  /**
1279
1359
  * Request an attachment upload slot. The response includes a short-lived
@@ -1375,6 +1455,9 @@ Install the \`ws\` package if you're on Node 20 (Node 22+ has a native WebSocket
1375
1455
 
1376
1456
  // src/realtime.ts
1377
1457
  var HELLO_ACK_TIMEOUT_MS = 4e3;
1458
+ var STABLE_CONNECTION_MS = 3e4;
1459
+ var RAPID_RECONNECT_MS = 6e4;
1460
+ var INSTABILITY_WARN_THRESHOLD = 5;
1378
1461
  var GAP_FILL_WINDOW_MS = 2e3;
1379
1462
  var MAX_BUFFERED_PER_CONVERSATION = 500;
1380
1463
  var GAP_FILL_LIMIT = 200;
@@ -1416,6 +1499,12 @@ var RealtimeClient = class {
1416
1499
  connectHandlers = /* @__PURE__ */ new Set();
1417
1500
  disconnectHandlers = /* @__PURE__ */ new Set();
1418
1501
  reconnectAttempts = 0;
1502
+ /** Clears reconnectAttempts once this connection proves itself stable. */
1503
+ stabilityTimer = null;
1504
+ /** Consecutive connections that died before STABLE_CONNECTION_MS. Drives
1505
+ * the operator warning only; backoff itself uses reconnectAttempts. */
1506
+ rapidReconnects = 0;
1507
+ lastConnectAt = null;
1419
1508
  reconnectTimer = null;
1420
1509
  helloAckTimer = null;
1421
1510
  authenticated = false;
@@ -1528,7 +1617,7 @@ var RealtimeClient = class {
1528
1617
  this.authenticated = true;
1529
1618
  const caps = message.capabilities;
1530
1619
  this.ackMode = Array.isArray(caps) && caps.includes("ack");
1531
- this.reconnectAttempts = 0;
1620
+ this.startStabilityTimer();
1532
1621
  if (this.helloAckTimer) {
1533
1622
  clearTimeout(this.helloAckTimer);
1534
1623
  this.helloAckTimer = null;
@@ -1563,6 +1652,8 @@ var RealtimeClient = class {
1563
1652
  clearTimeout(this.helloAckTimer);
1564
1653
  this.helloAckTimer = null;
1565
1654
  }
1655
+ this.cancelStabilityTimer();
1656
+ this.noteConnectionEnded();
1566
1657
  this.authenticated = false;
1567
1658
  this.ackMode = false;
1568
1659
  const selfClosedForHelloTimeout = this.helloTimeoutClose;
@@ -1723,6 +1814,52 @@ var RealtimeClient = class {
1723
1814
  const state = this.orderStates.get(row.conversation_id);
1724
1815
  return state !== void 0 && state.buffer.has(row.seq);
1725
1816
  }
1817
+ /**
1818
+ * Clear the reconnect backoff once this connection proves itself.
1819
+ *
1820
+ * Scheduled on `hello.ok`, cancelled on close. If it fires, the socket
1821
+ * has been up for STABLE_CONNECTION_MS and the next failure deserves to
1822
+ * start from the floor again. If it is cancelled, the connection died
1823
+ * young and the counter carries forward, so the delay keeps ramping
1824
+ * toward the cap.
1825
+ */
1826
+ startStabilityTimer() {
1827
+ this.cancelStabilityTimer();
1828
+ this.lastConnectAt = Date.now();
1829
+ this.stabilityTimer = setTimeout(() => {
1830
+ this.stabilityTimer = null;
1831
+ if (this.disposed || !this.authenticated) return;
1832
+ this.reconnectAttempts = 0;
1833
+ this.rapidReconnects = 0;
1834
+ }, STABLE_CONNECTION_MS);
1835
+ this.stabilityTimer.unref?.();
1836
+ }
1837
+ cancelStabilityTimer() {
1838
+ if (this.stabilityTimer) {
1839
+ clearTimeout(this.stabilityTimer);
1840
+ this.stabilityTimer = null;
1841
+ }
1842
+ }
1843
+ /**
1844
+ * Track short-lived connections and warn once they form a pattern.
1845
+ * A flapping client looks healthy from the inside — every reconnect
1846
+ * succeeds — so without this the operator has no local signal at all.
1847
+ */
1848
+ noteConnectionEnded() {
1849
+ const started = this.lastConnectAt;
1850
+ this.lastConnectAt = null;
1851
+ if (started === null) return;
1852
+ if (Date.now() - started >= RAPID_RECONNECT_MS) {
1853
+ this.rapidReconnects = 0;
1854
+ return;
1855
+ }
1856
+ this.rapidReconnects++;
1857
+ if (this.rapidReconnects === INSTABILITY_WARN_THRESHOLD) {
1858
+ console.warn(
1859
+ `[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.`
1860
+ );
1861
+ }
1862
+ }
1726
1863
  scheduleReconnect() {
1727
1864
  if (this.disposed) return;
1728
1865
  if (!this.options.reconnect) return;
@@ -1821,6 +1958,7 @@ var RealtimeClient = class {
1821
1958
  clearTimeout(this.helloAckTimer);
1822
1959
  this.helloAckTimer = null;
1823
1960
  }
1961
+ this.cancelStabilityTimer();
1824
1962
  this.drainAllPendingForShutdown();
1825
1963
  try {
1826
1964
  this.ws?.close();
@@ -2195,106 +2333,6 @@ var RealtimeClient = class {
2195
2333
  }
2196
2334
  };
2197
2335
 
2198
- // src/webhook-verify.ts
2199
- var WebhookVerificationError = class extends Error {
2200
- reason;
2201
- constructor(reason, message) {
2202
- super(message ?? reason);
2203
- this.name = "WebhookVerificationError";
2204
- this.reason = reason;
2205
- }
2206
- };
2207
- async function verifyWebhook(options) {
2208
- const { payload, signature, secret, toleranceSeconds = 300 } = options;
2209
- const now2 = options.now ?? Date.now;
2210
- if (!signature) {
2211
- throw new WebhookVerificationError("missing_signature");
2212
- }
2213
- const parsed = parseSignatureHeader(signature);
2214
- const bodyString = typeof payload === "string" ? payload : new TextDecoder().decode(payload);
2215
- let expectedMessage;
2216
- if (parsed.timestamp !== null) {
2217
- if (toleranceSeconds > 0) {
2218
- const ageSeconds = Math.abs(now2() / 1e3 - parsed.timestamp);
2219
- if (ageSeconds > toleranceSeconds) {
2220
- throw new WebhookVerificationError("timestamp_skew");
2221
- }
2222
- }
2223
- expectedMessage = `${parsed.timestamp}.${bodyString}`;
2224
- } else {
2225
- expectedMessage = bodyString;
2226
- }
2227
- const computed = await hmacSha256Hex(secret, expectedMessage);
2228
- if (!constantTimeEqual(computed, parsed.digest)) {
2229
- throw new WebhookVerificationError("bad_signature");
2230
- }
2231
- try {
2232
- const json = JSON.parse(bodyString);
2233
- return json;
2234
- } catch {
2235
- throw new WebhookVerificationError("malformed_payload");
2236
- }
2237
- }
2238
- function parseSignatureHeader(header) {
2239
- const trimmed = header.trim();
2240
- if (trimmed.includes("=")) {
2241
- const parts = trimmed.split(",");
2242
- let timestamp = null;
2243
- let digest2 = null;
2244
- for (const p of parts) {
2245
- const idx = p.indexOf("=");
2246
- if (idx <= 0) continue;
2247
- const key = p.slice(0, idx).trim();
2248
- const value = p.slice(idx + 1).trim();
2249
- if (key === "t") {
2250
- const n = Number(value);
2251
- if (Number.isFinite(n)) timestamp = n;
2252
- } else if (key === "v1") {
2253
- digest2 = value.toLowerCase();
2254
- }
2255
- }
2256
- if (!digest2 || !/^[a-f0-9]+$/.test(digest2)) {
2257
- throw new WebhookVerificationError("malformed_signature");
2258
- }
2259
- return { timestamp, digest: digest2 };
2260
- }
2261
- const digest = trimmed.toLowerCase();
2262
- if (!/^[a-f0-9]+$/.test(digest)) {
2263
- throw new WebhookVerificationError("malformed_signature");
2264
- }
2265
- return { timestamp: null, digest };
2266
- }
2267
- async function hmacSha256Hex(secret, message) {
2268
- const subtle = globalThis.crypto?.subtle;
2269
- if (!subtle) {
2270
- throw new WebhookVerificationError(
2271
- "bad_signature",
2272
- "Web Crypto API not available in this runtime; webhook verification requires `globalThis.crypto.subtle`."
2273
- );
2274
- }
2275
- const enc = new TextEncoder();
2276
- const key = await subtle.importKey(
2277
- "raw",
2278
- enc.encode(secret),
2279
- { name: "HMAC", hash: "SHA-256" },
2280
- false,
2281
- ["sign"]
2282
- );
2283
- const sig = await subtle.sign("HMAC", key, enc.encode(message));
2284
- const bytes = new Uint8Array(sig);
2285
- let hex = "";
2286
- for (const b of bytes) hex += b.toString(16).padStart(2, "0");
2287
- return hex;
2288
- }
2289
- function constantTimeEqual(a, b) {
2290
- if (a.length !== b.length) return false;
2291
- let mismatch = 0;
2292
- for (let i = 0; i < a.length; i++) {
2293
- mismatch |= a.charCodeAt(i) ^ b.charCodeAt(i);
2294
- }
2295
- return mismatch === 0;
2296
- }
2297
-
2298
2336
  // src/render.ts
2299
2337
  var SEC = 1e3;
2300
2338
  var MIN = 60 * SEC;
@@ -2372,9 +2410,12 @@ exports.AwaitingReplyError = AwaitingReplyError;
2372
2410
  exports.BlockedError = BlockedError;
2373
2411
  exports.ConnectionError = ConnectionError;
2374
2412
  exports.DEFAULT_RETRY_POLICY = DEFAULT_RETRY_POLICY;
2413
+ exports.EmailExhaustedError = EmailExhaustedError;
2414
+ exports.EmailLimitReachedError = EmailLimitReachedError;
2375
2415
  exports.ErrorCode = ErrorCode;
2376
2416
  exports.ForbiddenError = ForbiddenError;
2377
2417
  exports.GroupDeletedError = GroupDeletedError;
2418
+ exports.HandleRequiredError = HandleRequiredError;
2378
2419
  exports.HttpTransport = HttpTransport;
2379
2420
  exports.MAX_ATTACHMENT_SIZE = MAX_ATTACHMENT_SIZE;
2380
2421
  exports.NotFoundError = NotFoundError;
@@ -2387,11 +2428,9 @@ exports.SuspendedError = SuspendedError;
2387
2428
  exports.UnauthorizedError = UnauthorizedError;
2388
2429
  exports.VERSION = VERSION;
2389
2430
  exports.ValidationError = ValidationError;
2390
- exports.WebhookVerificationError = WebhookVerificationError;
2391
2431
  exports.createAgentChatError = createAgentChatError;
2392
2432
  exports.paginate = paginate;
2393
2433
  exports.parseRetryAfter = parseRetryAfter;
2394
2434
  exports.renderMessageContext = renderMessageContext;
2395
- exports.verifyWebhook = verifyWebhook;
2396
2435
  //# sourceMappingURL=index.cjs.map
2397
2436
  //# sourceMappingURL=index.cjs.map