
























@@ -2,6 +2,7 @@ import { logVerbose } from "openclaw/plugin-sdk/runtime-env";
22import { sleep } from "openclaw/plugin-sdk/text-runtime";
33import { beforeAll, describe, expect, it, vi } from "vitest";
44import { loadWebMedia } from "../media.js";
5+import { cacheInboundMessageMeta } from "../quoted-message.js";
56import type { WebInboundMsg } from "./types.js";
6778vi.mock("openclaw/plugin-sdk/runtime-env", async () => {
@@ -35,7 +36,12 @@ function makeMsg(): WebInboundMsg {
3536return {
3637from: "+10000000000",
3738to: "+20000000000",
39+accountId: "work",
40+chatId: "15551234567@s.whatsapp.net",
41+chatType: "group",
3842id: "msg-1",
43+body: "latest batch body",
44+senderJid: "222@s.whatsapp.net",
3945reply: vi.fn(async () => undefined),
4046sendMedia: vi.fn(async () => undefined),
4147} as unknown as WebInboundMsg;
@@ -120,6 +126,7 @@ describe("deliverWebReply", () => {
120126expect(msg.reply).toHaveBeenCalledTimes(1);
121127expect(msg.reply).toHaveBeenCalledWith(
122128"Intro line\nReasoning: appears in content but is not a prefix",
129+undefined,
123130);
124131});
125132@@ -136,11 +143,59 @@ describe("deliverWebReply", () => {
136143});
137144138145expect(msg.reply).toHaveBeenCalledTimes(2);
139-expect(msg.reply).toHaveBeenNthCalledWith(1, "aaa");
140-expect(msg.reply).toHaveBeenNthCalledWith(2, "aaa");
146+expect(msg.reply).toHaveBeenNthCalledWith(1, "aaa", undefined);
147+expect(msg.reply).toHaveBeenNthCalledWith(2, "aaa", undefined);
141148expect(replyLogger.info).toHaveBeenCalledWith(expect.any(Object), "auto-reply sent (text)");
142149});
143150151+it("keeps quote threading on every text chunk for a threaded reply", async () => {
152+const msg = makeMsg();
153+cacheInboundMessageMeta("work", "15551234567@s.whatsapp.net", "reply-1", {
154+participant: "111@s.whatsapp.net",
155+body: "quoted body",
156+fromMe: true,
157+});
158+159+await deliverWebReply({
160+replyResult: { text: "aaaaaa", replyToId: "reply-1" },
161+ msg,
162+maxMediaBytes: 1024 * 1024,
163+textLimit: 3,
164+ replyLogger,
165+skipLog: true,
166+});
167+168+expect(msg.reply).toHaveBeenCalledTimes(2);
169+expect(msg.reply).toHaveBeenNthCalledWith(
170+1,
171+"aaa",
172+expect.objectContaining({
173+quoted: expect.objectContaining({
174+key: expect.objectContaining({
175+id: "reply-1",
176+fromMe: true,
177+participant: "111@s.whatsapp.net",
178+}),
179+message: { conversation: "quoted body" },
180+}),
181+}),
182+);
183+expect(msg.reply).toHaveBeenNthCalledWith(
184+2,
185+"aaa",
186+expect.objectContaining({
187+quoted: expect.objectContaining({
188+key: expect.objectContaining({
189+id: "reply-1",
190+fromMe: true,
191+participant: "111@s.whatsapp.net",
192+}),
193+message: { conversation: "quoted body" },
194+}),
195+}),
196+);
197+});
198+144199it.each(["connection closed", "operation timed out"])(
145200"retries text send on transient failure: %s",
146201async (errorMessage) => {
@@ -188,12 +243,67 @@ describe("deliverWebReply", () => {
188243caption: "aaa",
189244mimetype: "image/jpeg",
190245}),
246+undefined,
191247);
192-expect(msg.reply).toHaveBeenCalledWith("aaa");
248+expect(msg.reply).toHaveBeenCalledWith("aaa", undefined);
193249expect(replyLogger.info).toHaveBeenCalledWith(expect.any(Object), "auto-reply sent (media)");
194250expect(logVerbose).toHaveBeenCalled();
195251});
196252253+it("keeps quote threading on media and trailing text chunks for a threaded reply", async () => {
254+const msg = makeMsg();
255+mockLoadedImageMedia();
256+cacheInboundMessageMeta("work", "15551234567@s.whatsapp.net", "reply-2", {
257+participant: "111@s.whatsapp.net",
258+body: "quoted media body",
259+fromMe: true,
260+});
261+262+await deliverWebReply({
263+replyResult: {
264+text: "captiontrail",
265+mediaUrl: "http://example.com/img.jpg",
266+replyToId: "reply-2",
267+},
268+ msg,
269+maxMediaBytes: 1024 * 1024,
270+textLimit: 7,
271+ replyLogger,
272+skipLog: true,
273+});
274+275+expect(msg.sendMedia).toHaveBeenCalledWith(
276+expect.objectContaining({
277+image: expect.any(Buffer),
278+caption: "caption",
279+mimetype: "image/jpeg",
280+}),
281+expect.objectContaining({
282+quoted: expect.objectContaining({
283+key: expect.objectContaining({
284+id: "reply-2",
285+fromMe: true,
286+participant: "111@s.whatsapp.net",
287+}),
288+message: { conversation: "quoted media body" },
289+}),
290+}),
291+);
292+expect(msg.reply).toHaveBeenCalledWith(
293+"trail",
294+expect.objectContaining({
295+quoted: expect.objectContaining({
296+key: expect.objectContaining({
297+id: "reply-2",
298+fromMe: true,
299+participant: "111@s.whatsapp.net",
300+}),
301+message: { conversation: "quoted media body" },
302+}),
303+}),
304+);
305+});
306+197307it("retries media send on transient failure", async () => {
198308const msg = makeMsg();
199309mockLoadedImageMedia();
@@ -265,6 +375,7 @@ describe("deliverWebReply", () => {
265375mimetype: "audio/ogg",
266376caption: "cap",
267377}),
378+undefined,
268379);
269380});
270381@@ -293,6 +404,7 @@ describe("deliverWebReply", () => {
293404caption: "cap",
294405mimetype: "video/mp4",
295406}),
407+undefined,
296408);
297409});
298410@@ -323,6 +435,7 @@ describe("deliverWebReply", () => {
323435caption: "cap",
324436mimetype: "application/octet-stream",
325437}),
438+undefined,
326439);
327440});
328441});
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。