Spaces:
Runtime error
Runtime error
| import type { Database } from 'better-sqlite3' | |
| export interface MentionTarget { | |
| handle: string | |
| recipient: string | |
| type: 'user' | 'agent' | |
| display: string | |
| role?: string | |
| } | |
| export interface MentionResolution { | |
| tokens: string[] | |
| unresolved: string[] | |
| recipients: string[] | |
| resolved: MentionTarget[] | |
| } | |
| const MENTION_PATTERN = /(^|[^A-Za-z0-9._-])@([A-Za-z0-9][A-Za-z0-9._-]{0,63})/g | |
| function normalizeAgentHandle(name: string): string { | |
| return name.trim().toLowerCase().replace(/\s+/g, '-') | |
| } | |
| export function parseMentions(text: string): string[] { | |
| if (!text || typeof text !== 'string') return [] | |
| const found: string[] = [] | |
| const seen = new Set<string>() | |
| let match: RegExpExecArray | null | |
| while ((match = MENTION_PATTERN.exec(text)) !== null) { | |
| const token = String(match[2] || '').trim() | |
| if (!token) continue | |
| const key = token.toLowerCase() | |
| if (seen.has(key)) continue | |
| seen.add(key) | |
| found.push(token) | |
| } | |
| return found | |
| } | |
| export function getMentionTargets(db: Database, workspaceId: number): MentionTarget[] { | |
| const targets: MentionTarget[] = [] | |
| const seenHandles = new Set<string>() | |
| const users = db.prepare(` | |
| SELECT username, display_name | |
| FROM users | |
| WHERE workspace_id = ? | |
| ORDER BY username ASC | |
| `).all(workspaceId) as Array<{ username: string; display_name?: string | null }> | |
| for (const user of users) { | |
| const username = String(user.username || '').trim() | |
| if (!username) continue | |
| const handle = username.toLowerCase() | |
| if (seenHandles.has(handle)) continue | |
| seenHandles.add(handle) | |
| targets.push({ | |
| handle, | |
| recipient: username, | |
| type: 'user', | |
| display: user.display_name?.trim() || username, | |
| }) | |
| } | |
| const agents = db.prepare(` | |
| SELECT name, role, config | |
| FROM agents | |
| WHERE workspace_id = ? | |
| ORDER BY name ASC | |
| `).all(workspaceId) as Array<{ name: string; role?: string | null; config?: string | null }> | |
| for (const agent of agents) { | |
| const recipient = String(agent.name || '').trim() | |
| if (!recipient) continue | |
| let openclawId: string | null = null | |
| try { | |
| const parsed = agent.config ? JSON.parse(agent.config) : null | |
| if (parsed && typeof parsed.openclawId === 'string' && parsed.openclawId.trim()) { | |
| openclawId = parsed.openclawId.trim() | |
| } | |
| } catch { | |
| // ignore invalid config JSON for mention indexing | |
| } | |
| const candidateHandles = [openclawId, normalizeAgentHandle(recipient), recipient.toLowerCase()] | |
| .filter((value): value is string => Boolean(value)) | |
| for (const rawHandle of candidateHandles) { | |
| const handle = rawHandle.toLowerCase() | |
| if (!handle || seenHandles.has(handle)) continue | |
| seenHandles.add(handle) | |
| targets.push({ | |
| handle, | |
| recipient, | |
| type: 'agent', | |
| display: recipient, | |
| role: agent.role || undefined, | |
| }) | |
| } | |
| } | |
| return targets | |
| } | |
| export function resolveMentionRecipients(text: string, db: Database, workspaceId: number): MentionResolution { | |
| const tokens = parseMentions(text) | |
| if (tokens.length === 0) { | |
| return { tokens: [], unresolved: [], recipients: [], resolved: [] } | |
| } | |
| const targets = getMentionTargets(db, workspaceId) | |
| const byHandle = new Map<string, MentionTarget>() | |
| for (const target of targets) { | |
| byHandle.set(target.handle.toLowerCase(), target) | |
| } | |
| const resolved: MentionTarget[] = [] | |
| const unresolved: string[] = [] | |
| const recipientSeen = new Set<string>() | |
| for (const token of tokens) { | |
| const key = token.toLowerCase() | |
| const target = byHandle.get(key) | |
| if (!target) { | |
| unresolved.push(token) | |
| continue | |
| } | |
| if (!recipientSeen.has(target.recipient)) { | |
| recipientSeen.add(target.recipient) | |
| resolved.push(target) | |
| } | |
| } | |
| return { | |
| tokens, | |
| unresolved, | |
| recipients: resolved.map((item) => item.recipient), | |
| resolved, | |
| } | |
| } | |