From 740bb77c8c028995a10f8e889f00637ff208ebc1 Mon Sep 17 00:00:00 2001 From: liuxiaopai-ai <73659136+liuxiaopai-ai@users.noreply.github.com> Date: Tue, 3 Mar 2026 00:14:59 +0800 Subject: [PATCH] fix(reply): prefer provider over surface for run channel fallback --- CHANGELOG.md | 1 + .../reply/get-reply-run.media-only.test.ts | 31 +++++++++++++++++++ src/auto-reply/reply/get-reply-run.ts | 5 ++- 3 files changed, 36 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index de02257a5..69f0b3ad1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -53,6 +53,7 @@ Docs: https://docs.openclaw.ai - Skills/sherpa-onnx-tts: run the `sherpa-onnx-tts` bin under ESM (replace CommonJS `require` imports) and add regression coverage to prevent `require is not defined in ES module scope` startup crashes. (#31965) Thanks @bmendonca3. - Browser/default profile selection: default `browser.defaultProfile` behavior now prefers `openclaw` (managed standalone CDP) when no explicit default is configured, while still auto-provisioning the `chrome` relay profile for explicit opt-in use. (#32031) Fixes #31907. Thanks @liuxiaopai-ai. - Doctor/local memory provider checks: stop false-positive local-provider warnings when `provider=local` and no explicit `modelPath` is set by honoring default local model fallback while still warning when gateway probe reports local embeddings not ready. (#32014) Fixes #31998. Thanks @adhishthite. +- Feishu/Run channel fallback: prefer `Provider` over `Surface` when inferring queued run `messageProvider` fallback (when `OriginatingChannel` is missing), preventing Feishu turns from being mislabeled as `webchat` in mixed relay metadata contexts. (#31880) Fixes #31859. Thanks @liuxiaopai-ai. - Sandbox/Docker setup command parsing: accept `agents.*.sandbox.docker.setupCommand` as either a string or a string array, and normalize arrays to newline-delimited shell scripts so multi-step setup commands no longer concatenate without separators. (#31953) Thanks @liuxiaopai-ai. - Gateway/Plugin HTTP route precedence: run explicit plugin HTTP routes before the Control UI SPA catch-all so registered plugin webhook/custom paths remain reachable, while unmatched paths still fall through to Control UI handling. (#31885) Thanks @Sid-Qin. - Security/Node exec approvals: preserve shell/dispatch-wrapper argv semantics during approval hardening so approved wrapper commands (for example `env sh -c ...`) cannot drift into a different runtime command shape, and add regression coverage for both approval-plan generation and approved runtime execution paths. Thanks @tdjackey for reporting. diff --git a/src/auto-reply/reply/get-reply-run.media-only.test.ts b/src/auto-reply/reply/get-reply-run.media-only.test.ts index 6105613d6..4e1c28f71 100644 --- a/src/auto-reply/reply/get-reply-run.media-only.test.ts +++ b/src/auto-reply/reply/get-reply-run.media-only.test.ts @@ -281,6 +281,37 @@ describe("runPreparedReply media-only handling", () => { expect(call?.followupRun.run.messageProvider).toBe("webchat"); }); + it("prefers Provider over Surface when origin channel is missing", async () => { + await runPreparedReply( + baseParams({ + ctx: { + Body: "", + RawBody: "", + CommandBody: "", + ThreadHistoryBody: "Earlier message in this thread", + OriginatingChannel: undefined, + OriginatingTo: undefined, + Provider: "feishu", + Surface: "webchat", + ChatType: "group", + }, + sessionCtx: { + Body: "", + BodyStripped: "", + ThreadHistoryBody: "Earlier message in this thread", + MediaPath: "/tmp/input.png", + Provider: "webchat", + ChatType: "group", + OriginatingChannel: undefined, + OriginatingTo: undefined, + }, + }), + ); + + const call = vi.mocked(runReplyAgent).mock.calls[0]?.[0]; + expect(call?.followupRun.run.messageProvider).toBe("feishu"); + }); + it("passes suppressTyping through typing mode resolution", async () => { await runPreparedReply( baseParams({ diff --git a/src/auto-reply/reply/get-reply-run.ts b/src/auto-reply/reply/get-reply-run.ts index b54115d10..3c4698756 100644 --- a/src/auto-reply/reply/get-reply-run.ts +++ b/src/auto-reply/reply/get-reply-run.ts @@ -477,7 +477,10 @@ export async function runPreparedReply( sessionKey, messageProvider: resolveOriginMessageProvider({ originatingChannel: ctx.OriginatingChannel ?? sessionCtx.OriginatingChannel, - provider: ctx.Surface ?? ctx.Provider ?? sessionCtx.Provider, + // Prefer Provider over Surface for fallback channel identity. + // Surface can carry relayed metadata (for example "webchat") while Provider + // still reflects the active channel that should own tool routing. + provider: ctx.Provider ?? ctx.Surface ?? sessionCtx.Provider, }), agentAccountId: sessionCtx.AccountId, groupId: resolveGroupSessionKey(sessionCtx)?.id ?? undefined,