@poncho-ai/messaging 0.2.0 → 0.2.2

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.0 build /home/runner/work/poncho-ai/poncho-ai/packages/messaging
2
+ > @poncho-ai/messaging@0.2.2 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 8.45 KB
11
- ESM ⚡️ Build success in 29ms
10
+ ESM dist/index.js 29.35 KB
11
+ ESM ⚡️ Build success in 39ms
12
12
  DTS Build start
13
- DTS ⚡️ Build success in 2735ms
14
- DTS dist/index.d.ts 3.23 KB
13
+ DTS ⚡️ Build success in 1509ms
14
+ DTS dist/index.d.ts 8.89 KB
@@ -0,0 +1,29 @@
1
+
2
+ > @poncho-ai/messaging@0.2.0 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) 12ms
21
+ ✓ test/adapters/email-utils.test.ts  (44 tests) 79ms
22
+ ✓ test/adapters/resend.test.ts  (13 tests) 11ms
23
+ ✓ test/adapters/slack.test.ts  (17 tests) 62ms
24
+
25
+  Test Files  4 passed (4)
26
+  Tests  89 passed (89)
27
+  Start at  09:39:50
28
+  Duration  1.33s (transform 355ms, setup 0ms, collect 774ms, tests 164ms, environment 1ms, prepare 1.04s)
29
+
package/CHANGELOG.md CHANGED
@@ -1,5 +1,29 @@
1
1
  # @poncho-ai/messaging
2
2
 
