From 3659179fc99905cb4e77f8b3bf2148593b78d486 Mon Sep 17 00:00:00 2001 From: paisley <8197966+su8su@users.noreply.github.com> Date: Thu, 21 May 2026 15:01:50 +0800 Subject: [PATCH] fix(chat): show inbound channel messages on the active main session (#1055) --- src/stores/chat.ts | 12 +++++++++++- src/stores/chat/helpers.ts | 1 + src/stores/gateway.ts | 19 +++++++++++++++++++ .../unit/chat-internal-message-filter.test.ts | 4 ++++ 4 files changed, 35 insertions(+), 1 deletion(-) diff --git a/src/stores/chat.ts b/src/stores/chat.ts index 033e0fc..d843ce6 100644 --- a/src/stores/chat.ts +++ b/src/stores/chat.ts @@ -1596,6 +1596,7 @@ function isInternalMessage(msg: { role?: unknown; content?: unknown; idempotency if (!hasImageUrlBlock) return true; } } + if (msg.role === 'user' && /^\[OpenClaw heartbeat poll\]\s*$/i.test(text.trim())) 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; @@ -3146,8 +3147,17 @@ export const useChatStore = create((set, get) => ({ return; } - // Only process events for the active run (or if no active run set) + // Only process events for the active run (or if no active run set). + // Inbound channel traffic (Feishu/Telegram/etc.) on the current session uses a + // different runId than a stale desktop activeRunId — still refresh history on finals. if (activeRunId && runId && runId !== activeRunId) { + const isCurrentSession = eventSessionKey == null || eventSessionKey === currentSessionKey; + const inboundTerminal = eventState === 'final' || eventState === 'error' + || (event.message && typeof event.message === 'object' + && getMessageStopReason(event.message as Record) != null); + if (isCurrentSession && inboundTerminal) { + void get().loadHistory(true); + } return; } diff --git a/src/stores/chat/helpers.ts b/src/stores/chat/helpers.ts index c3d8b34..2c3a0aa 100644 --- a/src/stores/chat/helpers.ts +++ b/src/stores/chat/helpers.ts @@ -1084,6 +1084,7 @@ function isInternalMessage(msg: { role?: unknown; content?: unknown }): boolean if (msg.role === 'assistant') { if (/^(HEARTBEAT_OK|NO_REPLY)\s*$/.test(text)) return true; } + if (msg.role === 'user' && /^\[OpenClaw heartbeat poll\]\s*$/i.test(text.trim())) 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; diff --git a/src/stores/gateway.ts b/src/stores/gateway.ts index 7094379..f9c2681 100644 --- a/src/stores/gateway.ts +++ b/src/stores/gateway.ts @@ -153,6 +153,21 @@ function maybeLoadHistory( void state.loadHistory(true); } +/** Bump sidebar ordering when any session receives gateway traffic (e.g. Feishu DM). */ +function touchSessionActivity(sessionKey: string | null | undefined, activityMs = Date.now()): void { + if (!sessionKey) return; + import('./chat') + .then(({ useChatStore }) => { + useChatStore.setState((state) => ({ + sessionLastActivity: { + ...state.sessionLastActivity, + [sessionKey]: Math.max(state.sessionLastActivity[sessionKey] ?? 0, activityMs), + }, + })); + }) + .catch(() => {}); +} + function handleGatewayNotification(notification: { method?: string; params?: Record } | undefined): void { const payload = notification; if (!payload || payload.method !== 'agent' || !payload.params || typeof payload.params !== 'object') { @@ -185,6 +200,10 @@ function handleGatewayNotification(notification: { method?: string; params?: Rec const runId = p.runId ?? data.runId; const sessionKey = p.sessionKey ?? data.sessionKey; + const resolvedSessionKeyForActivity = sessionKey != null ? String(sessionKey) : null; + if (resolvedSessionKeyForActivity) { + touchSessionActivity(resolvedSessionKeyForActivity); + } if (phase === 'started' && runId != null && sessionKey != null) { import('./chat') .then(({ useChatStore }) => { diff --git a/tests/unit/chat-internal-message-filter.test.ts b/tests/unit/chat-internal-message-filter.test.ts index fc6dc82..8eeb1d7 100644 --- a/tests/unit/chat-internal-message-filter.test.ts +++ b/tests/unit/chat-internal-message-filter.test.ts @@ -29,4 +29,8 @@ describe('chat internal message filter', () => { expect(isInternalMessage({ role: 'assistant', content })).toBe(false); }); + + it('filters OpenClaw heartbeat poll user turns', () => { + expect(isInternalMessage({ role: 'user', content: '[OpenClaw heartbeat poll]' })).toBe(true); + }); });