






















@@ -27,6 +27,38 @@ function firstMockArg(
2727return arg as Record<string, unknown>;
2828}
292930+const slackConfig = {
31+channels: {
32+slack: {
33+enabled: true,
34+},
35+},
36+} as OpenClawConfig;
37+38+function registerSlackTextPlugin() {
39+const sendText = vi.fn().mockResolvedValue({
40+channel: "slack",
41+messageId: "m1",
42+chatId: "C123",
43+});
44+setActivePluginRegistry(
45+createTestRegistry([
46+{
47+pluginId: "slack",
48+source: "test",
49+plugin: createOutboundTestPlugin({
50+id: "slack",
51+outbound: {
52+deliveryMode: "direct",
53+ sendText,
54+},
55+}),
56+},
57+]),
58+);
59+return sendText;
60+}
61+3062describe("runMessageAction core send routing", () => {
3163afterEach(() => {
3264setActivePluginRegistry(createTestRegistry([]));
@@ -196,6 +228,136 @@ describe("runMessageAction core send routing", () => {
196228expect(payload.dryRun).toBe(true);
197229});
198230231+it("uses best-effort delivery for implicit message-tool-only source replies", async () => {
232+const sendText = registerSlackTextPlugin();
233+234+const result = await runMessageAction({
235+cfg: slackConfig,
236+action: "send",
237+params: {
238+message: "visible source reply",
239+bestEffort: false,
240+},
241+toolContext: {
242+currentChannelProvider: "slack",
243+currentChannelId: "channel:C123",
244+},
245+sessionKey: "agent:main:slack:channel:C123",
246+sourceReplyDeliveryMode: "message_tool_only",
247+dryRun: false,
248+});
249+250+expect(result.kind).toBe("send");
251+expect(sendText).toHaveBeenCalledOnce();
252+});
253+254+it("uses best-effort delivery for explicit current-source message-tool-only replies", async () => {
255+const sendText = registerSlackTextPlugin();
256+257+const result = await runMessageAction({
258+cfg: slackConfig,
259+action: "send",
260+params: {
261+target: "channel:C123",
262+message: "visible current-channel source reply",
263+bestEffort: false,
264+},
265+toolContext: {
266+currentChannelProvider: "slack",
267+currentChannelId: "channel:C123",
268+},
269+sessionKey: "agent:main:slack:channel:C123",
270+sourceReplyDeliveryMode: "message_tool_only",
271+dryRun: false,
272+});
273+274+if (result.kind !== "send") {
275+throw new Error(`expected send result, got ${result.kind}`);
276+}
277+expect(sendText).toHaveBeenCalledOnce();
278+expect(result.to).toBe("channel:C123");
279+});
280+281+it("preserves required delivery when message-tool-only sends target another conversation", async () => {
282+const sendText = registerSlackTextPlugin();
283+284+await expect(
285+runMessageAction({
286+cfg: slackConfig,
287+action: "send",
288+params: {
289+target: "channel:C999",
290+message: "explicit durable send",
291+bestEffort: false,
292+},
293+toolContext: {
294+currentChannelProvider: "slack",
295+currentChannelId: "channel:C123",
296+},
297+sessionKey: "agent:main:slack:channel:C123",
298+sourceReplyDeliveryMode: "message_tool_only",
299+dryRun: false,
300+}),
301+).rejects.toThrow("missing reconcileUnknownSend");
302+expect(sendText).not.toHaveBeenCalled();
303+});
304+305+it("preserves required delivery when message-tool-only sends to another explicit channel", async () => {
306+const sendText = vi.fn().mockResolvedValue({
307+channel: "telegram",
308+messageId: "m1",
309+chatId: "C999",
310+});
311+setActivePluginRegistry(
312+createTestRegistry([
313+{
314+pluginId: "telegram",
315+source: "test",
316+plugin: createOutboundTestPlugin({
317+id: "telegram",
318+outbound: {
319+deliveryMode: "direct",
320+ sendText,
321+},
322+}),
323+},
324+]),
325+);
326+327+await expect(
328+runMessageAction({
329+cfg: {
330+channels: {
331+telegram: {
332+enabled: true,
333+},
334+},
335+tools: {
336+message: {
337+crossContext: {
338+allowAcrossProviders: true,
339+},
340+},
341+},
342+} as OpenClawConfig,
343+action: "send",
344+params: {
345+channel: "telegram",
346+message: "explicit channel-only durable send",
347+bestEffort: false,
348+},
349+toolContext: {
350+currentChannelProvider: "slack",
351+currentChannelId: "channel:C123",
352+},
353+sessionKey: "agent:main:slack:channel:C123",
354+sourceReplyDeliveryMode: "message_tool_only",
355+dryRun: false,
356+}),
357+).rejects.toThrow("missing reconcileUnknownSend");
358+expect(sendText).not.toHaveBeenCalled();
359+});
360+199361it("applies TTS to message-tool sends before core outbound delivery", async () => {
200362const sendMedia = vi.fn().mockResolvedValue({
201363channel: "testchat",
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。