mirror of
https://github.com/openclaw/openclaw.git
synced 2026-03-12 15:30:39 +00:00
fix(session): preserve external lastTo routing for internal turns
This commit is contained in:
committed by
Peter Steinberger
parent
0fa5d6ed2e
commit
95db5bb5e8
@@ -1474,13 +1474,50 @@ describe("initSessionState internal channel routing preservation", () => {
|
||||
Body: "internal follow-up",
|
||||
SessionKey: sessionKey,
|
||||
OriginatingChannel: "webchat",
|
||||
OriginatingTo: "session:dashboard",
|
||||
},
|
||||
cfg,
|
||||
commandAuthorized: true,
|
||||
});
|
||||
|
||||
expect(result.sessionEntry.lastChannel).toBe("telegram");
|
||||
expect(result.sessionEntry.lastTo).toBe("group:12345");
|
||||
expect(result.sessionEntry.deliveryContext?.channel).toBe("telegram");
|
||||
expect(result.sessionEntry.deliveryContext?.to).toBe("group:12345");
|
||||
});
|
||||
|
||||
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";
|
||||
await saveSessionStore(storePath, {
|
||||
[sessionKey]: {
|
||||
sessionId: "sess-2",
|
||||
updatedAt: Date.now(),
|
||||
lastChannel: "discord",
|
||||
lastTo: "channel:24680",
|
||||
deliveryContext: {
|
||||
channel: "discord",
|
||||
to: "channel:24680",
|
||||
},
|
||||
},
|
||||
});
|
||||
const cfg = { session: { store: storePath } } as OpenClawConfig;
|
||||
|
||||
const result = await initSessionState({
|
||||
ctx: {
|
||||
Body: "internal handoff",
|
||||
SessionKey: sessionKey,
|
||||
OriginatingChannel: "sessions_send",
|
||||
OriginatingTo: "session:handoff",
|
||||
},
|
||||
cfg,
|
||||
commandAuthorized: true,
|
||||
});
|
||||
|
||||
expect(result.sessionEntry.lastChannel).toBe("discord");
|
||||
expect(result.sessionEntry.lastTo).toBe("channel:24680");
|
||||
expect(result.sessionEntry.deliveryContext?.channel).toBe("discord");
|
||||
expect(result.sessionEntry.deliveryContext?.to).toBe("channel:24680");
|
||||
});
|
||||
|
||||
it("uses session key channel hint when first turn is internal webchat", async () => {
|
||||
|
||||
@@ -62,6 +62,12 @@ function resolveSessionKeyChannelHint(sessionKey?: string): string | undefined {
|
||||
return normalizeMessageChannel(head);
|
||||
}
|
||||
|
||||
function isExternalRoutingChannel(channel?: string): channel is string {
|
||||
return Boolean(
|
||||
channel && channel !== INTERNAL_MESSAGE_CHANNEL && isDeliverableMessageChannel(channel),
|
||||
);
|
||||
}
|
||||
|
||||
function resolveLastChannelRaw(params: {
|
||||
originatingChannelRaw?: string;
|
||||
persistedLastChannel?: string;
|
||||
@@ -71,26 +77,44 @@ function resolveLastChannelRaw(params: {
|
||||
const persistedChannel = normalizeMessageChannel(params.persistedLastChannel);
|
||||
const sessionKeyChannelHint = resolveSessionKeyChannelHint(params.sessionKey);
|
||||
let resolved = params.originatingChannelRaw || params.persistedLastChannel;
|
||||
// Internal webchat/system turns should not overwrite previously known external
|
||||
// delivery routes (or explicit channel hints encoded in the session key).
|
||||
if (originatingChannel === INTERNAL_MESSAGE_CHANNEL) {
|
||||
if (
|
||||
persistedChannel &&
|
||||
persistedChannel !== INTERNAL_MESSAGE_CHANNEL &&
|
||||
isDeliverableMessageChannel(persistedChannel)
|
||||
) {
|
||||
// Internal/non-deliverable sources should not overwrite previously known
|
||||
// external delivery routes (or explicit channel hints from the session key).
|
||||
if (!isExternalRoutingChannel(originatingChannel)) {
|
||||
if (isExternalRoutingChannel(persistedChannel)) {
|
||||
resolved = persistedChannel;
|
||||
} else if (
|
||||
sessionKeyChannelHint &&
|
||||
sessionKeyChannelHint !== INTERNAL_MESSAGE_CHANNEL &&
|
||||
isDeliverableMessageChannel(sessionKeyChannelHint)
|
||||
) {
|
||||
} else if (isExternalRoutingChannel(sessionKeyChannelHint)) {
|
||||
resolved = sessionKeyChannelHint;
|
||||
}
|
||||
}
|
||||
return resolved;
|
||||
}
|
||||
|
||||
function resolveLastToRaw(params: {
|
||||
originatingChannelRaw?: string;
|
||||
originatingToRaw?: string;
|
||||
toRaw?: string;
|
||||
persistedLastTo?: string;
|
||||
persistedLastChannel?: string;
|
||||
sessionKey?: string;
|
||||
}): string | undefined {
|
||||
const originatingChannel = normalizeMessageChannel(params.originatingChannelRaw);
|
||||
const persistedChannel = normalizeMessageChannel(params.persistedLastChannel);
|
||||
const sessionKeyChannelHint = resolveSessionKeyChannelHint(params.sessionKey);
|
||||
|
||||
// When the turn originates from an internal/non-deliverable source, do not
|
||||
// replace an established external destination with internal routing ids
|
||||
// (e.g., session/webchat ids).
|
||||
if (!isExternalRoutingChannel(originatingChannel)) {
|
||||
const hasExternalFallback =
|
||||
isExternalRoutingChannel(persistedChannel) || isExternalRoutingChannel(sessionKeyChannelHint);
|
||||
if (hasExternalFallback && params.persistedLastTo) {
|
||||
return params.persistedLastTo;
|
||||
}
|
||||
}
|
||||
|
||||
return params.originatingToRaw || params.toRaw || params.persistedLastTo;
|
||||
}
|
||||
|
||||
export type SessionInitResult = {
|
||||
sessionCtx: TemplateContext;
|
||||
sessionEntry: SessionEntry;
|
||||
@@ -422,7 +446,14 @@ export async function initSessionState(params: {
|
||||
persistedLastChannel: baseEntry?.lastChannel,
|
||||
sessionKey,
|
||||
});
|
||||
const lastToRaw = ctx.OriginatingTo || ctx.To || baseEntry?.lastTo;
|
||||
const lastToRaw = resolveLastToRaw({
|
||||
originatingChannelRaw,
|
||||
originatingToRaw: ctx.OriginatingTo,
|
||||
toRaw: ctx.To,
|
||||
persistedLastTo: baseEntry?.lastTo,
|
||||
persistedLastChannel: baseEntry?.lastChannel,
|
||||
sessionKey,
|
||||
});
|
||||
const lastAccountIdRaw = ctx.AccountId || baseEntry?.lastAccountId;
|
||||
// Only fall back to persisted threadId for thread sessions. Non-thread
|
||||
// sessions (e.g. DM without topics) must not inherit a stale threadId from a
|
||||
|
||||
Reference in New Issue
Block a user