@poncho-ai/messaging 0.7.10 → 0.8.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/.turbo/turbo-build.log +4 -4
- package/CHANGELOG.md +14 -0
- package/dist/index.js +42 -1
- package/package.json +1 -1
- package/src/adapters/slack/index.ts +21 -1
- package/src/adapters/slack/utils.ts +41 -0
- package/test/adapters/slack.test.ts +92 -0
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
> @poncho-ai/messaging@0.
|
|
2
|
+
> @poncho-ai/messaging@0.8.1 build /home/runner/work/poncho-ai/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[32m53.27 KB[39m
|
|
11
|
+
[32mESM[39m ⚡️ Build success in 60ms
|
|
12
12
|
[34mDTS[39m Build start
|
|
13
|
-
[32mDTS[39m ⚡️ Build success in
|
|
13
|
+
[32mDTS[39m ⚡️ Build success in 5292ms
|
|
14
14
|
[32mDTS[39m [1mdist/index.d.ts [22m[32m11.66 KB[39m
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# @poncho-ai/messaging
|
|
2
2
|
|
|
3
|
+
## 0.8.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [`69dd20a`](https://github.com/cesr/poncho-ai/commit/69dd20ae31ada0edaf281cf451729ffe37f4df71) Thanks [@cesr](https://github.com/cesr)! - fix: use GET for Slack conversations.replies API call so thread context is fetched correctly
|
|
8
|
+
|
|
9
|
+
## 0.8.0
|
|
10
|
+
|
|
11
|
+
### Minor Changes
|
|
12
|
+
|
|
13
|
+
- [`fb7ee97`](https://github.com/cesr/poncho-ai/commit/fb7ee97f7df0dda7318a7e59565e0b53285f10c4) Thanks [@cesr](https://github.com/cesr)! - feat: include thread context when Slack bot is @mentioned in a thread reply
|
|
14
|
+
|
|
15
|
+
When the bot is @mentioned in a thread (not the parent message), the adapter now fetches prior thread messages via `conversations.replies` and prepends them as context, so the agent understands what the conversation is about.
|
|
16
|
+
|
|
3
17
|
## 0.7.10
|
|
4
18
|
|
|
5
19
|
### Patch Changes
|
package/dist/index.js
CHANGED
|
@@ -203,6 +203,29 @@ var addReaction = async (token, channel, timestamp, reaction) => {
|
|
|
203
203
|
throw new Error(`Slack reactions.add failed: ${result.error}`);
|
|
204
204
|
}
|
|
205
205
|
};
|
|
206
|
+
var fetchThreadMessages = async (token, channel, threadTs, excludeTs) => {
|
|
207
|
+
const params = new URLSearchParams({
|
|
208
|
+
channel,
|
|
209
|
+
ts: threadTs,
|
|
210
|
+
limit: "50"
|
|
211
|
+
});
|
|
212
|
+
const res = await fetch(
|
|
213
|
+
`${SLACK_API}/conversations.replies?${params.toString()}`,
|
|
214
|
+
{
|
|
215
|
+
method: "GET",
|
|
216
|
+
headers: { authorization: `Bearer ${token}` }
|
|
217
|
+
}
|
|
218
|
+
);
|
|
219
|
+
const result = await res.json();
|
|
220
|
+
if (!result.ok) {
|
|
221
|
+
console.error(
|
|
222
|
+
`[slack-adapter] conversations.replies failed: ${result.error} \u2014 ensure the bot has the "channels:history" scope`
|
|
223
|
+
);
|
|
224
|
+
return [];
|
|
225
|
+
}
|
|
226
|
+
const messages = result.messages ?? [];
|
|
227
|
+
return excludeTs ? messages.filter((m) => m.ts !== excludeTs) : messages;
|
|
228
|
+
};
|
|
206
229
|
var removeReaction = async (token, channel, timestamp, reaction) => {
|
|
207
230
|
const result = await slackFetch("reactions.remove", token, {
|
|
208
231
|
channel,
|
|
@@ -335,8 +358,26 @@ var SlackAdapter = class {
|
|
|
335
358
|
const messageTs = String(event.ts ?? "");
|
|
336
359
|
const channel = String(event.channel ?? "");
|
|
337
360
|
const userId = String(event.user ?? "");
|
|
361
|
+
let contextPrefix = "";
|
|
362
|
+
if (event.thread_ts && event.thread_ts !== event.ts) {
|
|
363
|
+
const threadMessages = await fetchThreadMessages(
|
|
364
|
+
this.botToken,
|
|
365
|
+
channel,
|
|
366
|
+
threadTs,
|
|
367
|
+
messageTs
|
|
368
|
+
// exclude the triggering message itself
|
|
369
|
+
);
|
|
370
|
+
if (threadMessages.length > 0) {
|
|
371
|
+
const formatted = threadMessages.map((m) => `<${m.user ?? "unknown"}>: ${m.text ?? ""}`).join("\n");
|
|
372
|
+
contextPrefix = `[Thread context]
|
|
373
|
+
${formatted}
|
|
374
|
+
|
|
375
|
+
[New message]
|
|
376
|
+
`;
|
|
377
|
+
}
|
|
378
|
+
}
|
|
338
379
|
const message = {
|
|
339
|
-
text,
|
|
380
|
+
text: contextPrefix + text,
|
|
340
381
|
threadRef: {
|
|
341
382
|
platformThreadId: threadTs,
|
|
342
383
|
channelId: channel,
|
package/package.json
CHANGED
|
@@ -9,6 +9,7 @@ import type {
|
|
|
9
9
|
import { verifySlackSignature } from "./verify.js";
|
|
10
10
|
import {
|
|
11
11
|
addReaction,
|
|
12
|
+
fetchThreadMessages,
|
|
12
13
|
postMessage,
|
|
13
14
|
removeReaction,
|
|
14
15
|
splitMessage,
|
|
@@ -189,8 +190,27 @@ export class SlackAdapter implements MessagingAdapter {
|
|
|
189
190
|
const channel = String(event.channel ?? "");
|
|
190
191
|
const userId = String(event.user ?? "");
|
|
191
192
|
|
|
193
|
+
// When the mention is inside a thread (thread_ts differs from ts),
|
|
194
|
+
// fetch prior thread messages so the agent has context.
|
|
195
|
+
let contextPrefix = "";
|
|
196
|
+
if (event.thread_ts && event.thread_ts !== event.ts) {
|
|
197
|
+
const threadMessages = await fetchThreadMessages(
|
|
198
|
+
this.botToken,
|
|
199
|
+
channel,
|
|
200
|
+
threadTs,
|
|
201
|
+
messageTs, // exclude the triggering message itself
|
|
202
|
+
);
|
|
203
|
+
|
|
204
|
+
if (threadMessages.length > 0) {
|
|
205
|
+
const formatted = threadMessages
|
|
206
|
+
.map((m) => `<${m.user ?? "unknown"}>: ${m.text ?? ""}`)
|
|
207
|
+
.join("\n");
|
|
208
|
+
contextPrefix = `[Thread context]\n${formatted}\n\n[New message]\n`;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
192
212
|
const message: PonchoIncomingMessage = {
|
|
193
|
-
text,
|
|
213
|
+
text: contextPrefix + text,
|
|
194
214
|
threadRef: {
|
|
195
215
|
platformThreadId: threadTs,
|
|
196
216
|
channelId: channel,
|
|
@@ -90,6 +90,47 @@ export const addReaction = async (
|
|
|
90
90
|
}
|
|
91
91
|
};
|
|
92
92
|
|
|
93
|
+
/**
|
|
94
|
+
* Fetch thread replies using `conversations.replies`.
|
|
95
|
+
* Returns messages sorted chronologically (oldest first), excluding the
|
|
96
|
+
* message identified by `excludeTs` (typically the triggering mention).
|
|
97
|
+
*/
|
|
98
|
+
export const fetchThreadMessages = async (
|
|
99
|
+
token: string,
|
|
100
|
+
channel: string,
|
|
101
|
+
threadTs: string,
|
|
102
|
+
excludeTs?: string,
|
|
103
|
+
): Promise<Array<{ user?: string; text?: string; ts: string }>> => {
|
|
104
|
+
const params = new URLSearchParams({
|
|
105
|
+
channel,
|
|
106
|
+
ts: threadTs,
|
|
107
|
+
limit: "50",
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
const res = await fetch(
|
|
111
|
+
`${SLACK_API}/conversations.replies?${params.toString()}`,
|
|
112
|
+
{
|
|
113
|
+
method: "GET",
|
|
114
|
+
headers: { authorization: `Bearer ${token}` },
|
|
115
|
+
},
|
|
116
|
+
);
|
|
117
|
+
const result = (await res.json()) as {
|
|
118
|
+
ok: boolean;
|
|
119
|
+
error?: string;
|
|
120
|
+
messages?: Array<{ user?: string; text?: string; ts: string }>;
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
if (!result.ok) {
|
|
124
|
+
console.error(
|
|
125
|
+
`[slack-adapter] conversations.replies failed: ${result.error} — ensure the bot has the "channels:history" scope`,
|
|
126
|
+
);
|
|
127
|
+
return [];
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const messages = result.messages ?? [];
|
|
131
|
+
return excludeTs ? messages.filter((m) => m.ts !== excludeTs) : messages;
|
|
132
|
+
};
|
|
133
|
+
|
|
93
134
|
export const removeReaction = async (
|
|
94
135
|
token: string,
|
|
95
136
|
channel: string,
|
|
@@ -238,4 +238,96 @@ describe("SlackAdapter", () => {
|
|
|
238
238
|
expect(received).toHaveLength(0);
|
|
239
239
|
expect(resStatus()).toBe(200);
|
|
240
240
|
});
|
|
241
|
+
|
|
242
|
+
it("includes thread context when mention is in a thread reply", async () => {
|
|
243
|
+
const adapter = new SlackAdapter();
|
|
244
|
+
await adapter.initialize();
|
|
245
|
+
const received: PonchoIncomingMessage[] = [];
|
|
246
|
+
adapter.onMessage(async (msg) => { received.push(msg); });
|
|
247
|
+
|
|
248
|
+
// Mock fetch to intercept conversations.replies
|
|
249
|
+
const originalFetch = globalThis.fetch;
|
|
250
|
+
vi.stubGlobal("fetch", vi.fn(async (url: string) => {
|
|
251
|
+
if (typeof url === "string" && url.includes("conversations.replies")) {
|
|
252
|
+
return {
|
|
253
|
+
json: async () => ({
|
|
254
|
+
ok: true,
|
|
255
|
+
messages: [
|
|
256
|
+
{ user: "U_PARENT", text: "What are the limitations of self-hosted?", ts: "100.0" },
|
|
257
|
+
{ user: "U2", text: "<@U1> can you help?", ts: "100.1" },
|
|
258
|
+
],
|
|
259
|
+
}),
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
// Pass through for other Slack API calls (reactions)
|
|
263
|
+
return { json: async () => ({ ok: true }) };
|
|
264
|
+
}));
|
|
265
|
+
|
|
266
|
+
let handler: any;
|
|
267
|
+
adapter.registerRoutes((_m, _p, h) => { handler = h; });
|
|
268
|
+
|
|
269
|
+
const body = JSON.stringify({
|
|
270
|
+
type: "event_callback",
|
|
271
|
+
event: {
|
|
272
|
+
type: "app_mention",
|
|
273
|
+
text: "<@U1> can you help?",
|
|
274
|
+
ts: "100.1",
|
|
275
|
+
thread_ts: "100.0",
|
|
276
|
+
channel: "C1",
|
|
277
|
+
user: "U2",
|
|
278
|
+
},
|
|
279
|
+
});
|
|
280
|
+
const { req, res } = makeReqRes(body);
|
|
281
|
+
await handler(req, res);
|
|
282
|
+
|
|
283
|
+
// Wait for async handler
|
|
284
|
+
await new Promise((r) => setTimeout(r, 100));
|
|
285
|
+
|
|
286
|
+
expect(received).toHaveLength(1);
|
|
287
|
+
expect(received[0]!.text).toContain("[Thread context]");
|
|
288
|
+
expect(received[0]!.text).toContain("What are the limitations of self-hosted?");
|
|
289
|
+
expect(received[0]!.text).toContain("[New message]");
|
|
290
|
+
expect(received[0]!.text).toContain("can you help?");
|
|
291
|
+
|
|
292
|
+
vi.stubGlobal("fetch", originalFetch);
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
it("does not fetch thread context for top-level mentions", async () => {
|
|
296
|
+
const adapter = new SlackAdapter();
|
|
297
|
+
await adapter.initialize();
|
|
298
|
+
const received: PonchoIncomingMessage[] = [];
|
|
299
|
+
adapter.onMessage(async (msg) => { received.push(msg); });
|
|
300
|
+
|
|
301
|
+
const fetchSpy = vi.fn(async () => ({ json: async () => ({ ok: true }) }));
|
|
302
|
+
vi.stubGlobal("fetch", fetchSpy);
|
|
303
|
+
|
|
304
|
+
let handler: any;
|
|
305
|
+
adapter.registerRoutes((_m, _p, h) => { handler = h; });
|
|
306
|
+
|
|
307
|
+
// No thread_ts → top-level message
|
|
308
|
+
const body = JSON.stringify({
|
|
309
|
+
type: "event_callback",
|
|
310
|
+
event: {
|
|
311
|
+
type: "app_mention",
|
|
312
|
+
text: "<@U1> hello",
|
|
313
|
+
ts: "200.0",
|
|
314
|
+
channel: "C1",
|
|
315
|
+
user: "U2",
|
|
316
|
+
},
|
|
317
|
+
});
|
|
318
|
+
const { req, res } = makeReqRes(body);
|
|
319
|
+
await handler(req, res);
|
|
320
|
+
|
|
321
|
+
await new Promise((r) => setTimeout(r, 100));
|
|
322
|
+
|
|
323
|
+
expect(received).toHaveLength(1);
|
|
324
|
+
expect(received[0]!.text).toBe("hello");
|
|
325
|
+
// conversations.replies should NOT have been called
|
|
326
|
+
const repliesCalls = fetchSpy.mock.calls.filter(
|
|
327
|
+
([url]: any) => typeof url === "string" && url.includes("conversations.replies"),
|
|
328
|
+
);
|
|
329
|
+
expect(repliesCalls).toHaveLength(0);
|
|
330
|
+
|
|
331
|
+
vi.restoreAllMocks();
|
|
332
|
+
});
|
|
241
333
|
});
|