mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-09 03:22:40 +00:00
fix: route bindings return the wrong agent when route identifiers contain separators (#110001)
* fix(routing): replace delimiter-concatenated route cache key with JSON-serialized tuple to prevent identifier collisions * fix(routing): restore dmScope input contract alongside JSON cache key fix * fix(routing): restore max-lines exemption on resolve-route.ts * test(routing): correct cache collision regression Co-authored-by: YangManBOBO <152153397+YangManBOBO@users.noreply.github.com> --------- Co-authored-by: Peter Steinberger <steipete@gmail.com> Co-authored-by: YangManBOBO <152153397+YangManBOBO@users.noreply.github.com>
This commit is contained in:
@@ -135,7 +135,7 @@ describe("resolveAgentRoute", () => {
|
||||
});
|
||||
|
||||
test("allows a channel route to require a stronger direct-message scope", () => {
|
||||
const route = resolveRoute({
|
||||
const route = resolveAgentRoute({
|
||||
cfg: { session: { dmScope: "main" } },
|
||||
channel: "zalouser",
|
||||
peer: { kind: "direct", id: "321" },
|
||||
@@ -1207,6 +1207,136 @@ describe("wildcard peer bindings (peer.id=*)", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("resolved route cache keys", () => {
|
||||
test("does not reuse a cached route when peer and guild fields contain cache separators", () => {
|
||||
const cfg: OpenClawConfig = {
|
||||
agents: { list: [{ id: "whole-peer" }, { id: "guild-room" }] },
|
||||
bindings: [
|
||||
{
|
||||
agentId: "whole-peer",
|
||||
match: {
|
||||
channel: "discord",
|
||||
accountId: "default",
|
||||
peer: { kind: "group", id: "room\t-\tguild-1" },
|
||||
},
|
||||
},
|
||||
{
|
||||
agentId: "guild-room",
|
||||
match: {
|
||||
channel: "discord",
|
||||
accountId: "default",
|
||||
peer: { kind: "group", id: "room" },
|
||||
guildId: "guild-1",
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
expectResolvedRoute(
|
||||
resolveAgentRoute({
|
||||
cfg,
|
||||
channel: "discord",
|
||||
accountId: "default",
|
||||
peer: { kind: "group", id: "room\t-\tguild-1" },
|
||||
}),
|
||||
{ agentId: "whole-peer", matchedBy: "binding.peer" },
|
||||
);
|
||||
expectResolvedRoute(
|
||||
resolveAgentRoute({
|
||||
cfg,
|
||||
channel: "discord",
|
||||
accountId: "default",
|
||||
guildId: "guild-1",
|
||||
peer: { kind: "group", id: "room" },
|
||||
}),
|
||||
{ agentId: "guild-room", matchedBy: "binding.peer" },
|
||||
);
|
||||
});
|
||||
|
||||
test("does not reuse a cached route when role IDs contain cache separators", () => {
|
||||
const cfg: OpenClawConfig = {
|
||||
agents: { list: [{ id: "comma-role" }, { id: "suffix-role" }] },
|
||||
bindings: [
|
||||
{
|
||||
agentId: "comma-role",
|
||||
match: {
|
||||
channel: "discord",
|
||||
accountId: "default",
|
||||
guildId: "guild-1",
|
||||
roles: ["a,b"],
|
||||
},
|
||||
},
|
||||
{
|
||||
agentId: "suffix-role",
|
||||
match: {
|
||||
channel: "discord",
|
||||
accountId: "default",
|
||||
guildId: "guild-1",
|
||||
roles: ["b,c"],
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
expectResolvedRoute(
|
||||
resolveAgentRoute({
|
||||
cfg,
|
||||
channel: "discord",
|
||||
accountId: "default",
|
||||
guildId: "guild-1",
|
||||
memberRoleIds: ["a,b", "c"],
|
||||
}),
|
||||
{ agentId: "comma-role", matchedBy: "binding.guild+roles" },
|
||||
);
|
||||
expectResolvedRoute(
|
||||
resolveAgentRoute({
|
||||
cfg,
|
||||
channel: "discord",
|
||||
accountId: "default",
|
||||
guildId: "guild-1",
|
||||
memberRoleIds: ["a", "b,c"],
|
||||
}),
|
||||
{ agentId: "suffix-role", matchedBy: "binding.guild+roles" },
|
||||
);
|
||||
});
|
||||
|
||||
test("does not reuse a cached route when guildId is omitted versus the literal hyphen string", () => {
|
||||
const cfg: OpenClawConfig = {
|
||||
agents: { list: [{ id: "main", default: true }, { id: "hyphen-guild" }] },
|
||||
bindings: [
|
||||
{
|
||||
agentId: "hyphen-guild",
|
||||
match: {
|
||||
channel: "discord",
|
||||
accountId: "default",
|
||||
guildId: "-",
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
expectResolvedRoute(
|
||||
resolveAgentRoute({
|
||||
cfg,
|
||||
channel: "discord",
|
||||
accountId: "default",
|
||||
peer: { kind: "group", id: "room" },
|
||||
}),
|
||||
{ agentId: "main", matchedBy: "default" },
|
||||
);
|
||||
expectResolvedRoute(
|
||||
resolveAgentRoute({
|
||||
cfg,
|
||||
channel: "discord",
|
||||
accountId: "default",
|
||||
peer: { kind: "group", id: "room" },
|
||||
guildId: "-",
|
||||
}),
|
||||
{ agentId: "hyphen-guild", matchedBy: "binding.guild" },
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("binding evaluation cache scalability", () => {
|
||||
test("does not rescan full bindings across distinct channel/account cache entries (#36915)", () => {
|
||||
const cacheKeyCount = 64;
|
||||
|
||||
@@ -548,22 +548,6 @@ function formatRouteCachePeer(peer: RoutePeer | null): string {
|
||||
return `${peer.kind}:${peer.id}`;
|
||||
}
|
||||
|
||||
function formatRoleIdsCacheKey(roleIds: string[]): string {
|
||||
const count = roleIds.length;
|
||||
if (count === 0) {
|
||||
return "-";
|
||||
}
|
||||
if (count === 1) {
|
||||
return roleIds[0] ?? "-";
|
||||
}
|
||||
if (count === 2) {
|
||||
const first = roleIds[0] ?? "";
|
||||
const second = roleIds[1] ?? "";
|
||||
return first <= second ? `${first},${second}` : `${second},${first}`;
|
||||
}
|
||||
return roleIds.toSorted().join(",");
|
||||
}
|
||||
|
||||
function buildResolvedRouteCacheKey(params: {
|
||||
channel: string;
|
||||
accountId: string;
|
||||
@@ -574,7 +558,16 @@ function buildResolvedRouteCacheKey(params: {
|
||||
memberRoleIds: string[];
|
||||
dmScope: string;
|
||||
}): string {
|
||||
return `${params.channel}\t${params.accountId}\t${formatRouteCachePeer(params.peer)}\t${formatRouteCachePeer(params.parentPeer)}\t${params.guildId || "-"}\t${params.teamId || "-"}\t${formatRoleIdsCacheKey(params.memberRoleIds)}\t${params.dmScope}`;
|
||||
return JSON.stringify([
|
||||
params.channel,
|
||||
params.accountId,
|
||||
formatRouteCachePeer(params.peer),
|
||||
formatRouteCachePeer(params.parentPeer),
|
||||
params.guildId ?? null,
|
||||
params.teamId ?? null,
|
||||
params.memberRoleIds.toSorted(),
|
||||
params.dmScope,
|
||||
]);
|
||||
}
|
||||
|
||||
function hasGuildConstraint(match: NormalizedBindingMatch): boolean {
|
||||
|
||||
Reference in New Issue
Block a user