3
+ ## 0.2.2
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies [[`1f47bb4`](https://github.com/cesr/poncho-ai/commit/1f47bb49e5d48dc17644172012b057190b316469)]:
8
+ - @poncho-ai/sdk@1.0.3
9
+
10
+ ## 0.2.1
11
+
12
+ ### Patch Changes
13
+
14
+ - [`e000b96`](https://github.com/cesr/poncho-ai/commit/e000b96837cbbb8d95c868c91a614f458868c444) Thanks [@cesr](https://github.com/cesr)! - Durable approval checkpoints, email conversation improvements, and web UI fixes
15
+ - Simplify approval system to checkpoint-only (remove legacy blocking approvalHandler)
16
+ - Optimize checkpoint storage with delta messages instead of full history
17
+ - Add sidebar sections for conversations awaiting approval with status indicator
18
+ - Fix nested checkpoint missing baseMessageCount in resumeRunFromCheckpoint
19
+ - Improve email conversation titles (sender email + subject)
20
+ - Remove email threading — each incoming email creates its own conversation
21
+ - Fix streaming after approval to preserve existing messages (liveOnly mode)
22
+ - Preserve newlines in user messages in web UI
23
+
24
+ - Updated dependencies [[`e000b96`](https://github.com/cesr/poncho-ai/commit/e000b96837cbbb8d95c868c91a614f458868c444)]:
25
+ - @poncho-ai/sdk@1.0.2
26
+
3
27
  ## 0.2.0
4
28
 
5
29
  ### Minor Changes
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import http from 'node:http';
2
- import { Message } from '@poncho-ai/sdk';
2
+ import { ToolDefinition, Message } from '@poncho-ai/sdk';
3
3
 
4
4
  interface ThreadRef {
5
5
  platformThreadId: string;
@@ -7,8 +7,16 @@ interface ThreadRef {
7
7
  /** The specific message ID that triggered this interaction (for reactions). */
8
8
  messageId?: string;
9
9
  }
10
+ interface FileAttachment {
11
+ /** base64-encoded file data */
12
+ data: string;
13
+ mediaType: string;
14
+ filename?: string;
15
+ }
10
16
  interface IncomingMessage {
11
17
  text: string;
18
+ subject?: string;
19
+ files?: FileAttachment[];
12
20
  threadRef: ThreadRef;
13
21
  sender: {
14
22
  id: string;
@@ -22,6 +30,13 @@ type RouteHandler = (req: http.IncomingMessage, res: http.ServerResponse) => Pro
22
30
  type RouteRegistrar = (method: "GET" | "POST", path: string, handler: RouteHandler) => void;
23
31
  interface MessagingAdapter {
24
32
  readonly platform: string;
33
+ /** When true, the bridge auto-sends the agent's response as a reply. */
34
+ readonly autoReply: boolean;
35
+ /**
36
+ * Whether the adapter's tool has sent at least one message during the
37
+ * current request. Used by the bridge to suppress duplicate error replies.
38
+ */
39
+ readonly hasSentInCurrentRequest: boolean;
25
40
  /** Register HTTP routes on the host server for receiving platform events. */
26
41
  registerRoutes(router: RouteRegistrar): void;
27
42
  /** One-time startup (e.g. validate credentials). */
@@ -29,12 +44,21 @@ interface MessagingAdapter {
29
44
  /** Set the handler that processes incoming messages. */
30
45
  onMessage(handler: IncomingMessageHandler): void;
31
46
  /** Post a reply back to the originating thread. */
32
- sendReply(threadRef: ThreadRef, content: string): Promise<void>;
47
+ sendReply(threadRef: ThreadRef, content: string, options?: {
48
+ files?: FileAttachment[];
49
+ }): Promise<void>;
33
50
  /**
34
51
  * Show a processing indicator (e.g. reaction, typing).
35
52
  * Returns a cleanup function that removes the indicator.
36
53
  */
37
54
  indicateProcessing(threadRef: ThreadRef): Promise<() => Promise<void>>;
55
+ /**
56
+ * Optional: return tool definitions the agent can use (e.g. send_email).
57
+ * Called once after initialization to register tools with the harness.
58
+ */
59
+ getToolDefinitions?(): ToolDefinition[];
60
+ /** Reset per-request state (e.g. send counter, hasSentInCurrentRequest). */
61
+ resetRequestState?(): void;
38
62
  }
39
63
  interface AgentRunner {
40
64
  getOrCreateConversation(conversationId: string, meta: {
@@ -47,8 +71,18 @@ interface AgentRunner {
47
71
  run(conversationId: string, input: {
48
72
  task: string;
49
73
  messages: Message[];
74
+ files?: FileAttachment[];
75
+ metadata?: {
76
+ platform: string;
77
+ sender: {
78
+ id: string;
79
+ name?: string;
80
+ };
81
+ threadId: string;
82
+ };
50
83
  }): Promise<{
51
84
  response: string;
85
+ files?: FileAttachment[];
52
86
  }>;
53
87
  }
54
88
  interface AgentBridgeOptions {
@@ -59,12 +93,19 @@ interface AgentBridgeOptions {
59
93
  * On Vercel, pass the real `waitUntil` from `@vercel/functions`.
60
94
  */
61
95
  waitUntil?: (promise: Promise<unknown>) => void;
96
+ /**
97
+ * Override the ownerId for conversations created by this bridge.
98
+ * Defaults to the sender's ID. Set to a fixed value (e.g. "local-owner")
99
+ * so messaging conversations appear in the web UI alongside regular ones.
100
+ */
101
+ ownerId?: string;
62
102
  }
63
103
 
64
104
  declare class AgentBridge {
65
105
  private readonly adapter;
66
106
  private readonly runner;
67
107
  private readonly waitUntil;
108
+ private readonly ownerIdOverride;
68
109
  constructor(options: AgentBridgeOptions);
69
110
  /** Wire the adapter's message handler and initialise. */
70
111
  start(): Promise<void>;
@@ -77,6 +118,8 @@ interface SlackAdapterOptions {
77
118
  }
78
119
  declare class SlackAdapter implements MessagingAdapter {
79
120
  readonly platform: "slack";
121
+ readonly autoReply = true;
122
+ readonly hasSentInCurrentRequest = false;
80
123
  private botToken;
81
124
  private signingSecret;
82
125
  private readonly botTokenEnv;
@@ -86,9 +129,113 @@ declare class SlackAdapter implements MessagingAdapter {
86
129
  initialize(): Promise<void>;
87
130
  onMessage(handler: IncomingMessageHandler): void;
88
131
  registerRoutes(router: RouteRegistrar): void;
89
- sendReply(threadRef: ThreadRef, content: string): Promise<void>;
132
+ sendReply(threadRef: ThreadRef, content: string, _options?: {
133
+ files?: Array<{
134
+ data: string;
135
+ mediaType: string;
136
+ filename?: string;
137
+ }>;
138
+ }): Promise<void>;
90
139
  indicateProcessing(threadRef: ThreadRef): Promise<() => Promise<void>>;
91
140
  private handleRequest;
92
141
  }
93
142
 
94
- export { AgentBridge, type AgentBridgeOptions, type AgentRunner, type IncomingMessage, type IncomingMessageHandler, type MessagingAdapter, type RouteHandler, type RouteRegistrar, SlackAdapter, type SlackAdapterOptions, type ThreadRef };
143
+ interface ResendAdapterOptions {
144
+ apiKeyEnv?: string;
145
+ webhookSecretEnv?: string;
146
+ fromEnv?: string;
147
+ allowedSenders?: string[];
148
+ mode?: "auto-reply" | "tool";
149
+ allowedRecipients?: string[];
150
+ maxSendsPerRun?: number;
151
+ }
152
+ declare class ResendAdapter implements MessagingAdapter {
153
+ readonly platform: "resend";
154
+ readonly autoReply: boolean;
155
+ hasSentInCurrentRequest: boolean;
156
+ private resend;
157
+ private apiKey;
158
+ private webhookSecret;
159
+ private fromAddress;
160
+ private readonly apiKeyEnv;
161
+ private readonly webhookSecretEnv;
162
+ private readonly fromEnv;
163
+ private readonly allowedSenders;
164
+ private readonly allowedRecipients;
165
+ private readonly maxSendsPerRun;
166
+ private readonly mode;
167
+ private handler;
168
+ private sendCount;
169
+ /** Request-scoped thread metadata for sendReply. */
170
+ private readonly threadMeta;
171
+ /** Deduplication set for svix-id headers. */
172
+ private readonly processed;
173
+ constructor(options?: ResendAdapterOptions);
174
+ resetRequestState(): void;
175
+ initialize(): Promise<void>;
176
+ onMessage(handler: IncomingMessageHandler): void;
177
+ registerRoutes(router: RouteRegistrar): void;
178
+ sendReply(threadRef: ThreadRef, content: string, options?: {
179
+ files?: FileAttachment[];
180
+ }): Promise<void>;
181
+ indicateProcessing(_threadRef: ThreadRef): Promise<() => Promise<void>>;
182
+ getToolDefinitions(): ToolDefinition[];
183
+ private handleSendEmailTool;
184
+ private handleRequest;
185
+ private processInboundEmail;
186
+ private fetchAndDownloadAttachments;
187
+ }
188
+
189
+ /** Extract the bare email address from a formatted string like `"Name <addr>"`. */
190
+ declare function extractEmailAddress(formatted: string): string;
191
+ /** Extract the display name from `"Name <addr>"`, or return `undefined`. */
192
+ declare function extractDisplayName(formatted: string): string | undefined;
193
+ /**
194
+ * Parse a `References` header value into an ordered array of message IDs.
195
+ * Handles both space-separated and newline-folded formats.
196
+ */
197
+ declare function parseReferences(headers: Array<{
198
+ name: string;
199
+ value: string;
200
+ }> | Record<string, string> | undefined): string[];
201
+ /**
202
+ * Derive a stable root message ID for a conversation.
203
+ *
204
+ * 1. First entry in the `References` chain (the original message).
205
+ * 2. Fallback: hash of normalised subject + sender (for clients that strip References).
206
+ * 3. Last resort: the current message's own ID.
207
+ */
208
+ declare function deriveRootMessageId(references: string[], currentMessageId: string, fallback?: {
209
+ subject: string;
210
+ sender: string;
211
+ }): string;
212
+ /** Prepend `Re:` if the subject doesn't already have it. */
213
+ declare function buildReplySubject(subject: string): string;
214
+ /**
215
+ * Build `In-Reply-To` and `References` headers for an outbound reply.
216
+ */
217
+ declare function buildReplyHeaders(inReplyTo: string, existingReferences: string[]): Record<string, string>;
218
+ /**
219
+ * Strip quoted reply content from an email body (plain text).
220
+ *
221
+ * Handles common patterns from Gmail, Apple Mail, Outlook, and Thunderbird.
222
+ * Best-effort heuristic — the full original text should be preserved elsewhere
223
+ * (e.g. `IncomingMessage.raw`) for debugging.
224
+ */
225
+ declare function stripQuotedReply(text: string): string;
226
+ /**
227
+ * Convert a markdown-ish agent response to simple email-safe HTML.
228
+ *
229
+ * This is intentionally lightweight — no external dependency. It handles the
230
+ * most common patterns agents produce: paragraphs, bold, italic, inline code,
231
+ * code blocks, unordered/ordered lists, and headings.
232
+ */
233
+ declare function markdownToEmailHtml(text: string): string;
234
+ /**
235
+ * Check whether a sender email matches any pattern in an allowlist.
236
+ * Patterns can be exact addresses or domain wildcards like `*@example.com`.
237
+ * Returns `true` if the list is empty/undefined (no restriction).
238
+ */
239
+ declare function matchesSenderPattern(sender: string, patterns: string[] | undefined): boolean;
240
+
241
+ export { AgentBridge, type AgentBridgeOptions, type AgentRunner, type FileAttachment, type IncomingMessage, type IncomingMessageHandler, type MessagingAdapter, ResendAdapter, type ResendAdapterOptions, type RouteHandler, type RouteRegistrar, SlackAdapter, type SlackAdapterOptions, type ThreadRef, buildReplyHeaders, buildReplySubject, deriveRootMessageId, extractDisplayName, extractEmailAddress, markdownToEmailHtml, matchesSenderPattern, parseReferences, stripQuotedReply };