diff --git a/src/routing/binding-scope.ts b/src/routing/binding-scope.ts index 40e1daa14f0..998edd47b4e 100644 --- a/src/routing/binding-scope.ts +++ b/src/routing/binding-scope.ts @@ -1,3 +1,8 @@ +import { normalizeChatChannelId } from "../channels/ids.js"; +import type { AgentRouteBinding } from "../config/types.agents.js"; +import { normalizeLowercaseStringOrEmpty } from "../shared/string-coerce.js"; +import { normalizeAccountId, normalizeAgentId } from "./session-key.js"; + export type RouteBindingScopeConstraint = { guildId?: string | null; teamId?: string | null; @@ -11,6 +16,12 @@ export type RouteBindingScope = { memberRoleIds?: Iterable | null; }; +export type NormalizedRouteBindingMatch = { + agentId: string; + accountId: string; + channelId: string; +}; + export function normalizeRouteBindingId(value: unknown): string { if (typeof value === "string") { return value.trim(); @@ -25,6 +36,40 @@ export function normalizeRouteBindingRoles(value: string[] | null | undefined): return Array.isArray(value) && value.length > 0 ? value : null; } +export function normalizeRouteBindingChannelId(raw?: string | null): string | null { + const normalized = normalizeChatChannelId(raw); + if (normalized) { + return normalized; + } + const fallback = normalizeLowercaseStringOrEmpty(raw); + return fallback || null; +} + +export function resolveNormalizedRouteBindingMatch( + binding: AgentRouteBinding, +): NormalizedRouteBindingMatch | null { + if (!binding || typeof binding !== "object") { + return null; + } + const match = binding.match; + if (!match || typeof match !== "object") { + return null; + } + const channelId = normalizeRouteBindingChannelId(match.channel); + if (!channelId) { + return null; + } + const accountId = typeof match.accountId === "string" ? match.accountId.trim() : ""; + if (!accountId || accountId === "*") { + return null; + } + return { + agentId: normalizeAgentId(binding.agentId), + accountId: normalizeAccountId(accountId), + channelId, + }; +} + function scopeIdMatches(params: { constraint: string | null | undefined; exact: string; diff --git a/src/routing/bindings.ts b/src/routing/bindings.ts index 23e3c50e378..4e9b1c40929 100644 --- a/src/routing/bindings.ts +++ b/src/routing/bindings.ts @@ -1,59 +1,25 @@ import { resolveDefaultAgentId } from "../agents/agent-scope.js"; -import { normalizeChatChannelId } from "../channels/ids.js"; import { listRouteBindings } from "../config/bindings.js"; import type { AgentRouteBinding } from "../config/types.agents.js"; import type { OpenClawConfig } from "../config/types.openclaw.js"; -import { normalizeLowercaseStringOrEmpty } from "../shared/string-coerce.js"; -import { normalizeAccountId, normalizeAgentId } from "./session-key.js"; - -function normalizeBindingChannelId(raw?: string | null): string | null { - const normalized = normalizeChatChannelId(raw); - if (normalized) { - return normalized; - } - const fallback = normalizeLowercaseStringOrEmpty(raw); - return fallback || null; -} +import { + normalizeRouteBindingChannelId, + resolveNormalizedRouteBindingMatch, +} from "./binding-scope.js"; +import { normalizeAgentId } from "./session-key.js"; export function listBindings(cfg: OpenClawConfig): AgentRouteBinding[] { return listRouteBindings(cfg); } -function resolveNormalizedBindingMatch(binding: AgentRouteBinding): { - agentId: string; - accountId: string; - channelId: string; -} | null { - if (!binding || typeof binding !== "object") { - return null; - } - const match = binding.match; - if (!match || typeof match !== "object") { - return null; - } - const channelId = normalizeBindingChannelId(match.channel); - if (!channelId) { - return null; - } - const accountId = typeof match.accountId === "string" ? match.accountId.trim() : ""; - if (!accountId || accountId === "*") { - return null; - } - return { - agentId: normalizeAgentId(binding.agentId), - accountId: normalizeAccountId(accountId), - channelId, - }; -} - export function listBoundAccountIds(cfg: OpenClawConfig, channelId: string): string[] { - const normalizedChannel = normalizeBindingChannelId(channelId); + const normalizedChannel = normalizeRouteBindingChannelId(channelId); if (!normalizedChannel) { return []; } const ids = new Set(); for (const binding of listBindings(cfg)) { - const resolved = resolveNormalizedBindingMatch(binding); + const resolved = resolveNormalizedRouteBindingMatch(binding); if (!resolved || resolved.channelId !== normalizedChannel) { continue; } @@ -66,13 +32,13 @@ export function resolveDefaultAgentBoundAccountId( cfg: OpenClawConfig, channelId: string, ): string | null { - const normalizedChannel = normalizeBindingChannelId(channelId); + const normalizedChannel = normalizeRouteBindingChannelId(channelId); if (!normalizedChannel) { return null; } const defaultAgentId = normalizeAgentId(resolveDefaultAgentId(cfg)); for (const binding of listBindings(cfg)) { - const resolved = resolveNormalizedBindingMatch(binding); + const resolved = resolveNormalizedRouteBindingMatch(binding); if ( !resolved || resolved.channelId !== normalizedChannel || @@ -88,7 +54,7 @@ export function resolveDefaultAgentBoundAccountId( export function buildChannelAccountBindings(cfg: OpenClawConfig) { const map = new Map>(); for (const binding of listBindings(cfg)) { - const resolved = resolveNormalizedBindingMatch(binding); + const resolved = resolveNormalizedRouteBindingMatch(binding); if (!resolved) { continue; } diff --git a/src/routing/bound-account-read.ts b/src/routing/bound-account-read.ts index c98590d8de7..26b58cb533e 100644 --- a/src/routing/bound-account-read.ts +++ b/src/routing/bound-account-read.ts @@ -1,27 +1,18 @@ import { normalizeChatType, type ChatType } from "../channels/chat-type.js"; -import { normalizeChatChannelId } from "../channels/ids.js"; import { listRouteBindings } from "../config/bindings.js"; import type { AgentRouteBinding } from "../config/types.agents.js"; import type { OpenClawConfig } from "../config/types.openclaw.js"; -import { normalizeLowercaseStringOrEmpty } from "../shared/string-coerce.js"; import { + normalizeRouteBindingChannelId, normalizeRouteBindingId, normalizeRouteBindingRoles, + resolveNormalizedRouteBindingMatch, routeBindingScopeMatches, } from "./binding-scope.js"; import { peerKindMatches } from "./peer-kind-match.js"; -import { normalizeAccountId, normalizeAgentId } from "./session-key.js"; +import { normalizeAgentId } from "./session-key.js"; -function normalizeBindingChannelId(raw?: string | null): string | null { - const normalized = normalizeChatChannelId(raw); - if (normalized) { - return normalized; - } - const fallback = normalizeLowercaseStringOrEmpty(raw); - return fallback || null; -} - -function resolveNormalizedBindingMatch(binding: AgentRouteBinding): { +function resolveNormalizedBoundAccountMatch(binding: AgentRouteBinding): { agentId: string; accountId: string; channelId: string; @@ -31,27 +22,15 @@ function resolveNormalizedBindingMatch(binding: AgentRouteBinding): { teamId?: string | null; roles?: string[] | null; } | null { - if (!binding || typeof binding !== "object") { - return null; - } + const baseMatch = resolveNormalizedRouteBindingMatch(binding); const match = binding.match; - if (!match || typeof match !== "object") { - return null; - } - const channelId = normalizeBindingChannelId(match.channel); - if (!channelId) { - return null; - } - const accountId = typeof match.accountId === "string" ? match.accountId.trim() : ""; - if (!accountId || accountId === "*") { + if (!baseMatch || !match || typeof match !== "object") { return null; } const peerId = match.peer && typeof match.peer.id === "string" ? match.peer.id.trim() : undefined; const peerKind = match.peer ? normalizeChatType(match.peer.kind) : undefined; return { - agentId: normalizeAgentId(binding.agentId), - accountId: normalizeAccountId(accountId), - channelId, + ...baseMatch, peerId: peerId || undefined, peerKind: peerKind ?? undefined, guildId: normalizeRouteBindingId(match.guildId) || null, @@ -88,7 +67,7 @@ export function resolveFirstBoundAccountId(params: { groupSpace?: string | null; memberRoleIds?: string[]; }): string | undefined { - const normalizedChannel = normalizeBindingChannelId(params.channelId); + const normalizedChannel = normalizeRouteBindingChannelId(params.channelId); if (!normalizedChannel) { return undefined; } @@ -103,7 +82,7 @@ export function resolveFirstBoundAccountId(params: { let wildcardPeerMatch: string | undefined; let channelOnlyFallback: string | undefined; for (const binding of listRouteBindings(params.cfg)) { - const resolved = resolveNormalizedBindingMatch(binding); + const resolved = resolveNormalizedBoundAccountMatch(binding); if ( !resolved || resolved.channelId !== normalizedChannel ||