fix(chat): tighten runtime internal-message filtering and add targeted tests (#891)

This commit is contained in:
paisley
2026-04-22 17:23:57 +08:00
committed by GitHub
parent 36282fce35
commit c29ff4dd33
3 changed files with 101 additions and 2 deletions

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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);
});
});