fix(session): prefer webchat routes for direct ui turns (#37135)

This commit is contained in:
Frank Yang
2026-03-06 14:14:13 +08:00
committed by GitHub
parent 777af476cb
commit 5fdcef7cbe
2 changed files with 52 additions and 3 deletions

View File

@@ -1,6 +1,6 @@
import type { SessionEntry } from "../../config/sessions.js";
import { buildAgentMainSessionKey } from "../../routing/session-key.js";
import { parseAgentSessionKey } from "../../sessions/session-key-utils.js";
import { deriveSessionChatType, parseAgentSessionKey } from "../../sessions/session-key-utils.js";
import {
deliveryContextFromSession,
deliveryContextKey,
@@ -38,6 +38,10 @@ function isMainSessionKey(sessionKey?: string): boolean {
return parsed.rest.trim().toLowerCase() === "main";
}
function isDirectSessionKey(sessionKey?: string): boolean {
return deriveSessionChatType(sessionKey) === "direct";
}
function isExternalRoutingChannel(channel?: string): channel is string {
return Boolean(
channel && channel !== INTERNAL_MESSAGE_CHANNEL && isDeliverableMessageChannel(channel),
@@ -50,7 +54,12 @@ export function resolveLastChannelRaw(params: {
sessionKey?: string;
}): string | undefined {
const originatingChannel = normalizeMessageChannel(params.originatingChannelRaw);
if (originatingChannel === INTERNAL_MESSAGE_CHANNEL && isMainSessionKey(params.sessionKey)) {
// WebChat should own reply routing for direct-session UI turns, even when the
// session previously replied through an external channel like iMessage.
if (
originatingChannel === INTERNAL_MESSAGE_CHANNEL &&
(isMainSessionKey(params.sessionKey) || isDirectSessionKey(params.sessionKey))
) {
return params.originatingChannelRaw;
}
const persistedChannel = normalizeMessageChannel(params.persistedLastChannel);
@@ -77,7 +86,10 @@ export function resolveLastToRaw(params: {
sessionKey?: string;
}): string | undefined {
const originatingChannel = normalizeMessageChannel(params.originatingChannelRaw);
if (originatingChannel === INTERNAL_MESSAGE_CHANNEL && isMainSessionKey(params.sessionKey)) {
if (
originatingChannel === INTERNAL_MESSAGE_CHANNEL &&
(isMainSessionKey(params.sessionKey) || isDirectSessionKey(params.sessionKey))
) {
return params.originatingToRaw || params.toRaw;
}
const persistedChannel = normalizeMessageChannel(params.persistedLastChannel);

View File

@@ -1926,6 +1926,43 @@ describe("initSessionState internal channel routing preservation", () => {
expect(result.sessionEntry.deliveryContext?.to).toBe("group:12345");
});
it("lets direct webchat turns override persisted external routes for per-channel-peer sessions", async () => {
const storePath = await createStorePath("webchat-direct-route-override-");
const sessionKey = "agent:main:imessage:direct:+1555";
await writeSessionStoreFast(storePath, {
[sessionKey]: {
sessionId: "sess-webchat-direct",
updatedAt: Date.now(),
lastChannel: "imessage",
lastTo: "+1555",
deliveryContext: {
channel: "imessage",
to: "+1555",
},
},
});
const cfg = {
session: { store: storePath, dmScope: "per-channel-peer" },
} as OpenClawConfig;
const result = await initSessionState({
ctx: {
Body: "reply from control ui",
SessionKey: sessionKey,
OriginatingChannel: "webchat",
OriginatingTo: "session:dashboard",
Surface: "webchat",
},
cfg,
commandAuthorized: true,
});
expect(result.sessionEntry.lastChannel).toBe("webchat");
expect(result.sessionEntry.lastTo).toBe("session:dashboard");
expect(result.sessionEntry.deliveryContext?.channel).toBe("webchat");
expect(result.sessionEntry.deliveryContext?.to).toBe("session:dashboard");
});
it("keeps persisted external route when OriginatingChannel is non-deliverable", async () => {
const storePath = await createStorePath("preserve-nondeliverable-route-");
const sessionKey = "agent:main:discord:channel:24680";