@@ -6,15 +6,23 @@ import {
66 getGitHubInfoForUser ,
77 getLarkAppCredentials ,
88 getLarkTargetChannels ,
9- getWebHost ,
10- getWebPort ,
119 getWorkspaces ,
1210} from "@/config" ;
1311import { isThreadActive , markThreadActive } from "@/config/local/settings" ;
1412import { createCoreRuntime } from "@/core/runtime" ;
1513import type { IMAdapter } from "@/core/types" ;
1614import { log } from "@/utils" ;
1715import { isStopCommand } from "@/ims/shared/stop-command" ;
16+ import {
17+ toCoreMessageContext ,
18+ type UnifiedMessageContext ,
19+ } from "@/ims/shared/message-context" ;
20+ import { evaluateIncomingMessage } from "@/ims/shared/incoming-pipeline" ;
21+ import { executeIncomingFlow } from "@/ims/shared/incoming-executor" ;
22+ import { buildIncomingContext } from "@/ims/shared/incoming-normalizer" ;
23+ import { parseIncomingCommand } from "@/ims/shared/command-router" ;
24+ import { createRuntimeController } from "@/ims/shared/runtime-controller" ;
25+ import { sendLarkSettingsCard } from "./settings" ;
1826
1927let larkRuntimeStarted = false ;
2028
@@ -214,15 +222,6 @@ function parseLarkText(content: string | undefined): string {
214222 }
215223}
216224
217- function isSettingsCommand ( text : string ) : boolean {
218- const normalized = text . trim ( ) . replace ( / ^ / / , "/" ) ;
219- return / ^ \/ ? s e t t i n g s ? (?: \s | $ ) / i. test ( normalized ) ;
220- }
221-
222- function getLocalSettingsUrl ( ) : string {
223- return `http://${ getWebHost ( ) } :${ getWebPort ( ) } /` ;
224- }
225-
226225async function buildLarkContext (
227226 channelId : string ,
228227 threadId : string ,
@@ -259,71 +258,19 @@ async function sendMessage(
259258}
260259
261260async function sendSettingsCard ( channelId : string , threadId : string ) : Promise < string | undefined > {
262- const settingsUrl = getLocalSettingsUrl ( ) ;
263- logLarkEvent ( "Lark settings UI launcher triggered" , {
261+ return sendLarkSettingsCard ( {
264262 channelId,
265263 threadId,
266- settingsUrl,
264+ sendInteractive : ( card ) =>
265+ sendLarkMessage ( {
266+ channelId,
267+ threadId,
268+ msgType : "interactive" ,
269+ content : card ,
270+ } ) ,
271+ sendText : ( text ) => sendMessage ( channelId , threadId , text , true ) ,
272+ logEvent : logLarkEvent ,
267273 } ) ;
268- const card = {
269- config : {
270- wide_screen_mode : true ,
271- } ,
272- header : {
273- template : "blue" ,
274- title : {
275- tag : "plain_text" ,
276- content : "Ode Settings" ,
277- } ,
278- } ,
279- elements : [
280- {
281- tag : "markdown" ,
282- content : `Configure this chat in the local settings UI.\\n\\nChannel: \`${ channelId } \`` ,
283- } ,
284- {
285- tag : "action" ,
286- actions : [
287- {
288- tag : "button" ,
289- text : {
290- tag : "plain_text" ,
291- content : "Open Local Setting" ,
292- } ,
293- type : "primary" ,
294- url : settingsUrl ,
295- } ,
296- ] ,
297- } ,
298- ] ,
299- } ;
300-
301- try {
302- const messageId = await sendLarkMessage ( {
303- channelId,
304- threadId,
305- msgType : "interactive" ,
306- content : card as unknown as Record < string , unknown > ,
307- } ) ;
308- logLarkEvent ( "Lark settings card sent" , {
309- channelId,
310- threadId,
311- messageId : messageId ?? "" ,
312- } ) ;
313- return messageId ;
314- } catch {
315- logLarkEvent ( "Lark settings card failed, sending fallback text" , {
316- channelId,
317- threadId,
318- } ) ;
319- const fallbackText = [
320- "Ode settings" ,
321- `Open: ${ settingsUrl } ` ,
322- `Channel: ${ channelId } ` ,
323- "Use this channel in Local Setting to configure provider/model/directory." ,
324- ] . join ( "\n" ) ;
325- return sendMessage ( channelId , threadId , fallbackText , true ) ;
326- }
327274}
328275
329276async function updateMessage (
@@ -662,6 +609,18 @@ async function processLarkIncomingEvent(event: LarkIncomingEvent): Promise<void>
662609 : false ;
663610 const active = isThreadActive ( channelId , threadId ) ;
664611 const text = stripLarkMentionMarkup ( rawText ) ;
612+ const messageContext : UnifiedMessageContext = buildIncomingContext ( {
613+ platform : "lark" ,
614+ channelId,
615+ threadId,
616+ messageId,
617+ userId : senderOpenId ,
618+ isTopLevel : topLevelMessage ,
619+ mentionedBot : isMentioned ,
620+ activeThread : active ,
621+ rawText,
622+ normalizedText : text ,
623+ } ) ;
665624
666625 logLarkEvent ( "Lark inbound parsed" , {
667626 channelId,
@@ -675,7 +634,8 @@ async function processLarkIncomingEvent(event: LarkIncomingEvent): Promise<void>
675634 textLength : text . length ,
676635 } ) ;
677636
678- if ( isSettingsCommand ( text ) ) {
637+ const command = parseIncomingCommand ( text ) ;
638+ if ( command === "setting" ) {
679639 logLarkEvent ( "Lark inbound matched /setting" , {
680640 channelId,
681641 threadId,
@@ -687,66 +647,50 @@ async function processLarkIncomingEvent(event: LarkIncomingEvent): Promise<void>
687647 return ;
688648 }
689649
690- if ( ! topLevelMessage ) {
691- if ( ! isMentioned && ! active ) {
692- logLarkEvent ( "Lark inbound ignored: thread reply without mention and inactive thread" , {
650+ const flowResult = evaluateIncomingMessage ( messageContext , isStopCommand ) ;
651+ await executeIncomingFlow ( {
652+ context : messageContext ,
653+ flowResult,
654+ markThreadActive,
655+ handleStopCommand : ( flowChannelId , flowThreadId ) => coreRuntime . handleStopCommand ( flowChannelId , flowThreadId ) ,
656+ sendStopAck : async ( ) => {
657+ await sendMessage ( channelId , threadId , "Request stopped." , true ) ;
658+ } ,
659+ onIgnore : ( reason ) => {
660+ if ( reason === "not_mentioned_and_inactive" ) {
661+ logLarkEvent ( "Lark inbound ignored: not mentioned and thread inactive" , {
662+ channelId,
663+ threadId,
664+ messageId,
665+ reason,
666+ isTopLevel : topLevelMessage ,
667+ isMentioned,
668+ activeThread : active ,
669+ } ) ;
670+ return ;
671+ }
672+ logLarkEvent ( "Lark inbound ignored: empty text after mention stripping" , {
673+ channelId,
674+ messageId,
675+ } ) ;
676+ } ,
677+ forwardToCore : async ( forwardText ) => {
678+ logLarkEvent ( "Lark inbound accepted: forwarding to core runtime" , {
679+ channelId,
680+ threadId,
681+ messageId,
682+ userId : senderOpenId ,
683+ } ) ;
684+ await coreRuntime . handleIncomingMessage (
685+ toCoreMessageContext ( messageContext ) ,
686+ forwardText
687+ ) ;
688+ logLarkEvent ( "Lark inbound handled by core runtime" , {
693689 channelId,
694690 threadId,
695691 messageId,
696692 } ) ;
697- return ;
698- }
699- } else if ( ! isMentioned ) {
700- logLarkEvent ( "Lark inbound ignored: top-level message without mention" , {
701- channelId,
702- threadId,
703- messageId,
704- } ) ;
705- return ;
706- }
707-
708- if ( ! text ) {
709- logLarkEvent ( "Lark inbound ignored: empty text after mention stripping" , {
710- channelId,
711- messageId,
712- } ) ;
713- return ;
714- }
715-
716- if ( isStopCommand ( text ) ) {
717- logLarkEvent ( "Lark inbound matched stop command" , {
718- channelId,
719- threadId,
720- messageId,
721- } ) ;
722- const stopped = await coreRuntime . handleStopCommand ( channelId , threadId ) ;
723- if ( stopped ) {
724- await sendMessage ( channelId , threadId , "Request stopped." , true ) ;
725- }
726- return ;
727- }
728-
729- markThreadActive ( channelId , threadId ) ;
730- logLarkEvent ( "Lark inbound accepted: forwarding to core runtime" , {
731- channelId,
732- threadId,
733- messageId,
734- userId : senderOpenId ,
735- } ) ;
736- await coreRuntime . handleIncomingMessage (
737- {
738- channelId,
739- replyThreadId : threadId ,
740- threadId,
741- userId : senderOpenId ,
742- messageId,
743693 } ,
744- text
745- ) ;
746- logLarkEvent ( "Lark inbound handled by core runtime" , {
747- channelId,
748- threadId,
749- messageId,
750694 } ) ;
751695}
752696
@@ -849,32 +793,43 @@ export async function handleLarkEventPayload(payload: unknown): Promise<{ status
849793}
850794
851795export async function startLarkRuntime ( reason : string ) : Promise < boolean > {
852- if ( larkRuntimeStarted ) return true ;
853- const workspaces = getLarkAppCredentials ( ) ;
854- if ( workspaces . length === 0 ) {
855- log . debug ( "Lark runtime skipped (Lark app credentials missing)" , { reason } ) ;
856- return false ;
796+ if ( larkRuntimeStarted ) {
797+ log . debug ( "Lark runtime start skipped; already running" , { reason } ) ;
857798 }
858- larkRuntimeStarted = true ;
859- tenantTokenCache . clear ( ) ;
860- botOpenIdCache . clear ( ) ;
861- sentMessageThreadMap . clear ( ) ;
862- log . debug ( "Lark runtime started" , {
863- reason,
864- workspaceCount : workspaces . length ,
865- } ) ;
866- await startLarkLongConnections ( reason ) ;
867- return true ;
799+ return larkRuntimeController . start ( reason ) ;
868800}
869801
870802export async function stopLarkRuntime ( reason : string ) : Promise < void > {
871- if ( ! larkRuntimeStarted ) return ;
872- larkRuntimeStarted = false ;
873- await stopLarkLongConnections ( reason ) ;
874- tenantTokenCache . clear ( ) ;
875- botOpenIdCache . clear ( ) ;
876- sentMessageThreadMap . clear ( ) ;
877- log . debug ( "Lark runtime stopped" , { reason } ) ;
803+ await larkRuntimeController . stop ( reason ) ;
878804}
879805
806+ const larkRuntimeController = createRuntimeController ( {
807+ isRunning : ( ) => larkRuntimeStarted ,
808+ startInternal : async ( reason : string ) : Promise < boolean > => {
809+ const workspaces = getLarkAppCredentials ( ) ;
810+ if ( workspaces . length === 0 ) {
811+ log . debug ( "Lark runtime skipped (Lark app credentials missing)" , { reason } ) ;
812+ return false ;
813+ }
814+ larkRuntimeStarted = true ;
815+ tenantTokenCache . clear ( ) ;
816+ botOpenIdCache . clear ( ) ;
817+ sentMessageThreadMap . clear ( ) ;
818+ log . debug ( "Lark runtime started" , {
819+ reason,
820+ workspaceCount : workspaces . length ,
821+ } ) ;
822+ await startLarkLongConnections ( reason ) ;
823+ return true ;
824+ } ,
825+ stopInternal : async ( reason : string ) : Promise < void > => {
826+ larkRuntimeStarted = false ;
827+ await stopLarkLongConnections ( reason ) ;
828+ tenantTokenCache . clear ( ) ;
829+ botOpenIdCache . clear ( ) ;
830+ sentMessageThreadMap . clear ( ) ;
831+ log . debug ( "Lark runtime stopped" , { reason } ) ;
832+ } ,
833+ } ) ;
834+
880835export const recoverPendingRequests = coreRuntime . recoverPendingRequests ;
0 commit comments