682 lines
20 KiB
TypeScript
682 lines
20 KiB
TypeScript
import { describe, expect, test } from "vitest";
|
|
import type { ChatType } from "../channels/chat-type.js";
|
|
import type { OpenClawConfig } from "../config/config.js";
|
|
import { resolveAgentRoute } from "./resolve-route.js";
|
|
|
|
describe("resolveAgentRoute", () => {
|
|
test("defaults to main/default when no bindings exist", () => {
|
|
const cfg: OpenClawConfig = {};
|
|
const route = resolveAgentRoute({
|
|
cfg,
|
|
channel: "whatsapp",
|
|
accountId: null,
|
|
peer: { kind: "direct", id: "+15551234567" },
|
|
});
|
|
expect(route.agentId).toBe("main");
|
|
expect(route.accountId).toBe("default");
|
|
expect(route.sessionKey).toBe("agent:main:main");
|
|
expect(route.matchedBy).toBe("default");
|
|
});
|
|
|
|
test("dmScope controls direct-message session key isolation", () => {
|
|
const cases = [
|
|
{ dmScope: "per-peer" as const, expected: "agent:main:direct:+15551234567" },
|
|
{
|
|
dmScope: "per-channel-peer" as const,
|
|
expected: "agent:main:whatsapp:direct:+15551234567",
|
|
},
|
|
];
|
|
for (const testCase of cases) {
|
|
const cfg: OpenClawConfig = {
|
|
session: { dmScope: testCase.dmScope },
|
|
};
|
|
const route = resolveAgentRoute({
|
|
cfg,
|
|
channel: "whatsapp",
|
|
accountId: null,
|
|
peer: { kind: "direct", id: "+15551234567" },
|
|
});
|
|
expect(route.sessionKey).toBe(testCase.expected);
|
|
}
|
|
});
|
|
|
|
test("identityLinks applies to direct-message scopes", () => {
|
|
const cases = [
|
|
{
|
|
dmScope: "per-peer" as const,
|
|
channel: "telegram",
|
|
peerId: "111111111",
|
|
expected: "agent:main:direct:alice",
|
|
},
|
|
{
|
|
dmScope: "per-channel-peer" as const,
|
|
channel: "discord",
|
|
peerId: "222222222222222222",
|
|
expected: "agent:main:discord:direct:alice",
|
|
},
|
|
];
|
|
for (const testCase of cases) {
|
|
const cfg: OpenClawConfig = {
|
|
session: {
|
|
dmScope: testCase.dmScope,
|
|
identityLinks: {
|
|
alice: ["telegram:111111111", "discord:222222222222222222"],
|
|
},
|
|
},
|
|
};
|
|
const route = resolveAgentRoute({
|
|
cfg,
|
|
channel: testCase.channel,
|
|
accountId: null,
|
|
peer: { kind: "direct", id: testCase.peerId },
|
|
});
|
|
expect(route.sessionKey).toBe(testCase.expected);
|
|
}
|
|
});
|
|
|
|
test("peer binding wins over account binding", () => {
|
|
const cfg: OpenClawConfig = {
|
|
bindings: [
|
|
{
|
|
agentId: "a",
|
|
match: {
|
|
channel: "whatsapp",
|
|
accountId: "biz",
|
|
peer: { kind: "direct", id: "+1000" },
|
|
},
|
|
},
|
|
{
|
|
agentId: "b",
|
|
match: { channel: "whatsapp", accountId: "biz" },
|
|
},
|
|
],
|
|
};
|
|
const route = resolveAgentRoute({
|
|
cfg,
|
|
channel: "whatsapp",
|
|
accountId: "biz",
|
|
peer: { kind: "direct", id: "+1000" },
|
|
});
|
|
expect(route.agentId).toBe("a");
|
|
expect(route.sessionKey).toBe("agent:a:main");
|
|
expect(route.matchedBy).toBe("binding.peer");
|
|
});
|
|
|
|
test("discord channel peer binding wins over guild binding", () => {
|
|
const cfg: OpenClawConfig = {
|
|
bindings: [
|
|
{
|
|
agentId: "chan",
|
|
match: {
|
|
channel: "discord",
|
|
accountId: "default",
|
|
peer: { kind: "channel", id: "c1" },
|
|
},
|
|
},
|
|
{
|
|
agentId: "guild",
|
|
match: {
|
|
channel: "discord",
|
|
accountId: "default",
|
|
guildId: "g1",
|
|
},
|
|
},
|
|
],
|
|
};
|
|
const route = resolveAgentRoute({
|
|
cfg,
|
|
channel: "discord",
|
|
accountId: "default",
|
|
peer: { kind: "channel", id: "c1" },
|
|
guildId: "g1",
|
|
});
|
|
expect(route.agentId).toBe("chan");
|
|
expect(route.sessionKey).toBe("agent:chan:discord:channel:c1");
|
|
expect(route.matchedBy).toBe("binding.peer");
|
|
});
|
|
|
|
test("coerces numeric peer ids to stable session keys", () => {
|
|
const cfg: OpenClawConfig = {};
|
|
const route = resolveAgentRoute({
|
|
cfg,
|
|
channel: "discord",
|
|
accountId: "default",
|
|
peer: { kind: "channel", id: 1468834856187203680n as unknown as string },
|
|
});
|
|
expect(route.sessionKey).toBe("agent:main:discord:channel:1468834856187203680");
|
|
});
|
|
|
|
test("guild binding wins over account binding when peer not bound", () => {
|
|
const cfg: OpenClawConfig = {
|
|
bindings: [
|
|
{
|
|
agentId: "guild",
|
|
match: {
|
|
channel: "discord",
|
|
accountId: "default",
|
|
guildId: "g1",
|
|
},
|
|
},
|
|
{
|
|
agentId: "acct",
|
|
match: { channel: "discord", accountId: "default" },
|
|
},
|
|
],
|
|
};
|
|
const route = resolveAgentRoute({
|
|
cfg,
|
|
channel: "discord",
|
|
accountId: "default",
|
|
peer: { kind: "channel", id: "c1" },
|
|
guildId: "g1",
|
|
});
|
|
expect(route.agentId).toBe("guild");
|
|
expect(route.matchedBy).toBe("binding.guild");
|
|
});
|
|
|
|
test("peer+guild binding does not act as guild-wide fallback when peer mismatches (#14752)", () => {
|
|
const cfg: OpenClawConfig = {
|
|
bindings: [
|
|
{
|
|
agentId: "olga",
|
|
match: {
|
|
channel: "discord",
|
|
peer: { kind: "channel", id: "CHANNEL_A" },
|
|
guildId: "GUILD_1",
|
|
},
|
|
},
|
|
{
|
|
agentId: "main",
|
|
match: {
|
|
channel: "discord",
|
|
guildId: "GUILD_1",
|
|
},
|
|
},
|
|
],
|
|
};
|
|
const route = resolveAgentRoute({
|
|
cfg,
|
|
channel: "discord",
|
|
peer: { kind: "channel", id: "CHANNEL_B" },
|
|
guildId: "GUILD_1",
|
|
});
|
|
expect(route.agentId).toBe("main");
|
|
expect(route.matchedBy).toBe("binding.guild");
|
|
});
|
|
|
|
test("peer+guild binding requires guild match even when peer matches", () => {
|
|
const cfg: OpenClawConfig = {
|
|
bindings: [
|
|
{
|
|
agentId: "wrongguild",
|
|
match: {
|
|
channel: "discord",
|
|
peer: { kind: "channel", id: "c1" },
|
|
guildId: "g1",
|
|
},
|
|
},
|
|
{
|
|
agentId: "rightguild",
|
|
match: {
|
|
channel: "discord",
|
|
guildId: "g2",
|
|
},
|
|
},
|
|
],
|
|
};
|
|
const route = resolveAgentRoute({
|
|
cfg,
|
|
channel: "discord",
|
|
peer: { kind: "channel", id: "c1" },
|
|
guildId: "g2",
|
|
});
|
|
expect(route.agentId).toBe("rightguild");
|
|
expect(route.matchedBy).toBe("binding.guild");
|
|
});
|
|
|
|
test("peer+team binding does not act as team-wide fallback when peer mismatches", () => {
|
|
const cfg: OpenClawConfig = {
|
|
bindings: [
|
|
{
|
|
agentId: "roomonly",
|
|
match: {
|
|
channel: "slack",
|
|
peer: { kind: "channel", id: "C_A" },
|
|
teamId: "T1",
|
|
},
|
|
},
|
|
{
|
|
agentId: "teamwide",
|
|
match: {
|
|
channel: "slack",
|
|
teamId: "T1",
|
|
},
|
|
},
|
|
],
|
|
};
|
|
const route = resolveAgentRoute({
|
|
cfg,
|
|
channel: "slack",
|
|
teamId: "T1",
|
|
peer: { kind: "channel", id: "C_B" },
|
|
});
|
|
expect(route.agentId).toBe("teamwide");
|
|
expect(route.matchedBy).toBe("binding.team");
|
|
});
|
|
|
|
test("peer+team binding requires team match even when peer matches", () => {
|
|
const cfg: OpenClawConfig = {
|
|
bindings: [
|
|
{
|
|
agentId: "wrongteam",
|
|
match: {
|
|
channel: "slack",
|
|
peer: { kind: "channel", id: "C1" },
|
|
teamId: "T1",
|
|
},
|
|
},
|
|
{
|
|
agentId: "rightteam",
|
|
match: {
|
|
channel: "slack",
|
|
teamId: "T2",
|
|
},
|
|
},
|
|
],
|
|
};
|
|
const route = resolveAgentRoute({
|
|
cfg,
|
|
channel: "slack",
|
|
teamId: "T2",
|
|
peer: { kind: "channel", id: "C1" },
|
|
});
|
|
expect(route.agentId).toBe("rightteam");
|
|
expect(route.matchedBy).toBe("binding.team");
|
|
});
|
|
|
|
test("missing accountId in binding matches default account only", () => {
|
|
const cfg: OpenClawConfig = {
|
|
bindings: [{ agentId: "defaultAcct", match: { channel: "whatsapp" } }],
|
|
};
|
|
|
|
const defaultRoute = resolveAgentRoute({
|
|
cfg,
|
|
channel: "whatsapp",
|
|
accountId: undefined,
|
|
peer: { kind: "direct", id: "+1000" },
|
|
});
|
|
expect(defaultRoute.agentId).toBe("defaultacct");
|
|
expect(defaultRoute.matchedBy).toBe("binding.account");
|
|
|
|
const otherRoute = resolveAgentRoute({
|
|
cfg,
|
|
channel: "whatsapp",
|
|
accountId: "biz",
|
|
peer: { kind: "direct", id: "+1000" },
|
|
});
|
|
expect(otherRoute.agentId).toBe("main");
|
|
});
|
|
|
|
test("accountId=* matches any account as a channel fallback", () => {
|
|
const cfg: OpenClawConfig = {
|
|
bindings: [
|
|
{
|
|
agentId: "any",
|
|
match: { channel: "whatsapp", accountId: "*" },
|
|
},
|
|
],
|
|
};
|
|
const route = resolveAgentRoute({
|
|
cfg,
|
|
channel: "whatsapp",
|
|
accountId: "biz",
|
|
peer: { kind: "direct", id: "+1000" },
|
|
});
|
|
expect(route.agentId).toBe("any");
|
|
expect(route.matchedBy).toBe("binding.channel");
|
|
});
|
|
|
|
test("binding accountId matching is canonicalized", () => {
|
|
const cfg: OpenClawConfig = {
|
|
bindings: [{ agentId: "biz", match: { channel: "discord", accountId: "BIZ" } }],
|
|
};
|
|
const route = resolveAgentRoute({
|
|
cfg,
|
|
channel: "discord",
|
|
accountId: " biz ",
|
|
peer: { kind: "direct", id: "u-1" },
|
|
});
|
|
expect(route.agentId).toBe("biz");
|
|
expect(route.matchedBy).toBe("binding.account");
|
|
expect(route.accountId).toBe("biz");
|
|
});
|
|
|
|
test("defaultAgentId is used when no binding matches", () => {
|
|
const cfg: OpenClawConfig = {
|
|
agents: {
|
|
list: [{ id: "home", default: true, workspace: "~/openclaw-home" }],
|
|
},
|
|
};
|
|
const route = resolveAgentRoute({
|
|
cfg,
|
|
channel: "whatsapp",
|
|
accountId: "biz",
|
|
peer: { kind: "direct", id: "+1000" },
|
|
});
|
|
expect(route.agentId).toBe("home");
|
|
expect(route.sessionKey).toBe("agent:home:main");
|
|
});
|
|
});
|
|
|
|
test("dmScope=per-account-channel-peer isolates DM sessions per account, channel and sender", () => {
|
|
const cfg: OpenClawConfig = {
|
|
session: { dmScope: "per-account-channel-peer" },
|
|
};
|
|
const route = resolveAgentRoute({
|
|
cfg,
|
|
channel: "telegram",
|
|
accountId: "tasks",
|
|
peer: { kind: "direct", id: "7550356539" },
|
|
});
|
|
expect(route.sessionKey).toBe("agent:main:telegram:tasks:direct:7550356539");
|
|
});
|
|
|
|
test("dmScope=per-account-channel-peer uses default accountId when not provided", () => {
|
|
const cfg: OpenClawConfig = {
|
|
session: { dmScope: "per-account-channel-peer" },
|
|
};
|
|
const route = resolveAgentRoute({
|
|
cfg,
|
|
channel: "telegram",
|
|
accountId: null,
|
|
peer: { kind: "direct", id: "7550356539" },
|
|
});
|
|
expect(route.sessionKey).toBe("agent:main:telegram:default:direct:7550356539");
|
|
});
|
|
|
|
describe("parentPeer binding inheritance (thread support)", () => {
|
|
const threadPeer = { kind: "channel" as const, id: "thread-456" };
|
|
const defaultParentPeer = { kind: "channel" as const, id: "parent-channel-123" };
|
|
|
|
function makeDiscordPeerBinding(agentId: string, peerId: string) {
|
|
return {
|
|
agentId,
|
|
match: {
|
|
channel: "discord" as const,
|
|
peer: { kind: "channel" as const, id: peerId },
|
|
},
|
|
};
|
|
}
|
|
|
|
function makeDiscordGuildBinding(agentId: string, guildId: string) {
|
|
return {
|
|
agentId,
|
|
match: {
|
|
channel: "discord" as const,
|
|
guildId,
|
|
},
|
|
};
|
|
}
|
|
|
|
function resolveDiscordThreadRoute(params: {
|
|
cfg: OpenClawConfig;
|
|
parentPeer?: { kind: "channel"; id: string } | null;
|
|
guildId?: string;
|
|
}) {
|
|
const parentPeer = "parentPeer" in params ? params.parentPeer : defaultParentPeer;
|
|
return resolveAgentRoute({
|
|
cfg: params.cfg,
|
|
channel: "discord",
|
|
peer: threadPeer,
|
|
parentPeer,
|
|
guildId: params.guildId,
|
|
});
|
|
}
|
|
|
|
test("thread inherits binding from parent channel when no direct match", () => {
|
|
const cfg: OpenClawConfig = {
|
|
bindings: [makeDiscordPeerBinding("adecco", defaultParentPeer.id)],
|
|
};
|
|
const route = resolveDiscordThreadRoute({ cfg });
|
|
expect(route.agentId).toBe("adecco");
|
|
expect(route.matchedBy).toBe("binding.peer.parent");
|
|
});
|
|
|
|
test("direct peer binding wins over parent peer binding", () => {
|
|
const cfg: OpenClawConfig = {
|
|
bindings: [
|
|
makeDiscordPeerBinding("thread-agent", threadPeer.id),
|
|
makeDiscordPeerBinding("parent-agent", defaultParentPeer.id),
|
|
],
|
|
};
|
|
const route = resolveDiscordThreadRoute({ cfg });
|
|
expect(route.agentId).toBe("thread-agent");
|
|
expect(route.matchedBy).toBe("binding.peer");
|
|
});
|
|
|
|
test("parent peer binding wins over guild binding", () => {
|
|
const cfg: OpenClawConfig = {
|
|
bindings: [
|
|
makeDiscordPeerBinding("parent-agent", defaultParentPeer.id),
|
|
makeDiscordGuildBinding("guild-agent", "guild-789"),
|
|
],
|
|
};
|
|
const route = resolveDiscordThreadRoute({ cfg, guildId: "guild-789" });
|
|
expect(route.agentId).toBe("parent-agent");
|
|
expect(route.matchedBy).toBe("binding.peer.parent");
|
|
});
|
|
|
|
test("falls back to guild binding when no parent peer match", () => {
|
|
const cfg: OpenClawConfig = {
|
|
bindings: [
|
|
makeDiscordPeerBinding("other-parent-agent", "other-parent-999"),
|
|
makeDiscordGuildBinding("guild-agent", "guild-789"),
|
|
],
|
|
};
|
|
const route = resolveDiscordThreadRoute({ cfg, guildId: "guild-789" });
|
|
expect(route.agentId).toBe("guild-agent");
|
|
expect(route.matchedBy).toBe("binding.guild");
|
|
});
|
|
|
|
test("parentPeer with empty id is ignored", () => {
|
|
const cfg: OpenClawConfig = {
|
|
bindings: [makeDiscordPeerBinding("parent-agent", defaultParentPeer.id)],
|
|
};
|
|
const route = resolveDiscordThreadRoute({ cfg, parentPeer: { kind: "channel", id: "" } });
|
|
expect(route.agentId).toBe("main");
|
|
expect(route.matchedBy).toBe("default");
|
|
});
|
|
|
|
test("null parentPeer is handled gracefully", () => {
|
|
const cfg: OpenClawConfig = {
|
|
bindings: [makeDiscordPeerBinding("parent-agent", defaultParentPeer.id)],
|
|
};
|
|
const route = resolveDiscordThreadRoute({ cfg, parentPeer: null });
|
|
expect(route.agentId).toBe("main");
|
|
expect(route.matchedBy).toBe("default");
|
|
});
|
|
});
|
|
|
|
describe("backward compatibility: peer.kind dm → direct", () => {
|
|
test("legacy dm in config matches runtime direct peer", () => {
|
|
const cfg: OpenClawConfig = {
|
|
bindings: [
|
|
{
|
|
agentId: "alex",
|
|
match: {
|
|
channel: "whatsapp",
|
|
// Legacy config uses "dm" instead of "direct"
|
|
peer: { kind: "dm" as ChatType, id: "+15551234567" },
|
|
},
|
|
},
|
|
],
|
|
};
|
|
const route = resolveAgentRoute({
|
|
cfg,
|
|
channel: "whatsapp",
|
|
accountId: null,
|
|
// Runtime uses canonical "direct"
|
|
peer: { kind: "direct", id: "+15551234567" },
|
|
});
|
|
expect(route.agentId).toBe("alex");
|
|
expect(route.matchedBy).toBe("binding.peer");
|
|
});
|
|
});
|
|
|
|
describe("role-based agent routing", () => {
|
|
type DiscordBinding = NonNullable<OpenClawConfig["bindings"]>[number];
|
|
|
|
function makeDiscordRoleBinding(
|
|
agentId: string,
|
|
params: {
|
|
roles?: string[];
|
|
peerId?: string;
|
|
includeGuildId?: boolean;
|
|
} = {},
|
|
): DiscordBinding {
|
|
return {
|
|
agentId,
|
|
match: {
|
|
channel: "discord",
|
|
...(params.includeGuildId === false ? {} : { guildId: "g1" }),
|
|
...(params.roles !== undefined ? { roles: params.roles } : {}),
|
|
...(params.peerId ? { peer: { kind: "channel", id: params.peerId } } : {}),
|
|
},
|
|
};
|
|
}
|
|
|
|
function expectDiscordRoleRoute(params: {
|
|
bindings: DiscordBinding[];
|
|
memberRoleIds?: string[];
|
|
peerId?: string;
|
|
parentPeerId?: string;
|
|
expectedAgentId: string;
|
|
expectedMatchedBy: string;
|
|
}) {
|
|
const route = resolveAgentRoute({
|
|
cfg: { bindings: params.bindings },
|
|
channel: "discord",
|
|
guildId: "g1",
|
|
...(params.memberRoleIds ? { memberRoleIds: params.memberRoleIds } : {}),
|
|
peer: { kind: "channel", id: params.peerId ?? "c1" },
|
|
...(params.parentPeerId
|
|
? {
|
|
parentPeer: { kind: "channel", id: params.parentPeerId },
|
|
}
|
|
: {}),
|
|
});
|
|
expect(route.agentId).toBe(params.expectedAgentId);
|
|
expect(route.matchedBy).toBe(params.expectedMatchedBy);
|
|
}
|
|
|
|
test("guild+roles binding matches when member has matching role", () => {
|
|
expectDiscordRoleRoute({
|
|
bindings: [makeDiscordRoleBinding("opus", { roles: ["r1"] })],
|
|
memberRoleIds: ["r1"],
|
|
expectedAgentId: "opus",
|
|
expectedMatchedBy: "binding.guild+roles",
|
|
});
|
|
});
|
|
|
|
test("guild+roles binding skipped when no matching role", () => {
|
|
expectDiscordRoleRoute({
|
|
bindings: [makeDiscordRoleBinding("opus", { roles: ["r1"] })],
|
|
memberRoleIds: ["r2"],
|
|
expectedAgentId: "main",
|
|
expectedMatchedBy: "default",
|
|
});
|
|
});
|
|
|
|
test("guild+roles is more specific than guild-only", () => {
|
|
expectDiscordRoleRoute({
|
|
bindings: [
|
|
makeDiscordRoleBinding("opus", { roles: ["r1"] }),
|
|
makeDiscordRoleBinding("sonnet"),
|
|
],
|
|
memberRoleIds: ["r1"],
|
|
expectedAgentId: "opus",
|
|
expectedMatchedBy: "binding.guild+roles",
|
|
});
|
|
});
|
|
|
|
test("peer binding still beats guild+roles", () => {
|
|
expectDiscordRoleRoute({
|
|
bindings: [
|
|
makeDiscordRoleBinding("peer-agent", { peerId: "c1", includeGuildId: false }),
|
|
makeDiscordRoleBinding("roles-agent", { roles: ["r1"] }),
|
|
],
|
|
memberRoleIds: ["r1"],
|
|
expectedAgentId: "peer-agent",
|
|
expectedMatchedBy: "binding.peer",
|
|
});
|
|
});
|
|
|
|
test("parent peer binding still beats guild+roles", () => {
|
|
expectDiscordRoleRoute({
|
|
bindings: [
|
|
makeDiscordRoleBinding("parent-agent", {
|
|
peerId: "parent-1",
|
|
includeGuildId: false,
|
|
}),
|
|
makeDiscordRoleBinding("roles-agent", { roles: ["r1"] }),
|
|
],
|
|
memberRoleIds: ["r1"],
|
|
peerId: "thread-1",
|
|
parentPeerId: "parent-1",
|
|
expectedAgentId: "parent-agent",
|
|
expectedMatchedBy: "binding.peer.parent",
|
|
});
|
|
});
|
|
|
|
test("no memberRoleIds means guild+roles doesn't match", () => {
|
|
expectDiscordRoleRoute({
|
|
bindings: [makeDiscordRoleBinding("opus", { roles: ["r1"] })],
|
|
expectedAgentId: "main",
|
|
expectedMatchedBy: "default",
|
|
});
|
|
});
|
|
|
|
test("first matching binding wins with multiple role bindings", () => {
|
|
expectDiscordRoleRoute({
|
|
bindings: [
|
|
makeDiscordRoleBinding("opus", { roles: ["r1"] }),
|
|
makeDiscordRoleBinding("sonnet", { roles: ["r2"] }),
|
|
],
|
|
memberRoleIds: ["r1", "r2"],
|
|
expectedAgentId: "opus",
|
|
expectedMatchedBy: "binding.guild+roles",
|
|
});
|
|
});
|
|
|
|
test("empty roles array treated as no role restriction", () => {
|
|
expectDiscordRoleRoute({
|
|
bindings: [makeDiscordRoleBinding("opus", { roles: [] })],
|
|
memberRoleIds: ["r1"],
|
|
expectedAgentId: "opus",
|
|
expectedMatchedBy: "binding.guild",
|
|
});
|
|
});
|
|
|
|
test("guild+roles binding does not match as guild-only when roles do not match", () => {
|
|
expectDiscordRoleRoute({
|
|
bindings: [makeDiscordRoleBinding("opus", { roles: ["admin"] })],
|
|
memberRoleIds: ["regular"],
|
|
expectedAgentId: "main",
|
|
expectedMatchedBy: "default",
|
|
});
|
|
});
|
|
|
|
test("peer+guild+roles binding does not act as guild+roles fallback when peer mismatches", () => {
|
|
expectDiscordRoleRoute({
|
|
bindings: [
|
|
makeDiscordRoleBinding("peer-roles", { peerId: "c-target", roles: ["r1"] }),
|
|
makeDiscordRoleBinding("guild-roles", { roles: ["r1"] }),
|
|
],
|
|
memberRoleIds: ["r1"],
|
|
peerId: "c-other",
|
|
expectedAgentId: "guild-roles",
|
|
expectedMatchedBy: "binding.guild+roles",
|
|
});
|
|
});
|
|
});
|