2026-01-13 06:16:43 +00:00
import { getChannelDock } from "../../channels/dock.js" ;
2026-01-15 02:42:41 +00:00
import { getChannelPlugin , normalizeChannelId } from "../../channels/plugins/index.js" ;
2026-01-30 03:15:10 +01:00
import type { OpenClawConfig } from "../../config/config.js" ;
2026-01-14 14:31:43 +00:00
import type { GroupKeyResolution , SessionEntry } from "../../config/sessions.js" ;
2026-01-13 06:16:43 +00:00
import { isInternalMessageChannel } from "../../utils/message-channel.js" ;
2026-01-04 05:47:21 +01:00
import { normalizeGroupActivation } from "../group-activation.js" ;
import type { TemplateContext } from "../templating.js" ;
2026-01-17 08:46:19 +00:00
function extractGroupId ( raw : string | undefined | null ) : string | undefined {
const trimmed = ( raw ? ? "" ) . trim ( ) ;
if ( ! trimmed ) return undefined ;
const parts = trimmed . split ( ":" ) . filter ( Boolean ) ;
if ( parts . length >= 3 && ( parts [ 1 ] === "group" || parts [ 1 ] === "channel" ) ) {
return parts . slice ( 2 ) . join ( ":" ) || undefined ;
}
if (
parts . length >= 2 &&
parts [ 0 ] ? . toLowerCase ( ) === "whatsapp" &&
trimmed . toLowerCase ( ) . includes ( "@g.us" )
) {
return parts . slice ( 1 ) . join ( ":" ) || undefined ;
}
if ( parts . length >= 2 && ( parts [ 0 ] === "group" || parts [ 0 ] === "channel" ) ) {
return parts . slice ( 1 ) . join ( ":" ) || undefined ;
}
return trimmed ;
}
2026-01-04 05:47:21 +01:00
export function resolveGroupRequireMention ( params : {
2026-01-30 03:15:10 +01:00
cfg : OpenClawConfig ;
2026-01-04 05:47:21 +01:00
ctx : TemplateContext ;
groupResolution? : GroupKeyResolution ;
} ) : boolean {
const { cfg , ctx , groupResolution } = params ;
2026-01-13 06:16:43 +00:00
const rawChannel = groupResolution ? . channel ? ? ctx . Provider ? . trim ( ) ;
const channel = normalizeChannelId ( rawChannel ) ;
if ( ! channel ) return true ;
2026-01-17 08:46:19 +00:00
const groupId = groupResolution ? . id ? ? extractGroupId ( ctx . From ) ;
2026-01-17 07:41:01 +00:00
const groupChannel = ctx . GroupChannel ? . trim ( ) ? ? ctx . GroupSubject ? . trim ( ) ;
2026-01-05 13:55:32 +00:00
const groupSpace = ctx . GroupSpace ? . trim ( ) ;
2026-01-14 14:31:43 +00:00
const requireMention = getChannelDock ( channel ) ? . groups ? . resolveRequireMention ? . ( {
2026-01-11 11:45:25 +00:00
cfg ,
groupId ,
2026-01-17 07:41:01 +00:00
groupChannel ,
2026-01-11 11:45:25 +00:00
groupSpace ,
accountId : ctx.AccountId ,
} ) ;
if ( typeof requireMention === "boolean" ) return requireMention ;
2026-01-04 05:47:21 +01:00
return true ;
}
2026-01-14 14:31:43 +00:00
export function defaultGroupActivation ( requireMention : boolean ) : "always" | "mention" {
2026-01-31 16:03:28 +09:00
return ! requireMention ? "always" : "mention" ;
2026-01-04 05:47:21 +01:00
}
export function buildGroupIntro ( params : {
2026-01-30 03:15:10 +01:00
cfg : OpenClawConfig ;
2026-01-04 05:47:21 +01:00
sessionCtx : TemplateContext ;
sessionEntry? : SessionEntry ;
defaultActivation : "always" | "mention" ;
silentToken : string ;
} ) : string {
const activation =
2026-01-14 14:31:43 +00:00
normalizeGroupActivation ( params . sessionEntry ? . groupActivation ) ? ? params . defaultActivation ;
2026-01-04 05:47:21 +01:00
const subject = params . sessionCtx . GroupSubject ? . trim ( ) ;
const members = params . sessionCtx . GroupMembers ? . trim ( ) ;
2026-01-11 11:45:25 +00:00
const rawProvider = params . sessionCtx . Provider ? . trim ( ) ;
const providerKey = rawProvider ? . toLowerCase ( ) ? ? "" ;
2026-01-13 06:16:43 +00:00
const providerId = normalizeChannelId ( rawProvider ) ;
2026-01-06 18:25:37 +00:00
const providerLabel = ( ( ) = > {
2026-01-11 11:45:25 +00:00
if ( ! providerKey ) return "chat" ;
2026-01-13 06:16:43 +00:00
if ( isInternalMessageChannel ( providerKey ) ) return "WebChat" ;
2026-01-15 02:42:41 +00:00
if ( providerId ) return getChannelPlugin ( providerId ) ? . meta . label ? ? providerId ;
2026-01-11 11:45:25 +00:00
return ` ${ providerKey . at ( 0 ) ? . toUpperCase ( ) ? ? "" } ${ providerKey . slice ( 1 ) } ` ;
2026-01-04 05:47:21 +01:00
} ) ( ) ;
const subjectLine = subject
2026-01-06 18:25:37 +00:00
? ` You are replying inside the ${ providerLabel } group " ${ subject } ". `
: ` You are replying inside a ${ providerLabel } group chat. ` ;
2026-01-04 05:47:21 +01:00
const membersLine = members ? ` Group members: ${ members } . ` : undefined ;
const activationLine =
activation === "always"
? "Activation: always-on (you receive every group message)."
: "Activation: trigger-only (you are invoked only when explicitly mentioned; recent context may be included)." ;
2026-01-17 08:46:19 +00:00
const groupId = params . sessionEntry ? . groupId ? ? extractGroupId ( params . sessionCtx . From ) ;
2026-01-17 07:41:01 +00:00
const groupChannel = params . sessionCtx . GroupChannel ? . trim ( ) ? ? subject ;
2026-01-11 11:45:25 +00:00
const groupSpace = params . sessionCtx . GroupSpace ? . trim ( ) ;
const providerIdsLine = providerId
2026-01-13 06:16:43 +00:00
? getChannelDock ( providerId ) ? . groups ? . resolveGroupIntroHint ? . ( {
2026-01-11 11:45:25 +00:00
cfg : params.cfg ,
groupId ,
2026-01-17 07:41:01 +00:00
groupChannel ,
2026-01-11 11:45:25 +00:00
groupSpace ,
accountId : params.sessionCtx.AccountId ,
} )
: undefined ;
2026-01-04 05:47:21 +01:00
const silenceLine =
activation === "always"
2026-01-30 03:15:10 +01:00
? ` If no response is needed, reply with exactly " ${ params . silentToken } " (and nothing else) so OpenClaw stays silent. Do not add any other words, punctuation, tags, markdown/code blocks, or explanations. `
2026-01-04 05:47:21 +01:00
: undefined ;
const cautionLine =
activation === "always"
2026-01-07 08:14:47 +01:00
? "Be extremely selective: reply only when directly addressed or clearly helpful. Otherwise stay silent."
2026-01-04 05:47:21 +01:00
: undefined ;
const lurkLine =
2026-01-07 10:03:50 +00:00
"Be a good group participant: mostly lurk and follow the conversation; reply only when directly addressed or you can add clear value. Emoji reactions are welcome when available." ;
2026-01-13 03:40:22 +00:00
const styleLine =
"Write like a human. Avoid Markdown tables. Don't type literal \\n sequences; use real line breaks sparingly." ;
2026-01-04 05:47:21 +01:00
return [
subjectLine ,
membersLine ,
activationLine ,
2026-01-11 11:45:25 +00:00
providerIdsLine ,
2026-01-04 05:47:21 +01:00
silenceLine ,
cautionLine ,
lurkLine ,
2026-01-13 03:40:22 +00:00
styleLine ,
2026-01-04 05:47:21 +01:00
]
. filter ( Boolean )
. join ( " " )
. concat ( " Address the specific sender noted in the message context." ) ;
}