From c37afbd2975be19feb5ee83907306f2055105325 Mon Sep 17 00:00:00 2001 From: cynarAI <155274210+cynarAI@users.noreply.github.com> Date: Tue, 22 Sep 2026 12:00:21 +0200 Subject: [PATCH] fix(teamchat): retry delegated replies when external origin is missing (#69) When deliverDelegatedReplies could not resolve the Slack/Teams origin thread, it still set teamChatMirroredAt and permanently dropped the delegated reply. Leave the mirror timestamp unset when sourceMessageId exists so reconciliation retries once the wake chain can be resolved again. Co-authored-by: Cursor Agent Co-authored-by: cynarAI --- apps/api/src/team-chat-bridge.test.ts | 46 +++++++++++++++++++++++++++ apps/api/src/team-chat-bridge.ts | 6 +++- 2 files changed, 51 insertions(+), 1 deletion(-) diff --git a/apps/api/src/team-chat-bridge.test.ts b/apps/api/src/team-chat-bridge.test.ts index 3c56b3f..809f628 100644 --- a/apps/api/src/team-chat-bridge.test.ts +++ b/apps/api/src/team-chat-bridge.test.ts @@ -41,6 +41,52 @@ describe("team chat bridge", () => { expect(teamChatResponseText([])).toBe("Bot completed the request without a written reply."); }); + it("retries delegated replies when the external origin is temporarily missing", 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 () => null), + findFirst: vi.fn(async () => ({ + blocks: [{ kind: "text", text: "Delegated work finished." }], + })), + }, + externalMessage: { findUnique: vi.fn(async () => null) }, + } 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).not.toHaveBeenCalled(); + expect(runUpdateMany).not.toHaveBeenCalled(); + }); + 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 })); diff --git a/apps/api/src/team-chat-bridge.ts b/apps/api/src/team-chat-bridge.ts index f7c6221..0c6e00f 100644 --- a/apps/api/src/team-chat-bridge.ts +++ b/apps/api/src/team-chat-bridge.ts @@ -695,7 +695,11 @@ export class TeamChatBridge { for (const run of runs) { const origin = await this.findExternalOrigin(run.sourceMessageId); if (!origin) { - await this.markTeamChatMirrored(run.id); + // Keep teamChatMirroredAt unset when the wake chain is missing so reconciliation + // retries once the external origin can be resolved again. + if (!run.sourceMessageId) { + await this.markTeamChatMirrored(run.id); + } continue; } const response =