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 <cursoragent@cursor.com>
Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>
This commit is contained in:
cynarAI 2026-09-22 12:00:21 +02:00 committed by GitHub
parent ec3a3ceb39
commit c37afbd297
2 changed files with 51 additions and 1 deletions

View File

@ -41,6 +41,52 @@ describe("team chat bridge", () => {
expect(teamChatResponseText([])).toBe("Bot completed the request without a written reply."); 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<void>;
}
).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 () => { it("delivers delegated tool-only replies before marking them mirrored", async () => {
const send = vi.fn(async () => ({ handle: "reply-delegated" })); const send = vi.fn(async () => ({ handle: "reply-delegated" }));
const runUpdateMany = vi.fn(async () => ({ count: 1 })); const runUpdateMany = vi.fn(async () => ({ count: 1 }));

View File

@ -695,7 +695,11 @@ export class TeamChatBridge {
for (const run of runs) { for (const run of runs) {
const origin = await this.findExternalOrigin(run.sourceMessageId); const origin = await this.findExternalOrigin(run.sourceMessageId);
if (!origin) { 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; continue;
} }
const response = const response =