

























@@ -33,23 +33,39 @@ vi.mock("../send.js", async () => {
33333434let deliverDiscordReply: typeof import("./reply-delivery.js").deliverDiscordReply;
353536+type DeliverParams = Record<string, unknown> & {
37+cfg?: OpenClawConfig;
38+formatting?: unknown;
39+deps?: Record<string, (...args: unknown[]) => Promise<unknown>>;
40+};
41+3642function firstDeliverParams() {
37-const calls = sendDurableMessageBatchMock.mock.calls as unknown as Array<
38-[
39-{
40-cfg?: OpenClawConfig;
41-formatting?: unknown;
42-deps?: Record<string, (...args: unknown[]) => Promise<unknown>>;
43-},
44-]
45->;
43+const calls = sendDurableMessageBatchMock.mock.calls as unknown as Array<[DeliverParams]>;
4644const params = calls[0]?.[0];
4745if (!params) {
4846throw new Error("sendDurableMessageBatch was not called");
4947}
5048return params;
5149}
525051+function recordField(value: unknown, field: string): Record<string, unknown> {
52+if (value === undefined || value === null || typeof value !== "object" || Array.isArray(value)) {
53+throw new Error(`expected ${field} to be an object`);
54+}
55+return value as Record<string, unknown>;
56+}
57+58+function objectArgAt(
59+mock: { mock: { calls: unknown[][] } },
60+index: number,
61+): Record<string, unknown> {
62+const value = mock.mock.calls[0]?.[index];
63+if (value === undefined || value === null || typeof value !== "object" || Array.isArray(value)) {
64+throw new Error(`expected call argument ${index} to be an object`);
65+}
66+return value as Record<string, unknown>;
67+}
68+5369describe("deliverDiscordReply", () => {
5470const runtime = {} as RuntimeEnv;
5571const cfg = {
@@ -93,24 +109,22 @@ describe("deliverDiscordReply", () => {
93109replyToMode: "all",
94110});
9511196-expect(sendDurableMessageBatchMock).toHaveBeenCalledWith(
97-expect.objectContaining({
98-channel: "discord",
99-to: "channel:101",
100-accountId: "default",
101-payloads: replies,
102-replyToId: "reply-1",
103-replyToMode: "all",
104-}),
105-);
112+const params = firstDeliverParams();
113+expect(params.channel).toBe("discord");
114+expect(params.to).toBe("channel:101");
115+expect(params.accountId).toBe("default");
116+expect(params.payloads).toEqual(replies);
117+expect(params.replyToId).toBe("reply-1");
118+expect(params.replyToMode).toBe("all");
106119107-const deps = firstDeliverParams().deps!;
120+const deps = params.deps!;
108121await deps.discord("channel:101", "probe", { verbose: false });
109-expect(sendMessageDiscordMock).toHaveBeenCalledWith(
110-"channel:101",
111-"probe",
112-expect.objectContaining({ cfg: firstDeliverParams().cfg, token: "token", rest }),
113-);
122+expect(sendMessageDiscordMock.mock.calls[0]?.[0]).toBe("channel:101");
123+expect(sendMessageDiscordMock.mock.calls[0]?.[1]).toBe("probe");
124+const sendOptions = objectArgAt(sendMessageDiscordMock, 2);
125+expect(sendOptions.cfg).toBe(params.cfg);
126+expect(sendOptions.token).toBe("token");
127+expect(sendOptions.rest).toBe(rest);
114128});
115129116130it("fails when shared outbound accepts a final reply but delivers no Discord message", async () => {
@@ -153,11 +167,7 @@ describe("deliverDiscordReply", () => {
153167textLimit: 2000,
154168});
155169156-expect(sendDurableMessageBatchMock).toHaveBeenCalledWith(
157-expect.objectContaining({
158-payloads: [{ text: "Visible reply." }],
159-}),
160-);
170+expect(firstDeliverParams().payloads).toEqual([{ text: "Visible reply." }]);
161171});
162172163173it("drops pure internal trace text while preserving media-only delivery", async () => {
@@ -176,11 +186,9 @@ describe("deliverDiscordReply", () => {
176186textLimit: 2000,
177187});
178188179-expect(sendDurableMessageBatchMock).toHaveBeenCalledWith(
180-expect.objectContaining({
181-payloads: [{ mediaUrl: "https://example.com/result.png", text: undefined }],
182-}),
183-);
189+expect(firstDeliverParams().payloads).toEqual([
190+{ mediaUrl: "https://example.com/result.png", text: undefined },
191+]);
184192});
185193186194it("preserves component-only channelData payloads when text scrubs empty", async () => {
@@ -217,11 +225,7 @@ describe("deliverDiscordReply", () => {
217225textLimit: 2000,
218226});
219227220-expect(sendDurableMessageBatchMock).toHaveBeenCalledWith(
221-expect.objectContaining({
222-payloads: [{ channelData, text: undefined }],
223-}),
224-);
228+expect(firstDeliverParams().payloads).toEqual([{ channelData, text: undefined }]);
225229});
226230227231it("preserves presentation-only payloads when text scrubs empty", async () => {
@@ -250,11 +254,7 @@ describe("deliverDiscordReply", () => {
250254textLimit: 2000,
251255});
252256253-expect(sendDurableMessageBatchMock).toHaveBeenCalledWith(
254-expect.objectContaining({
255-payloads: [{ presentation, text: undefined }],
256-}),
257-);
257+expect(firstDeliverParams().payloads).toEqual([{ presentation, text: undefined }]);
258258});
259259260260it("does not strip ordinary code-fenced examples of tool-call labels", async () => {
@@ -270,11 +270,7 @@ describe("deliverDiscordReply", () => {
270270textLimit: 2000,
271271});
272272273-expect(sendDurableMessageBatchMock).toHaveBeenCalledWith(
274-expect.objectContaining({
275-payloads: [{ text }],
276-}),
277-);
273+expect(firstDeliverParams().payloads).toEqual([{ text }]);
278274});
279275280276it("does not strip ordinary visible labeled lines", async () => {
@@ -295,11 +291,7 @@ describe("deliverDiscordReply", () => {
295291textLimit: 2000,
296292});
297293298-expect(sendDurableMessageBatchMock).toHaveBeenCalledWith(
299-expect.objectContaining({
300-payloads: [{ text }],
301-}),
302-);
294+expect(firstDeliverParams().payloads).toEqual([{ text }]);
303295});
304296305297it("passes resolved Discord formatting options as explicit delivery options", async () => {
@@ -361,14 +353,11 @@ describe("deliverDiscordReply", () => {
361353mediaLocalRoots: ["/tmp/openclaw-media"],
362354});
363355364-expect(sendDurableMessageBatchMock).toHaveBeenCalledWith(
365-expect.objectContaining({
366-payloads: replies,
367-replyToId: undefined,
368-replyToMode: "off",
369-mediaAccess: { localRoots: ["/tmp/openclaw-media"] },
370-}),
371-);
356+const params = firstDeliverParams();
357+expect(params.payloads).toEqual(replies);
358+expect(params.replyToId).toBeUndefined();
359+expect(params.replyToMode).toBe("off");
360+expect(params.mediaAccess).toEqual({ localRoots: ["/tmp/openclaw-media"] });
372361});
373362374363it("bridges Discord voice sends through the outbound dependency bag", async () => {
@@ -388,11 +377,12 @@ describe("deliverDiscordReply", () => {
388377replyTo: "reply-1",
389378});
390379391-expect(sendVoiceMessageDiscordMock).toHaveBeenCalledWith(
392-"channel:123",
393-"https://example.com/voice.ogg",
394-expect.objectContaining({ cfg, token: "token", replyTo: "reply-1" }),
395-);
380+expect(sendVoiceMessageDiscordMock.mock.calls[0]?.[0]).toBe("channel:123");
381+expect(sendVoiceMessageDiscordMock.mock.calls[0]?.[1]).toBe("https://example.com/voice.ogg");
382+const voiceOptions = objectArgAt(sendVoiceMessageDiscordMock, 2);
383+expect(voiceOptions.cfg).toBe(cfg);
384+expect(voiceOptions.token).toBe("token");
385+expect(voiceOptions.replyTo).toBe("reply-1");
396386});
397387398388it("rewrites bound thread replies to parent target plus thread id and persona", async () => {
@@ -425,17 +415,13 @@ describe("deliverDiscordReply", () => {
425415 threadBindings,
426416});
427417428-expect(sendDurableMessageBatchMock).toHaveBeenCalledWith(
429-expect.objectContaining({
430-to: "channel:parent-1",
431-threadId: "thread-1",
432-replyToId: "reply-1",
433-identity: expect.objectContaining({ name: "🤖 child" }),
434-session: expect.objectContaining({
435-key: "agent:main:subagent:child",
436-agentId: "main",
437-}),
438-}),
439-);
418+const params = firstDeliverParams();
419+expect(params.to).toBe("channel:parent-1");
420+expect(params.threadId).toBe("thread-1");
421+expect(params.replyToId).toBe("reply-1");
422+expect(recordField(params.identity, "identity").name).toBe("🤖 child");
423+const session = recordField(params.session, "session");
424+expect(session.key).toBe("agent:main:subagent:child");
425+expect(session.agentId).toBe("main");
440426});
441427});
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。