@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.
- package/.turbo/turbo-build.log +5 -5
- package/.turbo/turbo-test.log +29 -0
- package/CHANGELOG.md +24 -0
- package/dist/index.d.ts +151 -4
- package/dist/index.js +627 -14
- package/package.json +14 -3
- package/src/adapters/email/utils.ts +259 -0
- package/src/adapters/resend/index.ts +653 -0
- package/src/adapters/slack/index.ts +7 -1
- package/src/bridge.ts +43 -13
- package/src/index.ts +15 -0
- package/src/types.ts +53 -4
- package/test/adapters/email-utils.test.ts +290 -0
- package/test/adapters/resend.test.ts +108 -0
- package/test/bridge.test.ts +121 -8
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
> @poncho-ai/messaging@0.2.
|
|
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
|
[34mCLI[39m Building entry: src/index.ts
|
|
@@ -7,8 +7,8 @@
|
|
|
7
7
|
[34mCLI[39m tsup v8.5.1
|
|
8
8
|
[34mCLI[39m Target: es2022
|
|
9
9
|
[34mESM[39m Build start
|
|
10
|
-
[32mESM[39m [1mdist/index.js [22m[
|
|
11
|
-
[32mESM[39m ⚡️ Build success in
|
|
10
|
+
[32mESM[39m [1mdist/index.js [22m[32m29.35 KB[39m
|
|
11
|
+
[32mESM[39m ⚡️ Build success in 39ms
|
|
12
12
|
[34mDTS[39m Build start
|
|
13
|
-
[32mDTS[39m ⚡️ Build success in
|
|
14
|
-
[32mDTS[39m [1mdist/index.d.ts [22m[
|
|
13
|
+
[32mDTS[39m ⚡️ Build success in 1509ms
|
|
14
|
+
[32mDTS[39m [1mdist/index.d.ts [22m[32m8.89 KB[39m
|
|
@@ -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
|
+
[7m[1m[36m RUN [39m[22m[27m [36mv1.6.1[39m [90m/Users/cesar/Dev/latitude/poncho-ai/packages/messaging[39m
|
|
7
|
+
|
|
8
|
+
[90mstderr[2m | test/bridge.test.ts[2m > [22m[2mAgentBridge[2m > [22m[2mposts an error message and cleans up on runner failure[22m[39m
|
|
9
|
+
[agent-bridge] handleMessage error: Model overloaded
|
|
10
|
+
|
|
11
|
+
[90mstderr[2m | test/bridge.test.ts[2m > [22m[2mAgentBridge[2m > [22m[2mskips sendReply when autoReply is false[22m[39m
|
|
12
|
+
[agent-bridge] tool mode completed without send_email being called; no reply sent
|
|
13
|
+
|
|
14
|
+
[90mstderr[2m | test/bridge.test.ts[2m > [22m[2mAgentBridge[2m > [22m[2msuppresses error reply when hasSentInCurrentRequest is true[22m[39m
|
|
15
|
+
[agent-bridge] handleMessage error: Oops
|
|
16
|
+
|
|
17
|
+
[90mstderr[2m | test/bridge.test.ts[2m > [22m[2mAgentBridge[2m > [22m[2mcalls resetRequestState before handling each message[22m[39m
|
|
18
|
+
[agent-bridge] tool mode completed without send_email being called; no reply sent
|
|
19
|
+
|
|
20
|
+
[32m✓[39m test/bridge.test.ts [2m ([22m[2m15 tests[22m[2m)[22m[90m 12[2mms[22m[39m
|
|
21
|
+
[32m✓[39m test/adapters/email-utils.test.ts [2m ([22m[2m44 tests[22m[2m)[22m[90m 79[2mms[22m[39m
|
|
22
|
+
[32m✓[39m test/adapters/resend.test.ts [2m ([22m[2m13 tests[22m[2m)[22m[90m 11[2mms[22m[39m
|
|
23
|
+
[32m✓[39m test/adapters/slack.test.ts [2m ([22m[2m17 tests[22m[2m)[22m[90m 62[2mms[22m[39m
|
|
24
|
+
|
|
25
|
+
[2m Test Files [22m [1m[32m4 passed[39m[22m[90m (4)[39m
|
|
26
|
+
[2m Tests [22m [1m[32m89 passed[39m[22m[90m (89)[39m
|
|
27
|
+
[2m Start at [22m 09:39:50
|
|
28
|
+
[2m Duration [22m 1.33s[2m (transform 355ms, setup 0ms, collect 774ms, tests 164ms, environment 1ms, prepare 1.04s)[22m
|
|
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
|
|
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
|
|
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
|
-
|
|
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 };
|