mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-26 20:35:39 -06:00
fix(agents): keep merged delivery routes account-bound (#98240)
* fix(agents): keep merged delivery routes account-bound mergeDeliveryContext gated route-field crossing on channel only, so a completion origin that knew its account but not a concrete target inherited a different account's to/threadId on the same channel. A subagent, cron, or media completion for bot-a could be addressed to bot-b's chat but sent through bot-a (cross-account misroute) or dropped. This restores the account-bound guard added in1ed8592467and removed as collateral by025db6cf9e(PR #89949); same-account and missing-account merges still backfill so the media route-pin path is preserved. Restores the deleted regression test. * fix(agents): centralize account-bound completion routes --------- Co-authored-by: Peter Steinberger <steipete@golden-gate.local>
This commit is contained in:
@@ -229,7 +229,7 @@ export function deliveryContextFromSession(
|
||||
return normalizeSessionDeliveryFields(source).deliveryContext;
|
||||
}
|
||||
|
||||
/** Merges delivery contexts without mixing target/account/thread fields across channels. */
|
||||
/** Merges delivery contexts without mixing target/account/thread fields across route owners. */
|
||||
export function mergeDeliveryContext(
|
||||
primary?: DeliveryContext,
|
||||
fallback?: DeliveryContext,
|
||||
@@ -243,17 +243,22 @@ export function mergeDeliveryContext(
|
||||
normalizedPrimary?.channel &&
|
||||
normalizedFallback?.channel &&
|
||||
normalizedPrimary.channel !== normalizedFallback.channel;
|
||||
const accountsConflict =
|
||||
normalizedPrimary?.accountId &&
|
||||
normalizedFallback?.accountId &&
|
||||
normalizedPrimary.accountId !== normalizedFallback.accountId;
|
||||
const routesConflict = channelsConflict || accountsConflict;
|
||||
return normalizeDeliveryContext({
|
||||
channel: normalizedPrimary?.channel ?? normalizedFallback?.channel,
|
||||
// Keep route fields paired to their channel; avoid crossing fields between
|
||||
// unrelated channels during session context merges.
|
||||
to: channelsConflict
|
||||
? normalizedPrimary?.to
|
||||
: (normalizedPrimary?.to ?? normalizedFallback?.to),
|
||||
accountId: channelsConflict
|
||||
channel: accountsConflict
|
||||
? normalizedPrimary?.channel
|
||||
: (normalizedPrimary?.channel ?? normalizedFallback?.channel),
|
||||
// Keep route fields paired to their channel account; crossing either owner
|
||||
// can address one account's target through another account's credentials.
|
||||
to: routesConflict ? normalizedPrimary?.to : (normalizedPrimary?.to ?? normalizedFallback?.to),
|
||||
accountId: routesConflict
|
||||
? normalizedPrimary?.accountId
|
||||
: (normalizedPrimary?.accountId ?? normalizedFallback?.accountId),
|
||||
threadId: channelsConflict
|
||||
threadId: routesConflict
|
||||
? normalizedPrimary?.threadId
|
||||
: (normalizedPrimary?.threadId ?? normalizedFallback?.threadId),
|
||||
});
|
||||
|
||||
@@ -53,6 +53,31 @@ describe("delivery context helpers", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("does not inherit route fields from a different account on the same channel", () => {
|
||||
const merged = mergeDeliveryContext(
|
||||
{ channel: "telegram", accountId: "bot-a" },
|
||||
{ channel: "telegram", to: "123", accountId: "bot-b", threadId: "99" },
|
||||
);
|
||||
|
||||
expect(merged).toEqual({
|
||||
channel: "telegram",
|
||||
to: undefined,
|
||||
accountId: "bot-a",
|
||||
});
|
||||
expect(merged?.threadId).toBeUndefined();
|
||||
|
||||
expect(
|
||||
mergeDeliveryContext(
|
||||
{ accountId: "bot-a" },
|
||||
{ channel: "telegram", to: "123", accountId: "bot-b", threadId: "99" },
|
||||
),
|
||||
).toEqual({
|
||||
channel: undefined,
|
||||
to: undefined,
|
||||
accountId: "bot-a",
|
||||
});
|
||||
});
|
||||
|
||||
it("uses fallback route fields when fallback has no channel", () => {
|
||||
const merged = mergeDeliveryContext(
|
||||
{ channel: "demo-channel" },
|
||||
|
||||
Reference in New Issue
Block a user