[codex] Fix main-session web UI reply routing to Telegram (openclaw#29328) thanks @BeeSting50

Verified:
- pnpm test src/auto-reply/reply/dispatch-from-config.test.ts src/gateway/server-methods/chat.directive-tags.test.ts
- pnpm exec oxfmt --check src/auto-reply/reply/dispatch-from-config.test.ts src/gateway/server-methods/chat.directive-tags.test.ts src/auto-reply/reply/dispatch-from-config.ts src/gateway/server-methods/chat.ts CHANGELOG.md
- CI note: non-required check "check" failed on unrelated src/slack/monitor/events/messages.ts TS errors outside this PR scope.

Co-authored-by: BeeSting50 <85285887+BeeSting50@users.noreply.github.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
This commit is contained in:
Keenan
2026-03-02 06:54:16 -07:00
committed by GitHub
parent 99ee26d534
commit 050e928985
5 changed files with 175 additions and 6 deletions

View File

@@ -77,7 +77,9 @@ vi.mock("./route-reply.js", () => ({
isRoutableChannel: (channel: string | undefined) =>
Boolean(
channel &&
["telegram", "slack", "discord", "signal", "imessage", "whatsapp"].includes(channel),
["telegram", "slack", "discord", "signal", "imessage", "whatsapp", "feishu"].includes(
channel,
),
),
routeReply: mocks.routeReply,
}));
@@ -327,6 +329,73 @@ describe("dispatchReplyFromConfig", () => {
await dispatchReplyFromConfig({ ctx, cfg, dispatcher, replyResolver });
});
it("routes when provider is webchat but surface carries originating channel metadata", async () => {
setNoAbort();
mocks.routeReply.mockClear();
const cfg = emptyConfig;
const dispatcher = createDispatcher();
const ctx = buildTestCtx({
Provider: "webchat",
Surface: "telegram",
OriginatingChannel: "telegram",
OriginatingTo: "telegram:999",
});
const replyResolver = async () => ({ text: "hi" }) satisfies ReplyPayload;
await dispatchReplyFromConfig({ ctx, cfg, dispatcher, replyResolver });
expect(dispatcher.sendFinalReply).not.toHaveBeenCalled();
expect(mocks.routeReply).toHaveBeenCalledWith(
expect.objectContaining({
channel: "telegram",
to: "telegram:999",
}),
);
});
it("routes Feishu replies when provider is webchat and origin metadata points to Feishu", async () => {
setNoAbort();
mocks.routeReply.mockClear();
const cfg = emptyConfig;
const dispatcher = createDispatcher();
const ctx = buildTestCtx({
Provider: "webchat",
Surface: "feishu",
OriginatingChannel: "feishu",
OriginatingTo: "ou_feishu_direct_123",
});
const replyResolver = async () => ({ text: "hi" }) satisfies ReplyPayload;
await dispatchReplyFromConfig({ ctx, cfg, dispatcher, replyResolver });
expect(dispatcher.sendFinalReply).not.toHaveBeenCalled();
expect(mocks.routeReply).toHaveBeenCalledWith(
expect.objectContaining({
channel: "feishu",
to: "ou_feishu_direct_123",
}),
);
});
it("does not route when provider already matches originating channel", async () => {
setNoAbort();
mocks.routeReply.mockClear();
const cfg = emptyConfig;
const dispatcher = createDispatcher();
const ctx = buildTestCtx({
Provider: "telegram",
Surface: "webchat",
OriginatingChannel: "telegram",
OriginatingTo: "telegram:999",
});
const replyResolver = async () => ({ text: "hi" }) satisfies ReplyPayload;
await dispatchReplyFromConfig({ ctx, cfg, dispatcher, replyResolver });
expect(mocks.routeReply).not.toHaveBeenCalled();
expect(dispatcher.sendFinalReply).toHaveBeenCalledTimes(1);
});
it("routes media-only tool results when summaries are suppressed", async () => {
setNoAbort();
mocks.routeReply.mockClear();