@@ -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 =
89 typeof import ( "../../plugins/runtime.js" ) . resetPluginRuntimeStateForTest ;
910type SetActivePluginRegistry = typeof import ( "../../plugins/runtime.js" ) . setActivePluginRegistry ;
1011type CreateTestRegistry = typeof import ( "../../test-utils/channel-plugins.js" ) . createTestRegistry ;
1112
1213let createMessageTool : CreateMessageTool ;
14+ let createOpenClawTools : CreateOpenClawTools ;
1315let resetPluginRuntimeStateForTest : ResetPluginRuntimeStateForTest ;
1416let setActivePluginRegistry : SetActivePluginRegistry ;
1517let createTestRegistry : CreateTestRegistry ;
@@ -154,6 +156,7 @@ beforeAll(async () => {
154156 await 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} ) ;
158161
159162beforeEach ( ( ) => {
@@ -358,6 +361,79 @@ describe("message tool agent routing", () => {
358361 expect ( call ?. agentId ) . toBe ( "alpha" ) ;
359362 expect ( 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} ) ;
362438
363439describe ( "message tool explicit target guard" , ( ) => {
0 commit comments