From a2bc7ab269cccfc28d3c6e49b45d0f6efaef892f Mon Sep 17 00:00:00 2001 From: xydt-tanshanshan <0668000363@xydigit.com> Date: Tue, 16 Jun 2026 13:15:08 +0800 Subject: [PATCH] [AI] fix(feishu): guard against missing inbound in channelRuntime fallback (#93466) * [AI] fix(feishu): guard against missing inbound in channelRuntime fallback When channelRuntime from gateway context is truthy but lacks the inbound property, the ?? operator still selects it over getFeishuRuntime().channel, causing TypeError at core.channel.inbound.run(). The ChannelGatewayContext types channelRuntime as ChannelRuntimeSurface (only guarantees runtimeContexts), but channel.ts casts it to PluginRuntimeChannel via type assertion. If a partial runtime object without inbound is provided, the type lie becomes a runtime crash. Fix: check channelRuntime?.inbound before using it; fall back to getFeishuRuntime().channel when inbound is absent. Related to #93453 * [AI] test(feishu): add regression for partial channelRuntime lacking inbound When channelRuntime has runtimeContexts but no inbound, the guard in bot.ts should fall back to getFeishuRuntime().channel. Add a test that passes a partial channelRuntime and verifies dispatch does not crash. Refs #93453 --- extensions/feishu/src/bot.test.ts | 26 ++++++++++++++++++++++++++ extensions/feishu/src/bot.ts | 2 +- 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/extensions/feishu/src/bot.test.ts b/extensions/feishu/src/bot.test.ts index b432f0465fc..fdae97b07bb 100644 --- a/extensions/feishu/src/bot.test.ts +++ b/extensions/feishu/src/bot.test.ts @@ -422,6 +422,7 @@ async function dispatchMessage(params: { cfg: ClawdbotConfig; currentCfg?: ClawdbotConfig; event: FeishuMessageEvent; + channelRuntime?: PluginRuntime["channel"]; }) { const runtime = createRuntimeEnv(); const feishuConfig = params.cfg.channels?.feishu; @@ -443,6 +444,7 @@ async function dispatchMessage(params: { cfg, event: params.event, runtime, + channelRuntime: params.channelRuntime, }); return runtime; } @@ -960,6 +962,30 @@ describe("handleFeishuMessage ACP routing", () => { ); expect(dispatcherOptions.allowReasoningPreview).toBe(true); }); + + it("falls back to full runtime channel when partial channelRuntime lacks inbound", async () => { + const partialChannelRuntime = { + runtimeContexts: {} as PluginRuntime["channel"]["runtimeContexts"], + } as PluginRuntime["channel"]; + + await dispatchMessage({ + cfg: { + session: { mainKey: "main", scope: "per-sender" }, + channels: { feishu: { enabled: true, allowFrom: ["ou_sender_1"], dmPolicy: "open" } }, + }, + event: { + sender: { sender_id: { open_id: "ou_sender_1" } }, + message: { + message_id: "msg-partial-runtime", + chat_id: "oc_dm", + chat_type: "p2p", + message_type: "text", + content: JSON.stringify({ text: "hello" }), + }, + }, + channelRuntime: partialChannelRuntime, + }); + }); }); describe("handleFeishuMessage command authorization", () => { diff --git a/extensions/feishu/src/bot.ts b/extensions/feishu/src/bot.ts index b1b216618c2..e7180b53944 100644 --- a/extensions/feishu/src/bot.ts +++ b/extensions/feishu/src/bot.ts @@ -734,7 +734,7 @@ export async function handleFeishuMessage(params: { try { const core = { - channel: channelRuntime ?? getFeishuRuntime().channel, + channel: channelRuntime?.inbound ? channelRuntime : getFeishuRuntime().channel, } as ReturnType; const pairing = createChannelPairingController({ core,