@poncho-ai/messaging 0.2.7 → 0.2.9

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.
@@ -1,5 +1,5 @@
1
1
 
2
- > @poncho-ai/messaging@0.2.7 build /home/runner/work/poncho-ai/poncho-ai/packages/messaging
2
+ > @poncho-ai/messaging@0.2.9 build /Users/cesar/Dev/latitude/poncho-ai/packages/messaging
3
3
  > tsup src/index.ts --format esm --dts
4
4
 
5
5
  CLI Building entry: src/index.ts
@@ -7,8 +7,8 @@
7
7
  CLI tsup v8.5.1
8
8
  CLI Target: es2022
9
9
  ESM Build start
10
- ESM dist/index.js 29.65 KB
11
- ESM ⚡️ Build success in 53ms
10
+ ESM dist/index.js 30.49 KB
11
+ ESM ⚡️ Build success in 58ms
12
12
  DTS Build start
13
- DTS ⚡️ Build success in 4785ms
13
+ DTS ⚡️ Build success in 1611ms
14
14
  DTS dist/index.d.ts 8.98 KB
@@ -0,0 +1,29 @@
1
+
2
+ > @poncho-ai/messaging@0.2.4 test /Users/cesar/Dev/latitude/poncho-ai/packages/messaging
3
+ > vitest
4
+
5
+
6
+  RUN  v1.6.1 /Users/cesar/Dev/latitude/poncho-ai/packages/messaging
7
+
8
+ stderr | test/bridge.test.ts > AgentBridge > posts an error message and cleans up on runner failure
9
+ [agent-bridge] handleMessage error: Model overloaded
10
+
11
+ stderr | test/bridge.test.ts > AgentBridge > skips sendReply when autoReply is false
12
+ [agent-bridge] tool mode completed without send_email being called; no reply sent
13
+
14
+ stderr | test/bridge.test.ts > AgentBridge > suppresses error reply when hasSentInCurrentRequest is true
15
+ [agent-bridge] handleMessage error: Oops
16
+
17
+ stderr | test/bridge.test.ts > AgentBridge > calls resetRequestState before handling each message
18
+ [agent-bridge] tool mode completed without send_email being called; no reply sent
19
+
20
+ ✓ test/bridge.test.ts  (15 tests) 7ms
21
+ ✓ test/adapters/email-utils.test.ts  (44 tests) 13ms
22
+ ✓ test/adapters/resend.test.ts  (13 tests) 7ms
23
+ ✓ test/adapters/slack.test.ts  (17 tests) 61ms
24
+
25
+  Test Files  4 passed (4)
26
+  Tests  89 passed (89)
27
+  Start at  17:47:42
28
+  Duration  442ms (transform 223ms, setup 0ms, collect 336ms, tests 88ms, environment 0ms, prepare 304ms)
29
+
package/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # @poncho-ai/messaging
2
2
 
