@@ -141,6 +141,38 @@ const ttsMocks = vi.hoisted(() => {
141141 resolveTtsConfig : vi . fn ( ( _cfg : OpenClawConfig ) => ( { mode : "final" } ) ) ,
142142 } ;
143143} ) ;
144+ const threadInfoMocks = vi . hoisted ( ( ) => ( {
145+ parseSessionThreadInfo : vi . fn <
146+ ( sessionKey : string | undefined ) => {
147+ baseSessionKey : string | undefined ;
148+ threadId : string | undefined ;
149+ }
150+ > ( ) ,
151+ } ) ) ;
152+
153+ function parseGenericThreadSessionInfo ( sessionKey : string | undefined ) {
154+ const trimmed = sessionKey ?. trim ( ) ;
155+ if ( ! trimmed ) {
156+ return { baseSessionKey : undefined , threadId : undefined } ;
157+ }
158+ const threadMarker = ":thread:" ;
159+ const topicMarker = ":topic:" ;
160+ const marker = trimmed . includes ( threadMarker )
161+ ? threadMarker
162+ : trimmed . includes ( topicMarker )
163+ ? topicMarker
164+ : undefined ;
165+ if ( ! marker ) {
166+ return { baseSessionKey : trimmed , threadId : undefined } ;
167+ }
168+ const index = trimmed . lastIndexOf ( marker ) ;
169+ if ( index < 0 ) {
170+ return { baseSessionKey : trimmed , threadId : undefined } ;
171+ }
172+ const baseSessionKey = trimmed . slice ( 0 , index ) . trim ( ) || undefined ;
173+ const threadId = trimmed . slice ( index + marker . length ) . trim ( ) || undefined ;
174+ return { baseSessionKey, threadId } ;
175+ }
144176
145177vi . mock ( "./route-reply.runtime.js" , ( ) => ( {
146178 isRoutableChannel : ( channel : string | undefined ) =>
@@ -194,6 +226,10 @@ vi.mock("../../logging/diagnostic.js", () => ({
194226 logMessageProcessed : diagnosticMocks . logMessageProcessed ,
195227 logSessionStateChange : diagnosticMocks . logSessionStateChange ,
196228} ) ) ;
229+ vi . mock ( "../../config/sessions/thread-info.js" , ( ) => ( {
230+ parseSessionThreadInfo : ( sessionKey : string | undefined ) =>
231+ threadInfoMocks . parseSessionThreadInfo ( sessionKey ) ,
232+ } ) ) ;
197233vi . mock ( "./dispatch-from-config.runtime.js" , ( ) => ( {
198234 createInternalHookEvent : internalHookMocks . createInternalHookEvent ,
199235 loadSessionStore : sessionStoreMocks . loadSessionStore ,
@@ -593,6 +629,8 @@ describe("dispatchReplyFromConfig", () => {
593629 sessionStoreMocks . loadSessionStore . mockClear ( ) ;
594630 sessionStoreMocks . resolveStorePath . mockClear ( ) ;
595631 sessionStoreMocks . resolveSessionStoreEntry . mockClear ( ) ;
632+ threadInfoMocks . parseSessionThreadInfo . mockReset ( ) ;
633+ threadInfoMocks . parseSessionThreadInfo . mockImplementation ( parseGenericThreadSessionInfo ) ;
596634 ttsMocks . state . synthesizeFinalAudio = false ;
597635 ttsMocks . maybeApplyTtsToPayload . mockClear ( ) ;
598636 ttsMocks . normalizeTtsAutoMode . mockClear ( ) ;
@@ -663,7 +701,7 @@ describe("dispatchReplyFromConfig", () => {
663701 mocks . routeReply . mockClear ( ) ;
664702 sessionStoreMocks . currentEntry = {
665703 deliveryContext : {
666- channel : "mattermost " ,
704+ channel : "discord " ,
667705 to : "channel:CHAN1" ,
668706 accountId : "default" ,
669707 } ,
@@ -677,10 +715,10 @@ describe("dispatchReplyFromConfig", () => {
677715 const ctx = buildTestCtx ( {
678716 Provider : "webchat" ,
679717 Surface : "webchat" ,
680- SessionKey : "agent:main:mattermost :channel:CHAN1:thread:post-root" ,
718+ SessionKey : "agent:main:discord :channel:CHAN1:thread:post-root" ,
681719 AccountId : "default" ,
682720 MessageThreadId : undefined ,
683- OriginatingChannel : "mattermost " ,
721+ OriginatingChannel : "discord " ,
684722 OriginatingTo : "channel:CHAN1" ,
685723 ExplicitDeliverRoute : true ,
686724 } ) ;
@@ -690,7 +728,7 @@ describe("dispatchReplyFromConfig", () => {
690728
691729 expect ( mocks . routeReply ) . toHaveBeenCalledWith (
692730 expect . objectContaining ( {
693- channel : "mattermost " ,
731+ channel : "discord " ,
694732 to : "channel:CHAN1" ,
695733 threadId : "post-root" ,
696734 } ) ,
0 commit comments