diff --git a/extensions/memory-neo4j/attention-gate.ts b/extensions/memory-neo4j/attention-gate.ts index 3ac72190264..7f334fe25c6 100644 --- a/extensions/memory-neo4j/attention-gate.ts +++ b/extensions/memory-neo4j/attention-gate.ts @@ -47,6 +47,10 @@ const NOISE_PATTERNS = [ /^GatewayRestart:\s*\{/i, // Background task completion reports /^\[\w{3}\s+\d{4}-\d{2}-\d{2}\s.*\]\s*A background task/i, + + // --- Conversation metadata that survived stripping --- + /^Conversation info\s*\(/i, + /^\[Queued messages/i, ]; /** Maximum message length — code dumps, logs, etc. are not memories. */ @@ -112,17 +116,19 @@ const MIN_ASSISTANT_WORD_COUNT = 10; */ const ASSISTANT_NARRATION_PATTERNS = [ // "Let me ..." / "Now let me ..." / "I'll ..." action narration - /^(ok[,.]?\s+)?(now\s+)?let me\s+(check|look|see|try|run|start|test|read|update|verify|fix|search|process|create|build|set up|examine|investigate|query|fetch|pull|scan|clean|install|download|configure)/i, + /^(ok[,.]?\s+)?(now\s+)?let me\s+(check|look|see|try|run|start|test|read|update|verify|fix|search|process|create|build|set up|examine|investigate|query|fetch|pull|scan|clean|install|download|configure|make|select|click|type|fill|open|close|switch|send|post|submit|edit|change|add|remove|write|save|upload)/i, // "I'll ..." action narration - /^I('ll| will)\s+(check|look|see|try|run|start|test|read|update|verify|fix|search|process|create|build|set up|examine|investigate|query|fetch|pull|scan|clean|install|download|configure|execute|help|handle)/i, + /^I('ll| will)\s+(check|look|see|try|run|start|test|read|update|verify|fix|search|process|create|build|set up|examine|investigate|query|fetch|pull|scan|clean|install|download|configure|execute|help|handle|make|select|click|type|fill|open|close|switch|send|post|submit|edit|change|add|remove|write|save|upload|use|grab|get|do)/i, // "Starting ..." / "Running ..." / "Processing ..." status updates - /^(starting|running|processing|checking|fetching|scanning|building|installing|downloading|configuring|executing|loading|updating)\s/i, - // "Good!" / "Great!" / "Perfect!" as opener followed by narration - /^(good|great|perfect|nice|excellent|awesome|done)[!.]?\s+(i |the |now |let |we |that )/i, + /^(starting|running|processing|checking|fetching|scanning|building|installing|downloading|configuring|executing|loading|updating|filling|selecting|clicking|typing|opening|closing|switching|navigating|uploading|saving|sending|posting|submitting)\s/i, + // "Good!" / "Great!" / "Perfect!" / "Done!" as opener followed by narration + /^(good|great|perfect|nice|excellent|awesome|done)[!.]?\s+(i |the |now |let |we |that |here)/i, // Progress narration: "Now I have..." / "Now I can see..." / "Now let me..." - /^now\s+(i\s+(have|can|need|see|understand)|we\s+(have|can|need)|the\s)/i, + /^now\s+(i\s+(have|can|need|see|understand)|we\s+(have|can|need)|the\s|on\s)/i, // Step narration: "Step 1:" / "**Step 1:**" /^\*?\*?step\s+\d/i, + // Page/section progress narration: "Page 1 done!", "Page 3 — final page!" + /^Page\s+\d/i, // Narration of what was found/done: "Found it." / "Found X." / "I see — ..." /^(found it|found the|i see\s*[—–-])/i, // Sub-agent task descriptions (workflow narration) @@ -131,6 +137,16 @@ const ASSISTANT_NARRATION_PATTERNS = [ /^🔄\s*\*?\*?context reset/i, // Filename slug generation prompts (internal tool use) /^based on this conversation,?\s*generate a short/i, + + // --- Conversational filler responses (not knowledge) --- + // "I'm here" / "I am here" filler: "I'm here to help", "I am here and listening", etc. + /^I('m| am) here\b/i, + // Ready-state: "Sure, (just) tell me what you want..." + /^Sure[,!.]?\s+(just\s+)?(tell|let)\s+me/i, + // Observational UI narration: "I can see the picker", "I can see the button" + /^I can see\s/i, + // A sub-agent task report (quoted or inline) + /^A sub-?agent task\b/i, ]; export function passesAssistantAttentionGate(text: string): boolean { diff --git a/extensions/memory-neo4j/config.ts b/extensions/memory-neo4j/config.ts index 8348396ff82..50dcdc1827c 100644 --- a/extensions/memory-neo4j/config.ts +++ b/extensions/memory-neo4j/config.ts @@ -31,8 +31,15 @@ export type MemoryNeo4jConfig = { baseUrl: string; }; autoCapture: boolean; + autoCaptureSkipPattern?: RegExp; autoRecall: boolean; autoRecallMinScore: number; + /** + * RegExp pattern to skip auto-recall for matching session keys. + * Useful for voice/realtime sessions where latency is critical. + * Example: /voice|realtime/ skips sessions containing "voice" or "realtime". + */ + autoRecallSkipPattern?: RegExp; coreMemory: { enabled: boolean; maxEntries: number; @@ -207,8 +214,10 @@ export const memoryNeo4jConfigSchema = { "embedding", "neo4j", "autoCapture", + "autoCaptureSkipPattern", "autoRecall", "autoRecallMinScore", + "autoRecallSkipPattern", "coreMemory", "extraction", "graphSearchDepth", @@ -369,8 +378,16 @@ export const memoryNeo4jConfigSchema = { }, extraction, autoCapture: cfg.autoCapture !== false, + autoCaptureSkipPattern: + typeof cfg.autoCaptureSkipPattern === "string" && cfg.autoCaptureSkipPattern + ? new RegExp(cfg.autoCaptureSkipPattern) + : undefined, autoRecall: cfg.autoRecall !== false, autoRecallMinScore: parseAutoRecallMinScore(cfg.autoRecallMinScore), + autoRecallSkipPattern: + typeof cfg.autoRecallSkipPattern === "string" && cfg.autoRecallSkipPattern + ? new RegExp(cfg.autoRecallSkipPattern) + : undefined, coreMemory: { enabled: coreMemoryEnabled, maxEntries: coreMemoryMaxEntries, diff --git a/extensions/memory-neo4j/index.ts b/extensions/memory-neo4j/index.ts index caef5ac4e0a..5d533a068b9 100644 --- a/extensions/memory-neo4j/index.ts +++ b/extensions/memory-neo4j/index.ts @@ -986,6 +986,17 @@ const memoryNeo4jPlugin = { return; } + // Skip auto-recall for voice/realtime sessions where latency is critical. + // These sessions use short conversational turns that don't benefit from + // memory injection, and the ~100-300ms embedding+search overhead matters. + const sessionKey = ctx.sessionKey ?? ""; + if (cfg.autoRecallSkipPattern && cfg.autoRecallSkipPattern.test(sessionKey)) { + api.logger.debug?.( + `memory-neo4j: skipping auto-recall for session ${sessionKey} (matches skipPattern)`, + ); + return; + } + const agentId = ctx.agentId || "default"; // ~1000 chars keeps us safely within even small embedding contexts @@ -1156,8 +1167,20 @@ const memoryNeo4jPlugin = { return; } - const agentId = ctx.agentId || "default"; + // Skip auto-capture for sessions matching the skip pattern (e.g. voice sessions) const sessionKey = ctx.sessionKey; + if ( + cfg.autoCaptureSkipPattern && + sessionKey && + cfg.autoCaptureSkipPattern.test(sessionKey) + ) { + api.logger.debug?.( + `memory-neo4j: skipping auto-capture for session ${sessionKey} (matches skipPattern)`, + ); + return; + } + + const agentId = ctx.agentId || "default"; // Fire-and-forget: run auto-capture asynchronously so it doesn't // block the agent_end hook (which otherwise adds 2-10s per turn). diff --git a/extensions/memory-neo4j/message-utils.ts b/extensions/memory-neo4j/message-utils.ts index 08e68db1ec4..8eac03368a4 100644 --- a/extensions/memory-neo4j/message-utils.ts +++ b/extensions/memory-neo4j/message-utils.ts @@ -72,10 +72,22 @@ export function stripMessageWrappers(text: string): string { s = s.replace(/^\[media attached:[^\]]*\]\s*(?:To send an image[^\n]*\n?)*/i, ""); // System exec output blocks (may appear before Telegram wrapper) s = s.replace(/^(?:System:\s*\[[^\]]*\][^\n]*\n?)+/gi, ""); + // Voice chat timestamp prefix: [Tue 2026-02-10 19:41 GMT+8] + s = s.replace( + /^\[(?:Mon|Tue|Wed|Thu|Fri|Sat|Sun)\s+\d{4}-\d{2}-\d{2}\s+\d{1,2}:\d{2}\s+GMT[+-]\d+\]\s*/i, + "", + ); + // Conversation info metadata block (gateway routing context with JSON code fence) + s = s.replace(/Conversation info\s*\(untrusted metadata\):\s*```[\s\S]*?```\s*/g, ""); + // Queued message batch header and separators + s = s.replace(/^\[Queued messages while agent was busy\]\s*/i, ""); + s = s.replace(/---\s*Queued #\d+\s*/g, ""); // Telegram wrapper — may now be at start after previous strips s = s.replace(/^\s*\[Telegram\s[^\]]+\]\s*/i, ""); // "[message_id: NNN]" suffix (Telegram) s = s.replace(/\n?\[message_id:\s*\d+\]\s*$/i, ""); + // "[message_id: UUID]" suffix (non-numeric Telegram/channel IDs) + s = s.replace(/\n?\[message_id:\s*[^\]]+\]\s*$/i, ""); // Slack wrapper — "[Slack #channel @user] MESSAGE [slack message id: ...]" s = s.replace(/^\s*\[Slack\s[^\]]+\]\s*/i, ""); s = s.replace(/\n?\[slack message id:\s*[^\]]*\]\s*$/i, ""); diff --git a/extensions/memory-neo4j/openclaw.plugin.json b/extensions/memory-neo4j/openclaw.plugin.json index 5ea257df7cc..54abca1997b 100644 --- a/extensions/memory-neo4j/openclaw.plugin.json +++ b/extensions/memory-neo4j/openclaw.plugin.json @@ -188,6 +188,14 @@ }, "required": ["halfLifeDays"] } + }, + "autoRecallSkipPattern": { + "type": "string", + "description": "RegExp pattern to skip auto-recall for matching session keys (e.g. voice|realtime)" + }, + "autoCaptureSkipPattern": { + "type": "string", + "description": "RegExp pattern to skip auto-capture for matching session keys (e.g. voice|realtime)" } }, "required": ["neo4j"]