mirror of
https://github.com/openclaw/openclaw.git
synced 2026-03-12 07:20:45 +00:00
146 lines
4.9 KiB
TypeScript
146 lines
4.9 KiB
TypeScript
import { resolveAgentConfig } from "../../agents/agent-scope.js";
|
|
import { getChannelDock } from "../../channels/dock.js";
|
|
import { normalizeChannelId } from "../../channels/plugins/index.js";
|
|
import type { ClawdbotConfig } from "../../config/config.js";
|
|
import type { MsgContext } from "../templating.js";
|
|
|
|
function escapeRegExp(text: string): string {
|
|
return text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
}
|
|
|
|
function deriveMentionPatterns(identity?: { name?: string; emoji?: string }) {
|
|
const patterns: string[] = [];
|
|
const name = identity?.name?.trim();
|
|
if (name) {
|
|
const parts = name.split(/\s+/).filter(Boolean).map(escapeRegExp);
|
|
const re = parts.length ? parts.join(String.raw`\s+`) : escapeRegExp(name);
|
|
patterns.push(String.raw`\b@?${re}\b`);
|
|
}
|
|
const emoji = identity?.emoji?.trim();
|
|
if (emoji) {
|
|
patterns.push(escapeRegExp(emoji));
|
|
}
|
|
return patterns;
|
|
}
|
|
|
|
const BACKSPACE_CHAR = "\u0008";
|
|
|
|
export const CURRENT_MESSAGE_MARKER = "[Current message - respond to this]";
|
|
|
|
function normalizeMentionPattern(pattern: string): string {
|
|
if (!pattern.includes(BACKSPACE_CHAR)) return pattern;
|
|
return pattern.split(BACKSPACE_CHAR).join("\\b");
|
|
}
|
|
|
|
function normalizeMentionPatterns(patterns: string[]): string[] {
|
|
return patterns.map(normalizeMentionPattern);
|
|
}
|
|
|
|
function resolveMentionPatterns(cfg: ClawdbotConfig | undefined, agentId?: string): string[] {
|
|
if (!cfg) return [];
|
|
const agentConfig = agentId ? resolveAgentConfig(cfg, agentId) : undefined;
|
|
const agentGroupChat = agentConfig?.groupChat;
|
|
if (agentGroupChat && Object.hasOwn(agentGroupChat, "mentionPatterns")) {
|
|
return agentGroupChat.mentionPatterns ?? [];
|
|
}
|
|
const globalGroupChat = cfg.messages?.groupChat;
|
|
if (globalGroupChat && Object.hasOwn(globalGroupChat, "mentionPatterns")) {
|
|
return globalGroupChat.mentionPatterns ?? [];
|
|
}
|
|
const derived = deriveMentionPatterns(agentConfig?.identity);
|
|
return derived.length > 0 ? derived : [];
|
|
}
|
|
|
|
export function buildMentionRegexes(cfg: ClawdbotConfig | undefined, agentId?: string): RegExp[] {
|
|
const patterns = normalizeMentionPatterns(resolveMentionPatterns(cfg, agentId));
|
|
return patterns
|
|
.map((pattern) => {
|
|
try {
|
|
return new RegExp(pattern, "i");
|
|
} catch {
|
|
return null;
|
|
}
|
|
})
|
|
.filter((value): value is RegExp => Boolean(value));
|
|
}
|
|
|
|
export function normalizeMentionText(text: string): string {
|
|
return (text ?? "").replace(/[\u200b-\u200f\u202a-\u202e\u2060-\u206f]/g, "").toLowerCase();
|
|
}
|
|
|
|
export function matchesMentionPatterns(text: string, mentionRegexes: RegExp[]): boolean {
|
|
if (mentionRegexes.length === 0) return false;
|
|
const cleaned = normalizeMentionText(text ?? "");
|
|
if (!cleaned) return false;
|
|
return mentionRegexes.some((re) => re.test(cleaned));
|
|
}
|
|
|
|
export type ExplicitMentionSignal = {
|
|
hasAnyMention: boolean;
|
|
isExplicitlyMentioned: boolean;
|
|
canResolveExplicit: boolean;
|
|
};
|
|
|
|
export function matchesMentionWithExplicit(params: {
|
|
text: string;
|
|
mentionRegexes: RegExp[];
|
|
explicit?: ExplicitMentionSignal;
|
|
}): boolean {
|
|
const cleaned = normalizeMentionText(params.text ?? "");
|
|
const explicit = params.explicit?.isExplicitlyMentioned === true;
|
|
const explicitAvailable = params.explicit?.canResolveExplicit === true;
|
|
const hasAnyMention = params.explicit?.hasAnyMention === true;
|
|
if (hasAnyMention && explicitAvailable) return explicit;
|
|
if (!cleaned) return explicit;
|
|
return explicit || params.mentionRegexes.some((re) => re.test(cleaned));
|
|
}
|
|
|
|
export function stripStructuralPrefixes(text: string): string {
|
|
// Ignore wrapper labels, timestamps, and sender prefixes so directive-only
|
|
// detection still works in group batches that include history/context.
|
|
const afterMarker = text.includes(CURRENT_MESSAGE_MARKER)
|
|
? text.slice(text.indexOf(CURRENT_MESSAGE_MARKER) + CURRENT_MESSAGE_MARKER.length).trimStart()
|
|
: text;
|
|
|
|
return afterMarker
|
|
.replace(/\[[^\]]+\]\s*/g, "")
|
|
.replace(/^[ \t]*[A-Za-z0-9+()\-_. ]+:\s*/gm, "")
|
|
.replace(/\\n/g, " ")
|
|
.replace(/\s+/g, " ")
|
|
.trim();
|
|
}
|
|
|
|
export function stripMentions(
|
|
text: string,
|
|
ctx: MsgContext,
|
|
cfg: ClawdbotConfig | undefined,
|
|
agentId?: string,
|
|
): string {
|
|
let result = text;
|
|
const providerId = ctx.Provider ? normalizeChannelId(ctx.Provider) : null;
|
|
const providerMentions = providerId ? getChannelDock(providerId)?.mentions : undefined;
|
|
const patterns = normalizeMentionPatterns([
|
|
...resolveMentionPatterns(cfg, agentId),
|
|
...(providerMentions?.stripPatterns?.({ ctx, cfg, agentId }) ?? []),
|
|
]);
|
|
for (const p of patterns) {
|
|
try {
|
|
const re = new RegExp(p, "gi");
|
|
result = result.replace(re, " ");
|
|
} catch {
|
|
// ignore invalid regex
|
|
}
|
|
}
|
|
if (providerMentions?.stripMentions) {
|
|
result = providerMentions.stripMentions({
|
|
text: result,
|
|
ctx,
|
|
cfg,
|
|
agentId,
|
|
});
|
|
}
|
|
// Generic mention patterns like @123456789 or plain digits
|
|
result = result.replace(/@[0-9+]{5,}/g, " ");
|
|
return result.replace(/\s+/g, " ").trim();
|
|
}
|