/** * Detect whether a user-role message is actually a system event * (heartbeat, webhook, cron, channel event, etc.) rather than * a real human message. */ const SYSTEM_PATTERNS: RegExp[] = [ // Explicit markers /^\[EVENT\b/i, /\[from:\s*[^\]]*\(system\)\]/i, /^\[HEARTBEAT\b/i, /^\[cron:/i, /^\[hook:/i, /^\[webhook:/i, /^\[sms-inbound\b/i, /^\[teamspeak\b/i, // Heartbeat prompt pattern (the standard OpenClaw heartbeat) /^Read HEARTBEAT\.md if it exists/, // System event envelope: [source:xxx] /^\[source:\s*\w+\]/i, ]; export function isSystemEvent(text: string): boolean { const trimmed = text.trim(); if (!trimmed) return false; return SYSTEM_PATTERNS.some(pat => pat.test(trimmed)); } /** * Strip webhook/hook scaffolding from message content. * OpenClaw wraps inbound webhook payloads in security envelopes like: * [hook:agent task_id=xxx job_id=xxx] * --- SECURITY NOTICE --- * ... * <<>> * actual message * <<>> * * This extracts the actual user content and returns it clean. * Also strips leading [hook:...] / [cron:...] / [sms-inbound ...] tags * and SECURITY NOTICE blocks when no EXTERNAL_UNTRUSTED_CONTENT delimiters exist. */ export function stripWebhookScaffolding(text: string): string { const trimmed = text.trim(); // Extract content between <<>> delimiters const extMatch = trimmed.match( /<<>>\s*([\s\S]*?)\s*<<>>/ ); if (extMatch) { return extMatch[1].trim(); } // Strip leading bracket tags: [hook:...], [cron:...], [sms-inbound ...], etc. let cleaned = trimmed.replace(/^\[(?:hook|cron|webhook|sms-inbound)[^\]]*\]\s*/i, ''); // Strip SECURITY NOTICE blocks (--- SECURITY NOTICE --- ... --- END ---) cleaned = cleaned.replace( /---\s*SECURITY NOTICE\s*---[\s\S]*?---\s*END\s*---\s*/i, '' ); // Strip standalone security notice lines without END marker cleaned = cleaned.replace( /---\s*SECURITY NOTICE\s*---[^\n]*\n(?:.*\n)*?(?=\S)/i, '' ); // Strip task/job ID lines cleaned = cleaned.replace(/^(?:task_id|job_id|Task|Job)\s*[:=]\s*\S+\s*\n?/gim, ''); return cleaned.trim() || trimmed; } /** * Check if a message contains webhook scaffolding that should be cleaned. */ export function hasWebhookScaffolding(text: string): boolean { return /<<>>/.test(text) || /---\s*SECURITY NOTICE\s*---/i.test(text); }