3
+ ## 0.2.9
4
+
5
+ ### Patch Changes
6
+
7
+ - [`e9b801f`](https://github.com/cesr/poncho-ai/commit/e9b801f0c70ffab6cb434b7adf05df22b29ea9fe) Thanks [@cesr](https://github.com/cesr)! - Derive deterministic UUIDs for messaging conversation IDs instead of composite strings. Fixes Latitude telemetry rejection and ensures consistency with web UI/API conversations.
8
+
9
+ ## 0.2.8
10
+
11
+ ### Patch Changes
12
+
13
+ - [`deb134e`](https://github.com/cesr/poncho-ai/commit/deb134e8a6ecf38d85dc200f57998e33406eff61) Thanks [@cesr](https://github.com/cesr)! - Retry Resend API requests on transient socket errors (common on Vercel cold starts).
14
+
3
15
  ## 0.2.7
4
16
 
5
17
  ### Patch Changes
package/dist/index.js CHANGED
@@ -1,5 +1,16 @@
1
1
  // src/bridge.ts
2
- var conversationIdFromThread = (platform, ref) => `${platform}:${ref.channelId}:${ref.platformThreadId}`;
2
+ import { createHash } from "crypto";
3
+ var conversationIdFromThread = (platform, ref) => {
4
+ const key = `${platform}:${ref.channelId}:${ref.platformThreadId}`;
5
+ const hex = createHash("sha256").update(key).digest("hex").slice(0, 32);
6
+ return [
7
+ hex.slice(0, 8),
8
+ hex.slice(8, 12),
9
+ `4${hex.slice(13, 16)}`,
10
+ (parseInt(hex.slice(16, 18), 16) & 63 | 128).toString(16).padStart(2, "0") + hex.slice(18, 20),
11
+ hex.slice(20, 32)
12
+ ].join("-");
13
+ };
3
14
  var AgentBridge = class {
4
15
  adapter;
5
16
  runner;
@@ -319,7 +330,7 @@ var SlackAdapter = class {
319
330
  import { createHmac as createHmac2 } from "crypto";
320
331
 
321
332
  // src/adapters/email/utils.ts
322
- import { createHash } from "crypto";
333
+ import { createHash as createHash2 } from "crypto";
323
334
  var ADDR_RE = /<([^>]+)>/;
324
335
  function extractEmailAddress(formatted) {
325
336
  const match = ADDR_RE.exec(formatted);
@@ -350,7 +361,7 @@ function deriveRootMessageId(references, currentMessageId, fallback) {
350
361
  if (references.length > 0) return references[0];
351
362
  if (fallback) {
352
363
  const normalised = normaliseSubject(fallback.subject) + "\0" + fallback.sender.toLowerCase();
353
- const hash = createHash("sha256").update(normalised).digest("hex").slice(0, 16);
364
+ const hash = createHash2("sha256").update(normalised).digest("hex").slice(0, 16);
354
365
  return `<fallback:${hash}>`;
355
366
  }
356
367
  return currentMessageId;
@@ -450,6 +461,20 @@ function matchesSenderPattern(sender, patterns) {
450
461
  }
451
462
 
452
463
  // src/adapters/resend/index.ts
464
+ var isSocketError = (err) => err instanceof TypeError && err.message === "fetch failed" && err.cause?.code === "UND_ERR_SOCKET";
465
+ async function fetchWithRetry(input, init, retries = 2) {
466
+ for (let attempt = 0; ; attempt++) {
467
+ try {
468
+ return await fetch(input, init);
469
+ } catch (err) {
470
+ if (attempt < retries && isSocketError(err)) {
471
+ await new Promise((r) => setTimeout(r, 200 * (attempt + 1)));
472
+ continue;
473
+ }
474
+ throw err;
475
+ }
476
+ }
477
+ }
453
478
  function verifySvixSignature(rawBody, svixId, svixTimestamp, svixSignature, secret) {
454
479
  const now = Math.floor(Date.now() / 1e3);
455
480
  const ts = parseInt(svixTimestamp, 10);
@@ -804,7 +829,7 @@ var ResendAdapter = class {
804
829
  let emailHeaders;
805
830
  if (emailId) {
806
831
  try {
807
- const resp = await fetch(
832
+ const resp = await fetchWithRetry(
808
833
  `https://api.resend.com/emails/receiving/${emailId}`,
809
834
  { headers: { Authorization: `Bearer ${this.apiKey}` } }
810
835
  );
@@ -860,7 +885,7 @@ var ResendAdapter = class {
860
885
  if (!emailId || !webhookAttachments || webhookAttachments.length === 0) return [];
861
886
  let attachments = [];
862
887
  try {
863
- const resp = await fetch(
888
+ const resp = await fetchWithRetry(
864
889
  `https://api.resend.com/emails/receiving/${emailId}/attachments`,
865
890
  { headers: { Authorization: `Bearer ${this.apiKey}` } }
866
891
  );
@@ -879,7 +904,7 @@ var ResendAdapter = class {
879
904
  for (const att of attachments) {
880
905
  if (!att.download_url) continue;
881
906
  try {
882
- const resp = await fetch(att.download_url);
907
+ const resp = await fetchWithRetry(att.download_url);
883
908
  if (!resp.ok) continue;
884
909
  const buf = Buffer.from(await resp.arrayBuffer());
885
910
  results.push({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@poncho-ai/messaging",
3
- "version": "0.2.7",
3
+ "version": "0.2.9",
4
4
  "description": "Messaging platform adapters for Poncho agents (Slack, Telegram, etc.)",
5
5
  "repository": {
6
6
  "type": "git",
@@ -9,6 +9,30 @@ import type {
9
9
  RouteRegistrar,
10
10
  ThreadRef,
11
11
  } from "../../types.js";
12
+
13
+ const isSocketError = (err: unknown): boolean =>
14
+ err instanceof TypeError &&
15
+ err.message === "fetch failed" &&
16
+ (err as { cause?: { code?: string } }).cause?.code === "UND_ERR_SOCKET";
17
+
18
+ async function fetchWithRetry(
19
+ input: string | URL | Request,
20
+ init?: RequestInit,
21
+ retries = 2,
22
+ ): Promise<Response> {
23
+ for (let attempt = 0; ; attempt++) {
24
+ try {
25
+ return await fetch(input, init);
26
+ } catch (err) {
27
+ if (attempt < retries && isSocketError(err)) {
28
+ await new Promise((r) => setTimeout(r, 200 * (attempt + 1)));
29
+ continue;
30
+ }
31
+ throw err;
32
+ }
33
+ }
34
+ }
35
+
12
36
  import {
13
37
  buildReplyHeaders,
14
38
  buildReplySubject,
@@ -550,7 +574,7 @@ export class ResendAdapter implements MessagingAdapter {
550
574
 
551
575
  if (emailId) {
552
576
  try {
553
- const resp = await fetch(
577
+ const resp = await fetchWithRetry(
554
578
  `https://api.resend.com/emails/receiving/${emailId}`,
555
579
  { headers: { Authorization: `Bearer ${this.apiKey}` } },
556
580
  );
@@ -624,7 +648,7 @@ export class ResendAdapter implements MessagingAdapter {
624
648
  // Fetch attachment metadata (with download_url) from the Resend API
625
649
  let attachments: Array<{ filename?: string; content_type?: string; download_url?: string }> = [];
626
650
  try {
627
- const resp = await fetch(
651
+ const resp = await fetchWithRetry(
628
652
  `https://api.resend.com/emails/receiving/${emailId}/attachments`,
629
653
  { headers: { Authorization: `Bearer ${this.apiKey}` } },
630
654
  );
@@ -644,7 +668,7 @@ export class ResendAdapter implements MessagingAdapter {
644
668
  for (const att of attachments) {
645
669
  if (!att.download_url) continue;
646
670
  try {
647
- const resp = await fetch(att.download_url);
671
+ const resp = await fetchWithRetry(att.download_url);
648
672
  if (!resp.ok) continue;
649
673
  const buf = Buffer.from(await resp.arrayBuffer());
650
674
  results.push({
package/src/bridge.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { createHash } from "node:crypto";
1
2
  import type {
2
3
  AgentBridgeOptions,
3
4
  IncomingMessage,
@@ -7,13 +8,24 @@ import type {
7
8
  } from "./types.js";
8
9
 
9
10
  /**
10
- * Derive a stable conversation ID from a platform thread reference.
11
- * Format: `<platform>:<channelId>:<threadId>`
11
+ * Derive a deterministic UUID from a platform thread reference.
12
+ * SHA-256 hashes the composite key and formats 16 bytes as a UUID v4-shaped
13
+ * string, ensuring a valid UUID that's stable across requests for the same thread.
12
14
  */
13
15
  const conversationIdFromThread = (
14
16
  platform: string,
15
17
  ref: ThreadRef,
16
- ): string => `${platform}:${ref.channelId}:${ref.platformThreadId}`;
18
+ ): string => {
19
+ const key = `${platform}:${ref.channelId}:${ref.platformThreadId}`;
20
+ const hex = createHash("sha256").update(key).digest("hex").slice(0, 32);
21
+ return [
22
+ hex.slice(0, 8),
23
+ hex.slice(8, 12),
24
+ `4${hex.slice(13, 16)}`,
25
+ ((parseInt(hex.slice(16, 18), 16) & 0x3f) | 0x80).toString(16).padStart(2, "0") + hex.slice(18, 20),
26
+ hex.slice(20, 32),
27
+ ].join("-");
28
+ };
17
29
 
18
30
  export class AgentBridge {
19
31
  private readonly adapter: MessagingAdapter;