From 3aa6d0503dbcba08666cb6ace45dcef2b9144a67 Mon Sep 17 00:00:00 2001 From: spiceoogway Date: Fri, 30 Jan 2026 08:37:23 -0500 Subject: [PATCH 1/3] fix: Remove standard Google providers from reasoning tag enforcement (#4536) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes #4536 - WebChat shows empty Assistant responses Root cause: Standard Google providers (google-gemini-cli, google-generative-ai) were incorrectly classified as 'reasoning tag providers', which: 1. Added instructions to use and tags in system prompt 2. Enabled enforceFinalTag=true which requires ALL responses to have tags 3. Stripped responses to empty strings when no tag found Gemini 2.0 doesn't natively use these tags, so all responses were stripped empty. Changes: - Remove 'google-gemini-cli' from isReasoningTagProvider() - Remove 'google-generative-ai' from isReasoningTagProvider() - Keep 'google-antigravity' (Gemini 3.0 DOES use reasoning tags) - Add clarifying comments about which providers natively use tags Impact: - ✅ Gemini 2.0 responses now display correctly - ✅ OpenAI errors now shown to users - ✅ Google Antigravity (Gemini 3.0) still works correctly - ✅ Ollama and Minimax unchanged --- src/utils/provider-utils.ts | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/utils/provider-utils.ts b/src/utils/provider-utils.ts index e29ffe1ab..25d468a7a 100644 --- a/src/utils/provider-utils.ts +++ b/src/utils/provider-utils.ts @@ -6,21 +6,22 @@ * Returns true if the provider requires reasoning to be wrapped in tags * (e.g. and ) in the text stream, rather than using native * API fields for reasoning/thinking. + * + * NOTE: Only include providers that NATIVELY use and tags. + * Standard Gemini 2.0 (google-gemini-cli, google-generative-ai) does NOT use + * these tags natively, but Google Antigravity (Gemini 3.0) does. */ export function isReasoningTagProvider(provider: string | undefined | null): boolean { if (!provider) return false; const normalized = provider.trim().toLowerCase(); // Check for exact matches or known prefixes/substrings for reasoning providers - if ( - normalized === "ollama" || - normalized === "google-gemini-cli" || - normalized === "google-generative-ai" - ) { + if (normalized === "ollama") { return true; } // Handle google-antigravity and its model variations (e.g. google-antigravity/gemini-3) + // This is Gemini 3.0 which DOES use reasoning tags natively. if (normalized.includes("google-antigravity")) { return true; } From d2c1fd3c61b621aa651a8fa369cfad7e47a7a853 Mon Sep 17 00:00:00 2001 From: spiceoogway Date: Fri, 30 Jan 2026 08:38:02 -0500 Subject: [PATCH 2/3] fix: improve device pairing error message for remote connections Fixes #4531 When accessing the Web UI from a different machine on the LAN, users were getting a cryptic 'disconnected (1008): pairing required' error with no indication of how to resolve it. Changes: 1. Enhanced server error message to include: - Clear explanation of the pairing requirement - Step-by-step CLI instructions (openclaw devices list/approve) - The specific request ID to approve - Note that approval must be done on the server machine - Alternative method (localhost Control UI) 2. Updated Web UI to propagate the full error message from the server instead of the generic 'connect failed' message Impact: - Non-breaking change (only improves error messaging) - Backwards compatible with existing clients - Significantly improves UX for LAN/remote access scenarios Before: Error: disconnected (1008): pairing required After: Error: Device pairing required. On the OpenClaw server machine, run: 'openclaw devices list' to see pending requests, then 'openclaw devices approve ' to approve this device. Alternatively, access the Control UI from the server's localhost to approve. --- .../server/ws-connection/message-handler.ts | 17 +++++++++++++++-- ui/src/ui/gateway.ts | 6 ++++-- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/src/gateway/server/ws-connection/message-handler.ts b/src/gateway/server/ws-connection/message-handler.ts index 948d6cefb..5c91c4eb9 100644 --- a/src/gateway/server/ws-connection/message-handler.ts +++ b/src/gateway/server/ws-connection/message-handler.ts @@ -664,12 +664,25 @@ export function attachGatewayWsMessageHandler(params: { requestId: pairing.request.requestId, reason, }); + const helpMessage = + `Device pairing required. ` + + `On the OpenClaw server machine, run: ` + + `'openclaw devices list' to see pending requests, then ` + + `'openclaw devices approve ${pairing.request.requestId}' to approve this device. ` + + `Alternatively, access the Control UI from the server's localhost to approve.`; send({ type: "res", id: frame.id, ok: false, - error: errorShape(ErrorCodes.NOT_PAIRED, "pairing required", { - details: { requestId: pairing.request.requestId }, + error: errorShape(ErrorCodes.NOT_PAIRED, helpMessage, { + details: { + requestId: pairing.request.requestId, + deviceId: device.id, + instructions: { + list: "openclaw devices list", + approve: `openclaw devices approve ${pairing.request.requestId}`, + } + }, }), }); close(1008, "pairing required"); diff --git a/ui/src/ui/gateway.ts b/ui/src/ui/gateway.ts index fc8dde08a..122df9fea 100644 --- a/ui/src/ui/gateway.ts +++ b/ui/src/ui/gateway.ts @@ -219,11 +219,13 @@ export class GatewayBrowserClient { this.backoffMs = 800; this.opts.onHello?.(hello); }) - .catch(() => { + .catch((err) => { if (canFallbackToShared && deviceIdentity) { clearDeviceAuthToken({ deviceId: deviceIdentity.deviceId, role }); } - this.ws?.close(CONNECT_FAILED_CLOSE_CODE, "connect failed"); + // Extract the error message to pass to onClose handler + const errorMessage = err instanceof Error ? err.message : "connect failed"; + this.ws?.close(CONNECT_FAILED_CLOSE_CODE, errorMessage); }); } From e31764fed19f41e7a0b72c4d1163b48433d176ff Mon Sep 17 00:00:00 2001 From: spiceoogway Date: Fri, 30 Jan 2026 08:42:22 -0500 Subject: [PATCH 3/3] fix: prevent multi-channel response routing race condition (#4530) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Problem When multiple channels (WhatsApp, iMessage, Telegram, etc.) are active, responses intended for one channel could leak to another channel if messages arrived simultaneously. This created serious privacy concerns. **Root Cause:** All channels were updating the SAME main session key with their delivery context (channel, to, accountId). When messages arrived concurrently: 1. WhatsApp message arrives → updates session['agent:main:main'].lastChannel = 'whatsapp' 2. Agent starts processing (takes time) 3. iMessage message arrives → OVERWRITES session['agent:main:main'].lastChannel = 'imessage' 4. WhatsApp response delivers → reads session lastChannel = 'imessage' 5. Response goes to wrong channel! ## Solution Changed all channel monitors to use the channel-specific session key (route.sessionKey) instead of the shared main session key (route.mainSessionKey) when updating delivery context. Now each channel updates its own isolated session: - WhatsApp: session['agent:main:whatsapp:dm:+1234'] - iMessage: session['agent:main:imessage:dm:alice'] - Telegram: session['agent:main:telegram:dm:12345'] This prevents cross-channel state clobbering. ## Changes - src/discord/monitor/message-handler.process.ts - src/imessage/monitor/monitor-provider.ts - src/line/bot-message-context.ts - src/signal/monitor/event-handler.ts - src/slack/monitor/message-handler/dispatch.ts - src/slack/monitor/message-handler/prepare.ts - src/telegram/bot-message-context.ts - src/web/auto-reply/monitor/process-message.ts All changed from: sessionKey: route.mainSessionKey To: sessionKey: route.sessionKey ## Testing Manual testing needed: 1. Configure 2+ channels (e.g., WhatsApp + iMessage) 2. Send message to channel A 3. Immediately send message to channel B (within 100ms) 4. Verify responses go to correct channels Fixes #4530 --- src/discord/monitor/message-handler.process.ts | 2 +- src/imessage/monitor/monitor-provider.ts | 2 +- src/line/bot-message-context.ts | 4 ++-- src/signal/monitor/event-handler.ts | 2 +- src/slack/monitor/message-handler/dispatch.ts | 2 +- src/slack/monitor/message-handler/prepare.ts | 2 +- src/telegram/bot-message-context.ts | 2 +- src/web/auto-reply/monitor/process-message.ts | 2 +- 8 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/discord/monitor/message-handler.process.ts b/src/discord/monitor/message-handler.process.ts index 6d502be21..e9901d07e 100644 --- a/src/discord/monitor/message-handler.process.ts +++ b/src/discord/monitor/message-handler.process.ts @@ -302,7 +302,7 @@ export async function processDiscordMessage(ctx: DiscordMessagePreflightContext) ctx: ctxPayload, updateLastRoute: isDirectMessage ? { - sessionKey: route.mainSessionKey, + sessionKey: route.sessionKey, channel: "discord", to: `user:${author.id}`, accountId: route.accountId, diff --git a/src/imessage/monitor/monitor-provider.ts b/src/imessage/monitor/monitor-provider.ts index 491071699..05e46436d 100644 --- a/src/imessage/monitor/monitor-provider.ts +++ b/src/imessage/monitor/monitor-provider.ts @@ -510,7 +510,7 @@ export async function monitorIMessageProvider(opts: MonitorIMessageOpts = {}): P updateLastRoute: !isGroup && updateTarget ? { - sessionKey: route.mainSessionKey, + sessionKey: route.sessionKey, channel: "imessage", to: updateTarget, accountId: route.accountId, diff --git a/src/line/bot-message-context.ts b/src/line/bot-message-context.ts index 8a1c049d2..77b6264b5 100644 --- a/src/line/bot-message-context.ts +++ b/src/line/bot-message-context.ts @@ -282,7 +282,7 @@ export async function buildLineMessageContext(params: BuildLineMessageContextPar if (!isGroup) { await updateLastRoute({ storePath, - sessionKey: route.mainSessionKey, + sessionKey: route.sessionKey, deliveryContext: { channel: "line", to: userId ?? peerId, @@ -432,7 +432,7 @@ export async function buildLinePostbackContext(params: { if (!isGroup) { await updateLastRoute({ storePath, - sessionKey: route.mainSessionKey, + sessionKey: route.sessionKey, deliveryContext: { channel: "line", to: userId ?? peerId, diff --git a/src/signal/monitor/event-handler.ts b/src/signal/monitor/event-handler.ts index 72195ff78..eb45e1dd2 100644 --- a/src/signal/monitor/event-handler.ts +++ b/src/signal/monitor/event-handler.ts @@ -156,7 +156,7 @@ export function createSignalEventHandler(deps: SignalEventHandlerDeps) { ctx: ctxPayload, updateLastRoute: !entry.isGroup ? { - sessionKey: route.mainSessionKey, + sessionKey: route.sessionKey, channel: "signal", to: entry.senderRecipient, accountId: route.accountId, diff --git a/src/slack/monitor/message-handler/dispatch.ts b/src/slack/monitor/message-handler/dispatch.ts index 38b69f049..a0c819638 100644 --- a/src/slack/monitor/message-handler/dispatch.ts +++ b/src/slack/monitor/message-handler/dispatch.ts @@ -27,7 +27,7 @@ export async function dispatchPreparedSlackMessage(prepared: PreparedSlackMessag }); await updateLastRoute({ storePath, - sessionKey: route.mainSessionKey, + sessionKey: route.sessionKey, deliveryContext: { channel: "slack", to: `user:${message.user}`, diff --git a/src/slack/monitor/message-handler/prepare.ts b/src/slack/monitor/message-handler/prepare.ts index 8a2a9e111..02fc19cae 100644 --- a/src/slack/monitor/message-handler/prepare.ts +++ b/src/slack/monitor/message-handler/prepare.ts @@ -533,7 +533,7 @@ export async function prepareSlackMessage(params: { ctx: ctxPayload, updateLastRoute: isDirectMessage ? { - sessionKey: route.mainSessionKey, + sessionKey: route.sessionKey, channel: "slack", to: `user:${message.user}`, accountId: route.accountId, diff --git a/src/telegram/bot-message-context.ts b/src/telegram/bot-message-context.ts index 9696e4f1b..41188df50 100644 --- a/src/telegram/bot-message-context.ts +++ b/src/telegram/bot-message-context.ts @@ -617,7 +617,7 @@ export const buildTelegramMessageContext = async ({ ctx: ctxPayload, updateLastRoute: !isGroup ? { - sessionKey: route.mainSessionKey, + sessionKey: route.sessionKey, channel: "telegram", to: String(chatId), accountId: route.accountId, diff --git a/src/web/auto-reply/monitor/process-message.ts b/src/web/auto-reply/monitor/process-message.ts index 27b638cfb..af8feaa05 100644 --- a/src/web/auto-reply/monitor/process-message.ts +++ b/src/web/auto-reply/monitor/process-message.ts @@ -295,7 +295,7 @@ export async function processMessage(params: { cfg: params.cfg, backgroundTasks: params.backgroundTasks, storeAgentId: params.route.agentId, - sessionKey: params.route.mainSessionKey, + sessionKey: params.route.sessionKey, channel: "whatsapp", to: dmRouteTarget, accountId: params.route.accountId,