From 23ff4f04f6a3524ee5208d1cf21d90a307979c3f Mon Sep 17 00:00:00 2001 From: cryppadotta <34892728+cryppadotta@users.noreply.github.com> Date: Fri, 6 Nov 2026 16:49:13 +0000 Subject: [PATCH] fix(slack): route task controls to completed DM threads Co-Authored-By: Paperclip --- .../chat-channels.integration.test.ts | 22 +++++++++++++++---- .../chat-channels/slack/task-start.ts | 3 --- 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/server/src/__tests__/chat-channels.integration.test.ts b/server/src/__tests__/chat-channels.integration.test.ts index fdf64434fef..6910b5568ff 100644 --- a/server/src/__tests__/chat-channels.integration.test.ts +++ b/server/src/__tests__/chat-channels.integration.test.ts @@ -58500,7 +58500,14 @@ describeEmbeddedPostgres("chat channel control-plane integration", () => { ]); }); - it.each(["new", "close"] as const)("binds Slack DM slash controls to the native root returned for the latest task (%s)", async (control) => { + it.each([ + { control: "new", status: "todo", state: "active" }, + { control: "close", status: "todo", state: "active" }, + { control: "new", status: "done", state: "active" }, + { control: "close", status: "cancelled", state: "active" }, + { control: "new", status: "done", state: "completed" }, + { control: "close", status: "cancelled", state: "completed" }, + ] as const)("binds Slack DM slash controls to the latest task ($control, $status, $state)", async ({ control, status, state }) => { const fixture = await seedCompany(); const { callbacks, endpoint, runtime, service } = await configuredSlackEndpoint(fixture); @@ -58557,18 +58564,20 @@ describeEmbeddedPostgres("chat channel control-plane integration", () => { ); const [conversation] = await service.listConversations(endpoint.id); expect(conversation.externalThreadId).toBe(starterThreadId); + await db.update(issues).set({ status }).where(eq(issues.id, conversation.issueId)); + await db.update(chatConversations).set({ state }).where(eq(chatConversations.id, conversation.id)); await invoke("status"); await service.processPendingPublications(); expect(runtime.endpoints.get(endpoint.id)?.posts.at(-1)).toMatchObject({ threadId: starterThreadId, - text: expect.stringMatching(/— todo$/), + text: expect.stringMatching(new RegExp(`— ${status}$`)), }); await invoke(control); - await expect(service.listConversations(endpoint.id)).resolves.toEqual([ + await expect(readChatConversationsByEndpointId(endpoint.id)).resolves.toEqual([ expect.objectContaining({ externalThreadId: starterThreadId, - state: "active", + state, }), ]); await service.processPendingPublications(); @@ -58578,6 +58587,11 @@ describeEmbeddedPostgres("chat channel control-plane integration", () => { state: "completed", }), ]); + const [completion] = await db.select().from(chatPublications).where(and( + eq(chatPublications.conversationId, conversation.id), + like(chatPublications.idempotencyKey, `control:${control}:%`), + )); + expect(completion?.state).toBe("published"); expect(await readIssuesByCompanyId(fixture.companyId)).toHaveLength(1); const providerTime = Date.now(); const providerTimestamp = `${Math.floor(providerTime / 1_000)}.${String((providerTime % 1_000) * 1_000).padStart(6, "0")}`; diff --git a/server/src/services/chat-channels/slack/task-start.ts b/server/src/services/chat-channels/slack/task-start.ts index 309953a69f9..8fd1251d75c 100644 --- a/server/src/services/chat-channels/slack/task-start.ts +++ b/server/src/services/chat-channels/slack/task-start.ts @@ -19,7 +19,6 @@ import { inArray, lte, ne, - notInArray, or, sql, } from "drizzle-orm"; @@ -173,8 +172,6 @@ export function createSlackTaskStart(db: Db, options: SlackTaskStartOptions) { eq(chatConversations.endpointId, endpointId), inArray(chatConversations.externalConversationId, conversationIds), eq(chatConversations.isDirectMessage, true), - inArray(chatConversations.state, ["active", "waiting"]), - notInArray(issues.status, ["done", "cancelled"]), ), ) .orderBy(