[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
This commit is contained in:
xydt-tanshanshan
2026-06-16 13:15:08 +08:00
committed by GitHub
parent 7ac2bbaaf0
commit a2bc7ab269
2 changed files with 27 additions and 1 deletions

View File

@@ -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", () => {

View File

@@ -734,7 +734,7 @@ export async function handleFeishuMessage(params: {
try {
const core = {
channel: channelRuntime ?? getFeishuRuntime().channel,
channel: channelRuntime?.inbound ? channelRuntime : getFeishuRuntime().channel,
} as ReturnType<typeof getFeishuRuntime>;
const pairing = createChannelPairingController({
core,