fix(messaging): deliver delegated tool-only replies before mirroring (#60)

deliverDelegatedReplies used allowSilence and marked teamChatMirroredAt
even when no Slack/Teams message was sent, leaving tool-only delegated
runs silently dropped with no retry path.

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 10:57:30 +02:00 committed by GitHub
parent ec597a5c68
commit 492e0f0965
2 changed files with 67 additions and 9 deletions

View File

@ -41,6 +41,65 @@ 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("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<void>;
}
).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 () => { it("keeps deferred messages outside reconciliation until routine routing resolves them", async () => {
const upsert = vi.fn(async ({ create }: { create: Record<string, unknown> }) => ({ const upsert = vi.fn(async ({ create }: { create: Record<string, unknown> }) => ({
id: "external-deferred", id: "external-deferred",

View File

@ -710,15 +710,14 @@ export class TeamChatBridge {
const content = const content =
run.status === "failed" run.status === "failed"
? `${target.name} could not complete the delegated request. Open Rakazo for details.` ? `${target.name} could not complete the delegated request. Open Rakazo for details.`
: teamChatResponseText(blocks, target.name, true); : teamChatResponseText(blocks, target.name);
if (content) { if (!content) continue;
await this.deps.send({ await this.deps.send({
conversationId: origin.externalConversation.conversationId, conversationId: origin.externalConversation.conversationId,
replyThreadId: origin.replyThreadId, replyThreadId: origin.replyThreadId,
content, content,
idempotencyKey: `team-chat-run:${run.id}`, idempotencyKey: `team-chat-run:${run.id}`,
}); });
}
await this.markTeamChatMirrored(run.id); await this.markTeamChatMirrored(run.id);
} }
} }