


























@@ -4,12 +4,14 @@ import type { ChannelMessageCapability } from "../../channels/plugins/message-ca
44import type { ChannelMessageActionName, ChannelPlugin } from "../../channels/plugins/types.js";
55import type { MessageActionRunResult } from "../../infra/outbound/message-action-runner.js";
66type CreateMessageTool = typeof import("./message-tool.js").createMessageTool;
7+type CreateOpenClawTools = typeof import("../openclaw-tools.js").createOpenClawTools;
78type ResetPluginRuntimeStateForTest =
89typeof import("../../plugins/runtime.js").resetPluginRuntimeStateForTest;
910type SetActivePluginRegistry = typeof import("../../plugins/runtime.js").setActivePluginRegistry;
1011type CreateTestRegistry = typeof import("../../test-utils/channel-plugins.js").createTestRegistry;
11121213let createMessageTool: CreateMessageTool;
14+let createOpenClawTools: CreateOpenClawTools;
1315let resetPluginRuntimeStateForTest: ResetPluginRuntimeStateForTest;
1416let setActivePluginRegistry: SetActivePluginRegistry;
1517let createTestRegistry: CreateTestRegistry;
@@ -154,6 +156,7 @@ beforeAll(async () => {
154156await import("../../plugins/runtime.js"));
155157({ createTestRegistry } = await import("../../test-utils/channel-plugins.js"));
156158({ createMessageTool } = await import("./message-tool.js"));
159+({ createOpenClawTools } = await import("../openclaw-tools.js"));
157160});
158161159162beforeEach(() => {
@@ -358,6 +361,79 @@ describe("message tool agent routing", () => {
358361expect(call?.agentId).toBe("alpha");
359362expect(call?.sessionKey).toBe("agent:alpha:main");
360363});
364+365+it("uses agentThreadId as ambient thread context when currentThreadTs is absent", async () => {
366+mockSendResult({ channel: "slack", to: "channel:C123" });
367+368+const tool = createMessageTool({
369+agentSessionKey: "agent:main:slack:channel:c123:thread:111.222",
370+config: {} as never,
371+currentChannelProvider: "slack",
372+currentChannelId: "channel:C123",
373+agentThreadId: "111.222",
374+runMessageAction: mocks.runMessageAction as never,
375+});
376+377+await tool.execute("1", {
378+action: "send",
379+channel: "slack",
380+message: "stay in thread",
381+});
382+383+const call = mocks.runMessageAction.mock.calls[0]?.[0];
384+expect(call?.toolContext?.currentThreadTs).toBe("111.222");
385+expect(call?.toolContext?.replyToMode).toBe("all");
386+});
387+388+it("keeps explicit reply mode opt-out when agentThreadId is present", async () => {
389+mockSendResult({ channel: "slack", to: "channel:C123" });
390+391+const tool = createMessageTool({
392+agentSessionKey: "agent:main:slack:channel:c123:thread:111.222",
393+config: {} as never,
394+currentChannelProvider: "slack",
395+currentChannelId: "channel:C123",
396+agentThreadId: "111.222",
397+replyToMode: "off",
398+runMessageAction: mocks.runMessageAction as never,
399+});
400+401+await tool.execute("1", {
402+action: "send",
403+channel: "slack",
404+message: "send at channel level",
405+});
406+407+const call = mocks.runMessageAction.mock.calls[0]?.[0];
408+expect(call?.toolContext?.currentThreadTs).toBe("111.222");
409+expect(call?.toolContext?.replyToMode).toBe("off");
410+});
411+412+it("forwards agentThreadId through createOpenClawTools to the message tool", async () => {
413+mockSendResult({ channel: "slack", to: "channel:C123" });
414+415+const tool = createOpenClawTools({
416+agentSessionKey: "agent:main:slack:channel:c123:thread:111.222",
417+config: {} as never,
418+agentChannel: "slack",
419+currentChannelId: "channel:C123",
420+agentThreadId: "111.222",
421+}).find((candidate) => candidate.name === "message");
422+423+if (!tool) {
424+throw new Error("message tool not found");
425+}
426+427+await tool.execute("1", {
428+action: "send",
429+channel: "slack",
430+message: "stay in thread",
431+});
432+433+const call = mocks.runMessageAction.mock.calls[0]?.[0];
434+expect(call?.toolContext?.currentThreadTs).toBe("111.222");
435+expect(call?.toolContext?.replyToMode).toBe("all");
436+});
361437});
362438363439describe("message tool explicit target guard", () => {
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。