fix: keep Slack thread replies in thread
This commit is contained in:
@@ -34,6 +34,7 @@
|
|||||||
- Auto-reply: unify tool/block/final delivery across providers and apply consistent heartbeat/prefix handling. Thanks @MSch for PR #225 (superseded commit 92c953d0749143eb2a3f31f3cd6ad0e8eabf48c3).
|
- Auto-reply: unify tool/block/final delivery across providers and apply consistent heartbeat/prefix handling. Thanks @MSch for PR #225 (superseded commit 92c953d0749143eb2a3f31f3cd6ad0e8eabf48c3).
|
||||||
- Heartbeat: make HEARTBEAT_OK ack padding configurable across heartbeat and cron delivery. (#238) — thanks @jalehman
|
- Heartbeat: make HEARTBEAT_OK ack padding configurable across heartbeat and cron delivery. (#238) — thanks @jalehman
|
||||||
- WhatsApp: set sender E.164 for direct chats so owner commands work in DMs.
|
- WhatsApp: set sender E.164 for direct chats so owner commands work in DMs.
|
||||||
|
- Slack: keep auto-replies in the original thread when responding to thread messages. Thanks @scald for PR #251.
|
||||||
|
|
||||||
### Maintenance
|
### Maintenance
|
||||||
- Deps: bump pi-* stack, Slack SDK, discord-api-types, file-type, zod, and Biome.
|
- Deps: bump pi-* stack, Slack SDK, discord-api-types, file-type, zod, and Biome.
|
||||||
|
|||||||
@@ -31,12 +31,11 @@ vi.mock("../config/sessions.js", () => ({
|
|||||||
|
|
||||||
vi.mock("discord.js", () => {
|
vi.mock("discord.js", () => {
|
||||||
const handlers = new Map<string, Set<(...args: unknown[]) => void>>();
|
const handlers = new Map<string, Set<(...args: unknown[]) => void>>();
|
||||||
let lastClient: Client | null = null;
|
|
||||||
|
|
||||||
class Client {
|
class Client {
|
||||||
|
static lastClient: Client | null = null;
|
||||||
user = { id: "bot-id", tag: "bot#1" };
|
user = { id: "bot-id", tag: "bot#1" };
|
||||||
constructor() {
|
constructor() {
|
||||||
lastClient = this;
|
Client.lastClient = this;
|
||||||
}
|
}
|
||||||
on(event: string, handler: (...args: unknown[]) => void) {
|
on(event: string, handler: (...args: unknown[]) => void) {
|
||||||
if (!handlers.has(event)) handlers.set(event, new Set());
|
if (!handlers.has(event)) handlers.set(event, new Set());
|
||||||
@@ -50,7 +49,7 @@ vi.mock("discord.js", () => {
|
|||||||
}
|
}
|
||||||
emit(event: string, ...args: unknown[]) {
|
emit(event: string, ...args: unknown[]) {
|
||||||
for (const handler of handlers.get(event) ?? []) {
|
for (const handler of handlers.get(event) ?? []) {
|
||||||
void handler(...args);
|
void Promise.resolve(handler(...args));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
login = vi.fn().mockResolvedValue(undefined);
|
login = vi.fn().mockResolvedValue(undefined);
|
||||||
@@ -59,7 +58,7 @@ vi.mock("discord.js", () => {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
Client,
|
Client,
|
||||||
__getLastClient: () => lastClient,
|
__getLastClient: () => Client.lastClient,
|
||||||
Events: {
|
Events: {
|
||||||
ClientReady: "ready",
|
ClientReady: "ready",
|
||||||
Error: "error",
|
Error: "error",
|
||||||
|
|||||||
@@ -122,4 +122,38 @@ describe("monitorSlackProvider tool results", () => {
|
|||||||
expect(sendMock.mock.calls[0][1]).toBe("PFX tool update");
|
expect(sendMock.mock.calls[0][1]).toBe("PFX tool update");
|
||||||
expect(sendMock.mock.calls[1][1]).toBe("PFX final reply");
|
expect(sendMock.mock.calls[1][1]).toBe("PFX final reply");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("threads replies when incoming message is in a thread", async () => {
|
||||||
|
replyMock.mockResolvedValue({ text: "thread reply" });
|
||||||
|
|
||||||
|
const controller = new AbortController();
|
||||||
|
const run = monitorSlackProvider({
|
||||||
|
botToken: "bot-token",
|
||||||
|
appToken: "app-token",
|
||||||
|
abortSignal: controller.signal,
|
||||||
|
});
|
||||||
|
|
||||||
|
await waitForEvent("message");
|
||||||
|
const handler = getSlackHandlers()?.get("message");
|
||||||
|
if (!handler) throw new Error("Slack message handler not registered");
|
||||||
|
|
||||||
|
await handler({
|
||||||
|
event: {
|
||||||
|
type: "message",
|
||||||
|
user: "U1",
|
||||||
|
text: "hello",
|
||||||
|
ts: "123",
|
||||||
|
thread_ts: "456",
|
||||||
|
channel: "C1",
|
||||||
|
channel_type: "im",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await flush();
|
||||||
|
controller.abort();
|
||||||
|
await run;
|
||||||
|
|
||||||
|
expect(sendMock).toHaveBeenCalledTimes(1);
|
||||||
|
expect(sendMock.mock.calls[0][2]).toMatchObject({ threadTs: "456" });
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -700,6 +700,9 @@ export async function monitorSlackProvider(opts: MonitorSlackOpts = {}) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Only thread replies if the incoming message was in a thread.
|
||||||
|
const incomingThreadTs = message.thread_ts;
|
||||||
|
|
||||||
const dispatcher = createReplyDispatcher({
|
const dispatcher = createReplyDispatcher({
|
||||||
responsePrefix: cfg.messages?.responsePrefix,
|
responsePrefix: cfg.messages?.responsePrefix,
|
||||||
deliver: async (payload) => {
|
deliver: async (payload) => {
|
||||||
@@ -709,6 +712,7 @@ export async function monitorSlackProvider(opts: MonitorSlackOpts = {}) {
|
|||||||
token: botToken,
|
token: botToken,
|
||||||
runtime,
|
runtime,
|
||||||
textLimit,
|
textLimit,
|
||||||
|
threadTs: incomingThreadTs,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
onError: (err, info) => {
|
onError: (err, info) => {
|
||||||
@@ -1379,6 +1383,7 @@ async function deliverReplies(params: {
|
|||||||
token: string;
|
token: string;
|
||||||
runtime: RuntimeEnv;
|
runtime: RuntimeEnv;
|
||||||
textLimit: number;
|
textLimit: number;
|
||||||
|
threadTs?: string;
|
||||||
}) {
|
}) {
|
||||||
const chunkLimit = Math.min(params.textLimit, 4000);
|
const chunkLimit = Math.min(params.textLimit, 4000);
|
||||||
for (const payload of params.replies) {
|
for (const payload of params.replies) {
|
||||||
@@ -1389,12 +1394,11 @@ async function deliverReplies(params: {
|
|||||||
|
|
||||||
if (mediaList.length === 0) {
|
if (mediaList.length === 0) {
|
||||||
for (const chunk of chunkText(text, chunkLimit)) {
|
for (const chunk of chunkText(text, chunkLimit)) {
|
||||||
const threadTs = undefined;
|
|
||||||
const trimmed = chunk.trim();
|
const trimmed = chunk.trim();
|
||||||
if (!trimmed || trimmed === SILENT_REPLY_TOKEN) continue;
|
if (!trimmed || trimmed === SILENT_REPLY_TOKEN) continue;
|
||||||
await sendMessageSlack(params.target, trimmed, {
|
await sendMessageSlack(params.target, trimmed, {
|
||||||
token: params.token,
|
token: params.token,
|
||||||
threadTs,
|
threadTs: params.threadTs,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -1402,11 +1406,10 @@ async function deliverReplies(params: {
|
|||||||
for (const mediaUrl of mediaList) {
|
for (const mediaUrl of mediaList) {
|
||||||
const caption = first ? text : "";
|
const caption = first ? text : "";
|
||||||
first = false;
|
first = false;
|
||||||
const threadTs = undefined;
|
|
||||||
await sendMessageSlack(params.target, caption, {
|
await sendMessageSlack(params.target, caption, {
|
||||||
token: params.token,
|
token: params.token,
|
||||||
mediaUrl,
|
mediaUrl,
|
||||||
threadTs,
|
threadTs: params.threadTs,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user