From c29ff4dd33364cfa1f65ade8e242d7022e4c3da2 Mon Sep 17 00:00:00 2001 From: paisley <8197966+su8su@users.noreply.github.com> Date: Wed, 22 Apr 2026 17:23:57 +0800 Subject: [PATCH] fix(chat): tighten runtime internal-message filtering and add targeted tests (#891) --- src/stores/chat.ts | 32 ++++++++++++++- src/stores/chat/helpers.ts | 39 ++++++++++++++++++- .../unit/chat-internal-message-filter.test.ts | 32 +++++++++++++++ 3 files changed, 101 insertions(+), 2 deletions(-) create mode 100644 tests/unit/chat-internal-message-filter.test.ts diff --git a/src/stores/chat.ts b/src/stores/chat.ts index c8d4ffb..574f561 100644 --- a/src/stores/chat.ts +++ b/src/stores/chat.ts @@ -934,10 +934,40 @@ function isToolResultRole(role: unknown): boolean { /** True for internal plumbing messages that should never be shown in the UI. */ function isInternalMessage(msg: { role?: unknown; content?: unknown }): boolean { if (msg.role === 'system') return true; + const text = getMessageText(msg.content); if (msg.role === 'assistant') { - const text = getMessageText(msg.content); if (/^(HEARTBEAT_OK|NO_REPLY)\s*$/.test(text)) return true; } + // Runtime system injections: these arrive as user or assistant-role messages + // but are internal plumbing (exec results, async-command notices, time pings, etc.) + if ((msg.role === 'user' || msg.role === 'assistant') && isRuntimeSystemInjection(text)) return true; + return false; +} + +/** + * Detect runtime-injected system messages that should be hidden from the chat UI. + * These are injected by the OpenClaw runtime as user-role messages and include: + * - "System (untrusted): ..." — exec results, tool output, etc. + * - "An async command you ran earlier has completed" — async completion notices + * - "Current time: ..." followed by nothing else — periodic heartbeat time pings + * - "Handle the result internally. Do not relay it to the user" — internal directives + */ +function isRuntimeSystemInjection(text: string): boolean { + if (!text) return false; + const normalized = text.trim(); + if (/^\s*System\s*\(untrusted\)\s*:/i.test(normalized)) return true; + if ( + /An async command you ran earlier has completed/i.test(normalized) + && /Do not relay it to the user unless explicitly requested/i.test(normalized) + ) { + return true; + } + if ( + /^\s*Current time\s*:/i.test(normalized) + && /^\s*Current time\s*:[^\n]*\/\s*\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}\s+UTC\s*$/i.test(normalized) + ) { + return true; + } return false; } diff --git a/src/stores/chat/helpers.ts b/src/stores/chat/helpers.ts index b794548..b317cc0 100644 --- a/src/stores/chat/helpers.ts +++ b/src/stores/chat/helpers.ts @@ -737,10 +737,47 @@ function isToolResultRole(role: unknown): boolean { /** True for internal plumbing messages that should never be shown in the UI. */ function isInternalMessage(msg: { role?: unknown; content?: unknown }): boolean { if (msg.role === 'system') return true; + const text = getMessageText(msg.content); if (msg.role === 'assistant') { - const text = getMessageText(msg.content); if (/^(HEARTBEAT_OK|NO_REPLY)\s*$/.test(text)) return true; } + // Runtime system injections: these arrive as user or assistant-role messages + // but are internal plumbing (exec results, async-command notices, time pings, etc.) + if ((msg.role === 'user' || msg.role === 'assistant') && isRuntimeSystemInjection(text)) return true; + return false; +} + +/** + * Detect runtime-injected system messages that should be hidden from the chat UI. + * These are injected by the OpenClaw runtime as user-role messages and include: + * - "System (untrusted): ..." — exec results, tool output, etc. + * - "An async command you ran earlier has completed" — async completion notices + * - "Current time: ..." followed by nothing else — periodic heartbeat time pings + * - "Handle the result internally. Do not relay it to the user" — internal directives + */ +function isRuntimeSystemInjection(text: string): boolean { + if (!text) return false; + const normalized = text.trim(); + // "System (untrusted): ..." at the start (with optional leading whitespace) + if (/^\s*System\s*\(untrusted\)\s*:/i.test(normalized)) return true; + + // Async command completion notice + internal relay directive commonly arrive together. + // Require both markers to avoid hiding normal conversational text that quotes one phrase. + if ( + /An async command you ran earlier has completed/i.test(normalized) + && /Do not relay it to the user unless explicitly requested/i.test(normalized) + ) { + return true; + } + + // Standalone time injection (e.g. "Current time: Wednesday, April 22nd, 2026 - 10:06 (Asia/Shanghai) / 2026-04-22 02:06 UTC") + // Only match when the full message is the time announcement. + if ( + /^\s*Current time\s*:/i.test(normalized) + && /^\s*Current time\s*:[^\n]*\/\s*\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}\s+UTC\s*$/i.test(normalized) + ) { + return true; + } return false; } diff --git a/tests/unit/chat-internal-message-filter.test.ts b/tests/unit/chat-internal-message-filter.test.ts new file mode 100644 index 0000000..fc6dc82 --- /dev/null +++ b/tests/unit/chat-internal-message-filter.test.ts @@ -0,0 +1,32 @@ +import { describe, expect, it } from 'vitest'; +import { isInternalMessage } from '@/stores/chat/helpers'; + +describe('chat internal message filter', () => { + it('filters runtime system injection bundle like async exec completion payload', () => { + const content = [ + 'System (untrusted): [2026-04-22 10:06:24 GMT+8] Exec completed (nimbler, code 0) ...', + 'An async command you ran earlier has completed. The result is shown in the system messages above. Handle the result internally. Do not relay it to the user unless explicitly requested.', + 'Current time: Wednesday, April 22nd, 2026 - 10:06 (Asia/Shanghai) / 2026-04-22 02:06 UTC', + ].join('\n\n'); + + expect(isInternalMessage({ role: 'user', content })).toBe(true); + }); + + it('filters standalone current-time runtime ping', () => { + const content = 'Current time: Wednesday, April 22nd, 2026 - 10:06 (Asia/Shanghai) / 2026-04-22 02:06 UTC'; + + expect(isInternalMessage({ role: 'assistant', content })).toBe(true); + }); + + it('does not filter normal user message that starts with current time', () => { + const content = 'Current time: 北京现在几点?'; + + expect(isInternalMessage({ role: 'user', content })).toBe(false); + }); + + it('does not filter normal assistant text that mentions async completion phrase only', () => { + const content = 'The sentence "An async command you ran earlier has completed" is just an example in docs.'; + + expect(isInternalMessage({ role: 'assistant', content })).toBe(false); + }); +});