diff --git a/apps/api/src/team-chat-bridge.test.ts b/apps/api/src/team-chat-bridge.test.ts index 71dc79b..3c56b3f 100644 --- a/apps/api/src/team-chat-bridge.test.ts +++ b/apps/api/src/team-chat-bridge.test.ts @@ -41,6 +41,65 @@ describe("team chat bridge", () => { expect(teamChatResponseText([])).toBe("Bot completed the request without a written reply."); }); + it("delivers delegated tool-only replies before marking them mirrored", async () => { + const send = vi.fn(async () => ({ handle: "reply-delegated" })); + const runUpdateMany = vi.fn(async () => ({ count: 1 })); + const bridge = new TeamChatBridge({ + prisma: { + run: { + findMany: vi.fn(async () => [ + { + id: "run-delegated", + status: "completed", + sourceMessageId: "msg-source", + }, + ]), + findUnique: vi.fn(async () => null), + updateMany: runUpdateMany, + }, + message: { + findUnique: vi.fn(async () => ({ replyTo: { runId: "parent-run-1" } })), + findFirst: vi.fn(async () => ({ + blocks: [{ kind: "progress", text: "Searching files" }], + })), + }, + externalMessage: { + findUnique: vi.fn(async () => ({ + replyThreadId: "100.1", + externalConversation: { conversationId: "C-1" }, + })), + }, + } as unknown as PrismaClient, + events: { sendUserMessage: vi.fn() }, + jobs: { enqueue: vi.fn() }, + send, + providerId: "slack", + botId: "bot-1", + }); + ( + bridge as unknown as { + target: { id: string; spaceId: string; userId: string; name: string }; + } + ).target = { id: "bot-1", spaceId: "space-1", userId: "owner-1", name: "Analyst" }; + + await ( + bridge as unknown as { + deliverDelegatedReplies: (target: { id: string; name: string }) => Promise; + } + ).deliverDelegatedReplies({ id: "bot-1", name: "Analyst" }); + + expect(send).toHaveBeenCalledWith({ + conversationId: "C-1", + replyThreadId: "100.1", + content: "Analyst completed the request without a written reply.", + idempotencyKey: "team-chat-run:run-delegated", + }); + expect(runUpdateMany).toHaveBeenCalledWith({ + where: { id: "run-delegated", teamChatMirroredAt: null }, + data: { teamChatMirroredAt: expect.any(Date) }, + }); + }); + it("keeps deferred messages outside reconciliation until routine routing resolves them", async () => { const upsert = vi.fn(async ({ create }: { create: Record }) => ({ id: "external-deferred", diff --git a/apps/api/src/team-chat-bridge.ts b/apps/api/src/team-chat-bridge.ts index cf6be58..f7c6221 100644 --- a/apps/api/src/team-chat-bridge.ts +++ b/apps/api/src/team-chat-bridge.ts @@ -710,15 +710,14 @@ export class TeamChatBridge { const content = run.status === "failed" ? `${target.name} could not complete the delegated request. Open Rakazo for details.` - : teamChatResponseText(blocks, target.name, true); - if (content) { - await this.deps.send({ - conversationId: origin.externalConversation.conversationId, - replyThreadId: origin.replyThreadId, - content, - idempotencyKey: `team-chat-run:${run.id}`, - }); - } + : teamChatResponseText(blocks, target.name); + if (!content) continue; + await this.deps.send({ + conversationId: origin.externalConversation.conversationId, + replyThreadId: origin.replyThreadId, + content, + idempotencyKey: `team-chat-run:${run.id}`, + }); await this.markTeamChatMirrored(run.id); } }