mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-09 12:17:40 +00:00
refactor(routing): normalize binding matching and harden qmd boot-update tests
This commit is contained in:
@@ -178,14 +178,9 @@ describe("QmdMemoryManager", () => {
|
|||||||
|
|
||||||
const resolved = resolveMemoryBackendConfig({ cfg, agentId });
|
const resolved = resolveMemoryBackendConfig({ cfg, agentId });
|
||||||
const createPromise = QmdMemoryManager.create({ cfg, agentId, resolved });
|
const createPromise = QmdMemoryManager.create({ cfg, agentId, resolved });
|
||||||
const race = await Promise.race([
|
await waitForCondition(() => releaseUpdate !== null, 400);
|
||||||
createPromise.then(() => "created" as const),
|
|
||||||
new Promise<"timeout">((resolve) => setTimeout(() => resolve("timeout"), 40)),
|
|
||||||
]);
|
|
||||||
expect(race).toBe("created");
|
|
||||||
await waitForCondition(() => releaseUpdate !== null, 200);
|
|
||||||
releaseUpdate?.();
|
|
||||||
const manager = await createPromise;
|
const manager = await createPromise;
|
||||||
|
releaseUpdate?.();
|
||||||
await manager?.close();
|
await manager?.close();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -219,12 +214,12 @@ describe("QmdMemoryManager", () => {
|
|||||||
|
|
||||||
const resolved = resolveMemoryBackendConfig({ cfg, agentId });
|
const resolved = resolveMemoryBackendConfig({ cfg, agentId });
|
||||||
const createPromise = QmdMemoryManager.create({ cfg, agentId, resolved });
|
const createPromise = QmdMemoryManager.create({ cfg, agentId, resolved });
|
||||||
|
await waitForCondition(() => releaseUpdate !== null, 400);
|
||||||
const race = await Promise.race([
|
const race = await Promise.race([
|
||||||
createPromise.then(() => "created" as const),
|
createPromise.then(() => "created" as const),
|
||||||
new Promise<"timeout">((resolve) => setTimeout(() => resolve("timeout"), 40)),
|
new Promise<"timeout">((resolve) => setTimeout(() => resolve("timeout"), 120)),
|
||||||
]);
|
]);
|
||||||
expect(race).toBe("timeout");
|
expect(race).toBe("timeout");
|
||||||
await waitForCondition(() => releaseUpdate !== null, 200);
|
|
||||||
releaseUpdate?.();
|
releaseUpdate?.();
|
||||||
const manager = await createPromise;
|
const manager = await createPromise;
|
||||||
await manager?.close();
|
await manager?.close();
|
||||||
|
|||||||
@@ -376,7 +376,7 @@ test("dmScope=per-account-channel-peer uses default accountId when not provided"
|
|||||||
|
|
||||||
describe("parentPeer binding inheritance (thread support)", () => {
|
describe("parentPeer binding inheritance (thread support)", () => {
|
||||||
test("thread inherits binding from parent channel when no direct match", () => {
|
test("thread inherits binding from parent channel when no direct match", () => {
|
||||||
const cfg: MoltbotConfig = {
|
const cfg: OpenClawConfig = {
|
||||||
bindings: [
|
bindings: [
|
||||||
{
|
{
|
||||||
agentId: "adecco",
|
agentId: "adecco",
|
||||||
@@ -398,7 +398,7 @@ describe("parentPeer binding inheritance (thread support)", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test("direct peer binding wins over parent peer binding", () => {
|
test("direct peer binding wins over parent peer binding", () => {
|
||||||
const cfg: MoltbotConfig = {
|
const cfg: OpenClawConfig = {
|
||||||
bindings: [
|
bindings: [
|
||||||
{
|
{
|
||||||
agentId: "thread-agent",
|
agentId: "thread-agent",
|
||||||
@@ -427,7 +427,7 @@ describe("parentPeer binding inheritance (thread support)", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test("parent peer binding wins over guild binding", () => {
|
test("parent peer binding wins over guild binding", () => {
|
||||||
const cfg: MoltbotConfig = {
|
const cfg: OpenClawConfig = {
|
||||||
bindings: [
|
bindings: [
|
||||||
{
|
{
|
||||||
agentId: "parent-agent",
|
agentId: "parent-agent",
|
||||||
@@ -457,7 +457,7 @@ describe("parentPeer binding inheritance (thread support)", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test("falls back to guild binding when no parent peer match", () => {
|
test("falls back to guild binding when no parent peer match", () => {
|
||||||
const cfg: MoltbotConfig = {
|
const cfg: OpenClawConfig = {
|
||||||
bindings: [
|
bindings: [
|
||||||
{
|
{
|
||||||
agentId: "other-parent-agent",
|
agentId: "other-parent-agent",
|
||||||
@@ -487,7 +487,7 @@ describe("parentPeer binding inheritance (thread support)", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test("parentPeer with empty id is ignored", () => {
|
test("parentPeer with empty id is ignored", () => {
|
||||||
const cfg: MoltbotConfig = {
|
const cfg: OpenClawConfig = {
|
||||||
bindings: [
|
bindings: [
|
||||||
{
|
{
|
||||||
agentId: "parent-agent",
|
agentId: "parent-agent",
|
||||||
@@ -509,7 +509,7 @@ describe("parentPeer binding inheritance (thread support)", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test("null parentPeer is handled gracefully", () => {
|
test("null parentPeer is handled gracefully", () => {
|
||||||
const cfg: MoltbotConfig = {
|
const cfg: OpenClawConfig = {
|
||||||
bindings: [
|
bindings: [
|
||||||
{
|
{
|
||||||
agentId: "parent-agent",
|
agentId: "parent-agent",
|
||||||
|
|||||||
@@ -135,117 +135,102 @@ function matchesChannel(
|
|||||||
return key === channel;
|
return key === channel;
|
||||||
}
|
}
|
||||||
|
|
||||||
function matchesPeer(
|
type NormalizedPeerConstraint =
|
||||||
match: { peer?: { kind?: string; id?: string } | undefined } | undefined,
|
| { state: "none" }
|
||||||
peer: RoutePeer,
|
| { state: "invalid" }
|
||||||
): boolean {
|
| { state: "valid"; kind: ChatType; id: string };
|
||||||
const m = match?.peer;
|
|
||||||
if (!m) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
// Backward compat: normalize "dm" to "direct" in config match rules
|
|
||||||
const kind = normalizeChatType(m.kind);
|
|
||||||
const id = normalizeId(m.id);
|
|
||||||
if (!kind || !id) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return kind === peer.kind && id === peer.id;
|
|
||||||
}
|
|
||||||
|
|
||||||
function matchesRoles(
|
type NormalizedBindingMatch = {
|
||||||
match: { roles?: string[] | undefined } | undefined,
|
accountPattern: string;
|
||||||
memberRoleIds: string[],
|
peer: NormalizedPeerConstraint;
|
||||||
): boolean {
|
guildId: string | null;
|
||||||
const roles = match?.roles;
|
teamId: string | null;
|
||||||
if (!Array.isArray(roles) || roles.length === 0) {
|
roles: string[] | null;
|
||||||
return false;
|
};
|
||||||
}
|
|
||||||
return roles.some((role) => memberRoleIds.includes(role));
|
|
||||||
}
|
|
||||||
|
|
||||||
function hasGuildConstraint(match: { guildId?: string | undefined } | undefined): boolean {
|
type EvaluatedBinding = {
|
||||||
return Boolean(normalizeId(match?.guildId));
|
binding: ReturnType<typeof listBindings>[number];
|
||||||
}
|
match: NormalizedBindingMatch;
|
||||||
|
};
|
||||||
|
|
||||||
function hasTeamConstraint(match: { teamId?: string | undefined } | undefined): boolean {
|
type BindingScope = {
|
||||||
return Boolean(normalizeId(match?.teamId));
|
peer: RoutePeer | null;
|
||||||
}
|
guildId: string;
|
||||||
|
teamId: string;
|
||||||
|
memberRoleIds: Set<string>;
|
||||||
|
};
|
||||||
|
|
||||||
function hasRolesConstraint(match: { roles?: string[] | undefined } | undefined): boolean {
|
function normalizePeerConstraint(
|
||||||
return Array.isArray(match?.roles) && match.roles.length > 0;
|
peer: { kind?: string; id?: string } | undefined,
|
||||||
}
|
): NormalizedPeerConstraint {
|
||||||
|
|
||||||
function matchesOptionalPeer(
|
|
||||||
match: { peer?: { kind?: string; id?: string } | undefined } | undefined,
|
|
||||||
peer: RoutePeer | null,
|
|
||||||
): boolean {
|
|
||||||
if (!match?.peer) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (!peer) {
|
if (!peer) {
|
||||||
return false;
|
return { state: "none" };
|
||||||
}
|
}
|
||||||
return matchesPeer(match, peer);
|
const kind = normalizeChatType(peer.kind);
|
||||||
|
const id = normalizeId(peer.id);
|
||||||
|
if (!kind || !id) {
|
||||||
|
return { state: "invalid" };
|
||||||
|
}
|
||||||
|
return { state: "valid", kind, id };
|
||||||
}
|
}
|
||||||
|
|
||||||
function matchesOptionalGuild(
|
function normalizeBindingMatch(
|
||||||
match: { guildId?: string | undefined } | undefined,
|
|
||||||
guildId: string,
|
|
||||||
): boolean {
|
|
||||||
const requiredGuildId = normalizeId(match?.guildId);
|
|
||||||
if (!requiredGuildId) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (!guildId) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return requiredGuildId === guildId;
|
|
||||||
}
|
|
||||||
|
|
||||||
function matchesOptionalTeam(
|
|
||||||
match: { teamId?: string | undefined } | undefined,
|
|
||||||
teamId: string,
|
|
||||||
): boolean {
|
|
||||||
const requiredTeamId = normalizeId(match?.teamId);
|
|
||||||
if (!requiredTeamId) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (!teamId) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return requiredTeamId === teamId;
|
|
||||||
}
|
|
||||||
|
|
||||||
function matchesOptionalRoles(
|
|
||||||
match: { roles?: string[] | undefined } | undefined,
|
|
||||||
memberRoleIds: string[],
|
|
||||||
): boolean {
|
|
||||||
if (!hasRolesConstraint(match)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return matchesRoles(match, memberRoleIds);
|
|
||||||
}
|
|
||||||
|
|
||||||
function matchesBindingScope(params: {
|
|
||||||
match:
|
match:
|
||||||
| {
|
| {
|
||||||
|
accountId?: string | undefined;
|
||||||
peer?: { kind?: string; id?: string } | undefined;
|
peer?: { kind?: string; id?: string } | undefined;
|
||||||
guildId?: string | undefined;
|
guildId?: string | undefined;
|
||||||
teamId?: string | undefined;
|
teamId?: string | undefined;
|
||||||
roles?: string[] | undefined;
|
roles?: string[] | undefined;
|
||||||
}
|
}
|
||||||
| undefined;
|
| undefined,
|
||||||
peer: RoutePeer | null;
|
): NormalizedBindingMatch {
|
||||||
guildId: string;
|
const rawRoles = match?.roles;
|
||||||
teamId: string;
|
return {
|
||||||
memberRoleIds: string[];
|
accountPattern: (match?.accountId ?? "").trim(),
|
||||||
}): boolean {
|
peer: normalizePeerConstraint(match?.peer),
|
||||||
return (
|
guildId: normalizeId(match?.guildId) || null,
|
||||||
matchesOptionalPeer(params.match, params.peer) &&
|
teamId: normalizeId(match?.teamId) || null,
|
||||||
matchesOptionalGuild(params.match, params.guildId) &&
|
roles: Array.isArray(rawRoles) && rawRoles.length > 0 ? rawRoles : null,
|
||||||
matchesOptionalTeam(params.match, params.teamId) &&
|
};
|
||||||
matchesOptionalRoles(params.match, params.memberRoleIds)
|
}
|
||||||
);
|
|
||||||
|
function hasGuildConstraint(match: NormalizedBindingMatch): boolean {
|
||||||
|
return Boolean(match.guildId);
|
||||||
|
}
|
||||||
|
|
||||||
|
function hasTeamConstraint(match: NormalizedBindingMatch): boolean {
|
||||||
|
return Boolean(match.teamId);
|
||||||
|
}
|
||||||
|
|
||||||
|
function hasRolesConstraint(match: NormalizedBindingMatch): boolean {
|
||||||
|
return Boolean(match.roles);
|
||||||
|
}
|
||||||
|
|
||||||
|
function matchesBindingScope(match: NormalizedBindingMatch, scope: BindingScope): boolean {
|
||||||
|
if (match.peer.state === "invalid") {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (match.peer.state === "valid") {
|
||||||
|
if (!scope.peer || scope.peer.kind !== match.peer.kind || scope.peer.id !== match.peer.id) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (match.guildId && match.guildId !== scope.guildId) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (match.teamId && match.teamId !== scope.teamId) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (match.roles) {
|
||||||
|
for (const role of match.roles) {
|
||||||
|
if (scope.memberRoleIds.has(role)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function resolveAgentRoute(input: ResolveAgentRouteInput): ResolvedAgentRoute {
|
export function resolveAgentRoute(input: ResolveAgentRouteInput): ResolvedAgentRoute {
|
||||||
@@ -255,15 +240,19 @@ export function resolveAgentRoute(input: ResolveAgentRouteInput): ResolvedAgentR
|
|||||||
const guildId = normalizeId(input.guildId);
|
const guildId = normalizeId(input.guildId);
|
||||||
const teamId = normalizeId(input.teamId);
|
const teamId = normalizeId(input.teamId);
|
||||||
const memberRoleIds = input.memberRoleIds ?? [];
|
const memberRoleIds = input.memberRoleIds ?? [];
|
||||||
|
const memberRoleIdSet = new Set(memberRoleIds);
|
||||||
|
|
||||||
const bindings = listBindings(input.cfg).filter((binding) => {
|
const bindings: EvaluatedBinding[] = listBindings(input.cfg).flatMap((binding) => {
|
||||||
if (!binding || typeof binding !== "object") {
|
if (!binding || typeof binding !== "object") {
|
||||||
return false;
|
return [];
|
||||||
}
|
}
|
||||||
if (!matchesChannel(binding.match, channel)) {
|
if (!matchesChannel(binding.match, channel)) {
|
||||||
return false;
|
return [];
|
||||||
}
|
}
|
||||||
return matchesAccountId(binding.match?.accountId, accountId);
|
if (!matchesAccountId(binding.match?.accountId, accountId)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
return [{ binding, match: normalizeBindingMatch(binding.match) }];
|
||||||
});
|
});
|
||||||
|
|
||||||
const dmScope = input.cfg.session?.dmScope ?? "main";
|
const dmScope = input.cfg.session?.dmScope ?? "main";
|
||||||
@@ -293,126 +282,84 @@ export function resolveAgentRoute(input: ResolveAgentRouteInput): ResolvedAgentR
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
if (peer) {
|
|
||||||
const peerMatch = bindings.find(
|
|
||||||
(b) =>
|
|
||||||
Boolean(b.match?.peer) &&
|
|
||||||
matchesBindingScope({
|
|
||||||
match: b.match,
|
|
||||||
peer,
|
|
||||||
guildId,
|
|
||||||
teamId,
|
|
||||||
memberRoleIds,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
if (peerMatch) {
|
|
||||||
return choose(peerMatch.agentId, "binding.peer");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Thread parent inheritance: if peer (thread) didn't match, check parent peer binding
|
// Thread parent inheritance: if peer (thread) didn't match, check parent peer binding
|
||||||
const parentPeer = input.parentPeer
|
const parentPeer = input.parentPeer
|
||||||
? { kind: input.parentPeer.kind, id: normalizeId(input.parentPeer.id) }
|
? { kind: input.parentPeer.kind, id: normalizeId(input.parentPeer.id) }
|
||||||
: null;
|
: null;
|
||||||
if (parentPeer && parentPeer.id) {
|
const baseScope = {
|
||||||
const parentPeerMatch = bindings.find(
|
guildId,
|
||||||
(b) =>
|
teamId,
|
||||||
Boolean(b.match?.peer) &&
|
memberRoleIds: memberRoleIdSet,
|
||||||
matchesBindingScope({
|
};
|
||||||
match: b.match,
|
|
||||||
peer: parentPeer,
|
const tiers: Array<{
|
||||||
guildId,
|
matchedBy: Exclude<ResolvedAgentRoute["matchedBy"], "default">;
|
||||||
teamId,
|
enabled: boolean;
|
||||||
memberRoleIds,
|
scopePeer: RoutePeer | null;
|
||||||
|
predicate: (candidate: EvaluatedBinding) => boolean;
|
||||||
|
}> = [
|
||||||
|
{
|
||||||
|
matchedBy: "binding.peer",
|
||||||
|
enabled: Boolean(peer),
|
||||||
|
scopePeer: peer,
|
||||||
|
predicate: (candidate) => candidate.match.peer.state === "valid",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
matchedBy: "binding.peer.parent",
|
||||||
|
enabled: Boolean(parentPeer && parentPeer.id),
|
||||||
|
scopePeer: parentPeer && parentPeer.id ? parentPeer : null,
|
||||||
|
predicate: (candidate) => candidate.match.peer.state === "valid",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
matchedBy: "binding.guild+roles",
|
||||||
|
enabled: Boolean(guildId && memberRoleIds.length > 0),
|
||||||
|
scopePeer: peer,
|
||||||
|
predicate: (candidate) =>
|
||||||
|
hasGuildConstraint(candidate.match) && hasRolesConstraint(candidate.match),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
matchedBy: "binding.guild",
|
||||||
|
enabled: Boolean(guildId),
|
||||||
|
scopePeer: peer,
|
||||||
|
predicate: (candidate) =>
|
||||||
|
hasGuildConstraint(candidate.match) && !hasRolesConstraint(candidate.match),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
matchedBy: "binding.team",
|
||||||
|
enabled: Boolean(teamId),
|
||||||
|
scopePeer: peer,
|
||||||
|
predicate: (candidate) => hasTeamConstraint(candidate.match),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
matchedBy: "binding.account",
|
||||||
|
enabled: true,
|
||||||
|
scopePeer: peer,
|
||||||
|
predicate: (candidate) => candidate.match.accountPattern !== "*",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
matchedBy: "binding.channel",
|
||||||
|
enabled: true,
|
||||||
|
scopePeer: peer,
|
||||||
|
predicate: (candidate) => candidate.match.accountPattern === "*",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const tier of tiers) {
|
||||||
|
if (!tier.enabled) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const matched = bindings.find(
|
||||||
|
(candidate) =>
|
||||||
|
tier.predicate(candidate) &&
|
||||||
|
matchesBindingScope(candidate.match, {
|
||||||
|
...baseScope,
|
||||||
|
peer: tier.scopePeer,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
if (parentPeerMatch) {
|
if (matched) {
|
||||||
return choose(parentPeerMatch.agentId, "binding.peer.parent");
|
return choose(matched.binding.agentId, tier.matchedBy);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (guildId && memberRoleIds.length > 0) {
|
|
||||||
const guildRolesMatch = bindings.find(
|
|
||||||
(b) =>
|
|
||||||
hasGuildConstraint(b.match) &&
|
|
||||||
hasRolesConstraint(b.match) &&
|
|
||||||
matchesBindingScope({
|
|
||||||
match: b.match,
|
|
||||||
peer,
|
|
||||||
guildId,
|
|
||||||
teamId,
|
|
||||||
memberRoleIds,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
if (guildRolesMatch) {
|
|
||||||
return choose(guildRolesMatch.agentId, "binding.guild+roles");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (guildId) {
|
|
||||||
const guildMatch = bindings.find(
|
|
||||||
(b) =>
|
|
||||||
hasGuildConstraint(b.match) &&
|
|
||||||
!hasRolesConstraint(b.match) &&
|
|
||||||
matchesBindingScope({
|
|
||||||
match: b.match,
|
|
||||||
peer,
|
|
||||||
guildId,
|
|
||||||
teamId,
|
|
||||||
memberRoleIds,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
if (guildMatch) {
|
|
||||||
return choose(guildMatch.agentId, "binding.guild");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (teamId) {
|
|
||||||
const teamMatch = bindings.find(
|
|
||||||
(b) =>
|
|
||||||
hasTeamConstraint(b.match) &&
|
|
||||||
matchesBindingScope({
|
|
||||||
match: b.match,
|
|
||||||
peer,
|
|
||||||
guildId,
|
|
||||||
teamId,
|
|
||||||
memberRoleIds,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
if (teamMatch) {
|
|
||||||
return choose(teamMatch.agentId, "binding.team");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const accountMatch = bindings.find(
|
|
||||||
(b) =>
|
|
||||||
b.match?.accountId?.trim() !== "*" &&
|
|
||||||
matchesBindingScope({
|
|
||||||
match: b.match,
|
|
||||||
peer,
|
|
||||||
guildId,
|
|
||||||
teamId,
|
|
||||||
memberRoleIds,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
if (accountMatch) {
|
|
||||||
return choose(accountMatch.agentId, "binding.account");
|
|
||||||
}
|
|
||||||
|
|
||||||
const anyAccountMatch = bindings.find(
|
|
||||||
(b) =>
|
|
||||||
b.match?.accountId?.trim() === "*" &&
|
|
||||||
matchesBindingScope({
|
|
||||||
match: b.match,
|
|
||||||
peer,
|
|
||||||
guildId,
|
|
||||||
teamId,
|
|
||||||
memberRoleIds,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
if (anyAccountMatch) {
|
|
||||||
return choose(anyAccountMatch.agentId, "binding.channel");
|
|
||||||
}
|
|
||||||
|
|
||||||
return choose(resolveDefaultAgentId(input.cfg), "default");
|
return choose(resolveDefaultAgentId(input.cfg), "default");
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user