import { describe, expect, it, vi } from "vitest"; import type { FollowupRun } from "./queue.js"; import { createMockTypingController } from "./test-helpers.js"; const runEmbeddedPiAgentMock = vi.fn(); vi.mock("../../agents/model-fallback.js", () => ({ runWithModelFallback: async ({ provider, model, run, }: { provider: string; model: string; run: (provider: string, model: string) => Promise; }) => ({ result: await run(provider, model), provider, model, }), })); vi.mock("../../agents/pi-embedded.js", () => ({ runEmbeddedPiAgent: (params: unknown) => runEmbeddedPiAgentMock(params), })); import { createFollowupRunner } from "./followup-runner.js"; const baseQueuedRun = (messageProvider = "whatsapp"): FollowupRun => ({ prompt: "hello", summaryLine: "hello", enqueuedAt: Date.now(), originatingTo: "channel:C1", run: { sessionId: "session", sessionKey: "main", messageProvider, agentAccountId: "primary", sessionFile: "/tmp/session.jsonl", workspaceDir: "/tmp", config: {}, skillsSnapshot: {}, provider: "anthropic", model: "claude", thinkLevel: "low", verboseLevel: "off", elevatedLevel: "off", bashElevated: { enabled: false, allowed: false, defaultLevel: "off", }, timeoutMs: 1_000, blockReplyBreak: "message_end", }, }) as FollowupRun; describe("createFollowupRunner messaging tool dedupe", () => { it("drops payloads already sent via messaging tool", async () => { const onBlockReply = vi.fn(async () => {}); runEmbeddedPiAgentMock.mockResolvedValueOnce({ payloads: [{ text: "hello world!" }], messagingToolSentTexts: ["hello world!"], meta: {}, }); const runner = createFollowupRunner({ opts: { onBlockReply }, typing: createMockTypingController(), typingMode: "instant", defaultModel: "anthropic/claude-opus-4-5", }); await runner(baseQueuedRun()); expect(onBlockReply).not.toHaveBeenCalled(); }); it("delivers payloads when not duplicates", async () => { const onBlockReply = vi.fn(async () => {}); runEmbeddedPiAgentMock.mockResolvedValueOnce({ payloads: [{ text: "hello world!" }], messagingToolSentTexts: ["different message"], meta: {}, }); const runner = createFollowupRunner({ opts: { onBlockReply }, typing: createMockTypingController(), typingMode: "instant", defaultModel: "anthropic/claude-opus-4-5", }); await runner(baseQueuedRun()); expect(onBlockReply).toHaveBeenCalledTimes(1); }); it("suppresses replies when a messaging tool sent via the same provider + target", async () => { const onBlockReply = vi.fn(async () => {}); runEmbeddedPiAgentMock.mockResolvedValueOnce({ payloads: [{ text: "hello world!" }], messagingToolSentTexts: ["different message"], messagingToolSentTargets: [ { tool: "slack", provider: "slack", to: "channel:C1" }, ], meta: {}, }); const runner = createFollowupRunner({ opts: { onBlockReply }, typing: createMockTypingController(), typingMode: "instant", defaultModel: "anthropic/claude-opus-4-5", }); await runner(baseQueuedRun("slack")); expect(onBlockReply).not.toHaveBeenCalled(); }); });