test(agent-runner): regression — createReplyMediaPathNormalizer.runtime not called when normalizer injected

This commit is contained in:
ayeshakhalid192007-dev
2026-04-17 15:51:55 +05:00
committed by Peter Steinberger
parent 552d5dcbce
commit 8d4e6a39b5

View File

@@ -17,6 +17,7 @@ const enqueueFollowupRunMock = vi.fn();
const scheduleFollowupDrainMock = vi.fn();
const refreshQueuedFollowupSessionMock = vi.fn();
const resolveOutboundAttachmentFromUrlMock = vi.fn();
const createReplyMediaPathNormalizerRuntimeMock = vi.fn();
vi.mock("../../agents/model-fallback.js", () => ({
runWithModelFallback: (params: {
@@ -52,6 +53,18 @@ vi.mock("../../media/outbound-attachment.js", () => ({
resolveOutboundAttachmentFromUrlMock(...args),
}));
// Spy on the .runtime import path used by agent-runner-execution.ts so we can assert
// that the fix prevents a second normalizer from being created inside runAgentTurnWithFallback.
vi.mock("./reply-media-paths.runtime.js", async (importOriginal) => {
const mod = await importOriginal<typeof import("./reply-media-paths.runtime.js")>();
return {
createReplyMediaPathNormalizer: (...args: Parameters<typeof mod.createReplyMediaPathNormalizer>) => {
createReplyMediaPathNormalizerRuntimeMock(...args);
return mod.createReplyMediaPathNormalizer(...args);
},
};
});
let runReplyAgent: typeof import("./agent-runner.js").runReplyAgent;
describe("runReplyAgent media path normalization", () => {
@@ -73,6 +86,7 @@ describe("runReplyAgent media path normalization", () => {
scheduleFollowupDrainMock.mockReset();
refreshQueuedFollowupSessionMock.mockReset();
resolveOutboundAttachmentFromUrlMock.mockReset();
createReplyMediaPathNormalizerRuntimeMock.mockReset();
vi.stubEnv("OPENCLAW_TEST_FAST", "1");
resolveOutboundAttachmentFromUrlMock.mockImplementation(async (mediaUrl: string) => ({
path: path.join("/tmp/outbound-media", path.basename(mediaUrl)),
@@ -160,4 +174,69 @@ describe("runReplyAgent media path normalization", () => {
}),
);
});
it("does not create a second normalizer inside runAgentTurnWithFallback when onBlockReply is provided", async () => {
// Regression test for openclaw/openclaw#68056.
// Before the fix, runAgentTurnWithFallback always called createReplyMediaPathNormalizer
// from reply-media-paths.runtime.js to build its own normalizer instance — separate from
// the one agent-runner.ts created and passed to buildReplyPayloads. Two separate
// persistedMediaBySource caches meant the same source could be persisted twice (two UUID
// outbound files, two WhatsApp sends).
//
// After the fix, agent-runner.ts passes its normalizer into runAgentTurnWithFallback, so
// the .runtime import path is never called from inside that function.
runEmbeddedPiAgentMock.mockResolvedValue({
payloads: [],
meta: {
agentMeta: {
sessionId: "session",
provider: "anthropic",
model: "claude",
},
},
});
await runReplyAgent({
commandBody: "generate chart",
followupRun: createMockFollowupRun({
prompt: "generate chart",
run: {
agentId: "main",
agentDir: "/tmp/agent",
messageProvider: "whatsapp",
workspaceDir: "/tmp/workspace",
},
}) as unknown as FollowupRun,
queueKey: "main",
resolvedQueue: { mode: "interrupt" } as QueueSettings,
shouldSteer: false,
shouldFollowup: false,
isActive: false,
isStreaming: false,
typing: createMockTypingController(),
sessionCtx: {
Provider: "whatsapp",
Surface: "whatsapp",
To: "chat-1",
OriginatingTo: "chat-1",
AccountId: "default",
MessageSid: "msg-1",
} as unknown as TemplateContext,
defaultModel: "anthropic/claude",
resolvedVerboseLevel: "off",
isNewSession: false,
blockStreamingEnabled: false,
resolvedBlockStreamingBreak: "message_end",
shouldInjectGroupIntro: false,
typingMode: "instant",
opts: {
onBlockReply: vi.fn(),
},
});
// The .runtime import is only used by agent-runner-execution.ts. After the fix,
// runAgentTurnWithFallback receives the normalizer from the caller and never
// calls createReplyMediaPathNormalizer itself.
expect(createReplyMediaPathNormalizerRuntimeMock).not.toHaveBeenCalled();
});
});