mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 20:41:37 +00:00
Fix Control UI duplicate iMessage replies for internal webchat turns (#36151)
* Auto-reply: avoid routing external replies from internal webchat turns * Auto-reply tests: cover internal webchat non-routing with external origin metadata * Changelog: add Control UI iMessage duplicate-reply fix note * Auto-reply context: track explicit deliver routes * Gateway chat: mark explicit external deliver routes in context * Auto-reply: preserve explicit deliver routes for internal webchat turns * Auto-reply tests: cover explicit deliver routes from internal webchat turns * Gateway chat tests: assert explicit deliver route context tagging
This commit is contained in:
@@ -399,6 +399,58 @@ describe("dispatchReplyFromConfig", () => {
|
||||
expect(dispatcher.sendFinalReply).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("does not route external origin replies when current surface is internal webchat without explicit delivery", async () => {
|
||||
setNoAbort();
|
||||
mocks.routeReply.mockClear();
|
||||
const cfg = emptyConfig;
|
||||
const dispatcher = createDispatcher();
|
||||
const ctx = buildTestCtx({
|
||||
Provider: "webchat",
|
||||
Surface: "webchat",
|
||||
OriginatingChannel: "imessage",
|
||||
OriginatingTo: "imessage:+15550001111",
|
||||
});
|
||||
|
||||
const replyResolver = async (
|
||||
_ctx: MsgContext,
|
||||
_opts?: GetReplyOptions,
|
||||
_cfg?: OpenClawConfig,
|
||||
) => ({ text: "hi" }) satisfies ReplyPayload;
|
||||
await dispatchReplyFromConfig({ ctx, cfg, dispatcher, replyResolver });
|
||||
|
||||
expect(mocks.routeReply).not.toHaveBeenCalled();
|
||||
expect(dispatcher.sendFinalReply).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("routes external origin replies for internal webchat turns when explicit delivery is set", async () => {
|
||||
setNoAbort();
|
||||
mocks.routeReply.mockClear();
|
||||
const cfg = emptyConfig;
|
||||
const dispatcher = createDispatcher();
|
||||
const ctx = buildTestCtx({
|
||||
Provider: "webchat",
|
||||
Surface: "webchat",
|
||||
OriginatingChannel: "imessage",
|
||||
OriginatingTo: "imessage:+15550001111",
|
||||
ExplicitDeliverRoute: true,
|
||||
});
|
||||
|
||||
const replyResolver = async (
|
||||
_ctx: MsgContext,
|
||||
_opts?: GetReplyOptions,
|
||||
_cfg?: OpenClawConfig,
|
||||
) => ({ text: "hi" }) satisfies ReplyPayload;
|
||||
await dispatchReplyFromConfig({ ctx, cfg, dispatcher, replyResolver });
|
||||
|
||||
expect(dispatcher.sendFinalReply).not.toHaveBeenCalled();
|
||||
expect(mocks.routeReply).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
channel: "imessage",
|
||||
to: "imessage:+15550001111",
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("routes media-only tool results when summaries are suppressed", async () => {
|
||||
setNoAbort();
|
||||
mocks.routeReply.mockClear();
|
||||
|
||||
@@ -215,8 +215,15 @@ export async function dispatchReplyFromConfig(params: {
|
||||
const surfaceChannel = normalizeMessageChannel(ctx.Surface);
|
||||
// Prefer provider channel because surface may carry origin metadata in relayed flows.
|
||||
const currentSurface = providerChannel ?? surfaceChannel;
|
||||
const isInternalWebchatTurn =
|
||||
currentSurface === INTERNAL_MESSAGE_CHANNEL &&
|
||||
(surfaceChannel === INTERNAL_MESSAGE_CHANNEL || !surfaceChannel) &&
|
||||
ctx.ExplicitDeliverRoute !== true;
|
||||
const shouldRouteToOriginating = Boolean(
|
||||
isRoutableChannel(originatingChannel) && originatingTo && originatingChannel !== currentSurface,
|
||||
!isInternalWebchatTurn &&
|
||||
isRoutableChannel(originatingChannel) &&
|
||||
originatingTo &&
|
||||
originatingChannel !== currentSurface,
|
||||
);
|
||||
const shouldSuppressTyping =
|
||||
shouldRouteToOriginating || originatingChannel === INTERNAL_MESSAGE_CHANNEL;
|
||||
|
||||
Reference in New Issue
Block a user