diff --git a/src/cron/isolated-agent/delivery-target.ts b/src/cron/isolated-agent/delivery-target.ts index b97b1afcef4..20da3a12667 100644 --- a/src/cron/isolated-agent/delivery-target.ts +++ b/src/cron/isolated-agent/delivery-target.ts @@ -11,8 +11,8 @@ import { resolveSessionDeliveryTarget } from "../../infra/outbound/targets-sessi import type { OutboundChannel } from "../../infra/outbound/targets.js"; import { readChannelAllowFromStoreEntriesSync } from "../../pairing/allow-from-store-read.js"; import { mapAllowFromEntries } from "../../plugin-sdk/channel-config-helpers.js"; -import { buildChannelAccountBindings } from "../../routing/bindings.js"; -import { normalizeAccountId, normalizeAgentId } from "../../routing/session-key.js"; +import { resolveFirstBoundAccountId } from "../../routing/bound-account-read.js"; +import { normalizeAccountId } from "../../routing/session-key.js"; export type DeliveryTargetResolution = | { @@ -140,12 +140,7 @@ export async function resolveDeliveryTarget( : undefined; let accountId = explicitAccountId ?? resolved.accountId; if (!accountId && channel) { - const bindings = buildChannelAccountBindings(cfg); - const byAgent = bindings.get(channel); - const boundAccounts = byAgent?.get(normalizeAgentId(agentId)); - if (boundAccounts && boundAccounts.length > 0) { - accountId = boundAccounts[0]; - } + accountId = resolveFirstBoundAccountId({ cfg, channelId: channel, agentId }); } // job.delivery.accountId takes highest precedence — explicitly set by the job author. diff --git a/src/routing/bound-account-read.ts b/src/routing/bound-account-read.ts new file mode 100644 index 00000000000..93212b155e2 --- /dev/null +++ b/src/routing/bound-account-read.ts @@ -0,0 +1,65 @@ +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; +} + +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 resolveFirstBoundAccountId(params: { + cfg: OpenClawConfig; + channelId: string; + agentId: string; +}): string | undefined { + const normalizedChannel = normalizeBindingChannelId(params.channelId); + if (!normalizedChannel) { + return undefined; + } + const normalizedAgentId = normalizeAgentId(params.agentId); + for (const binding of listRouteBindings(params.cfg)) { + const resolved = resolveNormalizedBindingMatch(binding); + if ( + resolved && + resolved.channelId === normalizedChannel && + resolved.agentId === normalizedAgentId + ) { + return resolved.accountId; + } + } + return undefined; +}