diff --git a/src/commands/doctor/shared/preview-warnings.test.ts b/src/commands/doctor/shared/preview-warnings.test.ts index e3186b6e1f5..1f5c40fcba5 100644 --- a/src/commands/doctor/shared/preview-warnings.test.ts +++ b/src/commands/doctor/shared/preview-warnings.test.ts @@ -613,4 +613,43 @@ describe("doctor preview warnings", () => { expect(warnings.join("\n")).not.toContain("commander"); expect(warnings.join("\n")).not.toContain("discord"); }); + + it("warns for default-routed traffic when a channel only has scoped routes", () => { + const warnings = collectChannelBoundMessageToolPolicyWarnings({ + channels: { + discord: {}, + }, + agents: { + list: [ + { + id: "main", + default: true, + tools: { + allow: ["read"], + }, + }, + { + id: "commander", + tools: { + profile: "messaging", + }, + }, + ], + }, + bindings: [ + { + agentId: "commander", + match: { + channel: "discord", + accountId: "workspace-1", + }, + }, + ], + }); + + expect(warnings).toEqual([ + expect.stringContaining('Agent "main" is routed from channel "discord"'), + ]); + expect(warnings.join("\n")).not.toContain("commander"); + }); }); diff --git a/src/commands/doctor/shared/preview-warnings.ts b/src/commands/doctor/shared/preview-warnings.ts index 68b2dc23836..9e65404948a 100644 --- a/src/commands/doctor/shared/preview-warnings.ts +++ b/src/commands/doctor/shared/preview-warnings.ts @@ -222,6 +222,14 @@ function formatChannelList(channels: string[]): string { .join(", ")}, and ${channels.length - 2} more`; } +function isUnscopedChannelRouteBinding(binding: AgentRouteBinding): boolean { + const match = binding.match; + const accountId = match.accountId?.trim(); + const hasScopedAccount = Boolean(accountId && accountId !== "*"); + const hasRoles = Array.isArray(match.roles) && match.roles.length > 0; + return !hasScopedAccount && !match.peer && !match.guildId && !match.teamId && !hasRoles; +} + function collectBoundChannelTargets(cfg: OpenClawConfig): Array<{ agentId: string; channels: string[]; @@ -242,18 +250,18 @@ function collectBoundChannelTargets(cfg: OpenClawConfig): Array<{ }; const routeBindings: AgentRouteBinding[] = listRouteBindings(cfg); - const explicitlyBoundChannels = new Set(); + const fullyCoveredChannels = new Set(); for (const binding of routeBindings) { const channel = binding.match.channel.trim(); add(binding.agentId, channel); - if (channel) { - explicitlyBoundChannels.add(channel); + if (channel && isUnscopedChannelRouteBinding(binding)) { + fullyCoveredChannels.add(channel); } } const defaultAgentId = resolveDefaultAgentId(cfg); for (const channel of listConfiguredChannelIds(cfg)) { - if (!explicitlyBoundChannels.has(channel)) { + if (!fullyCoveredChannels.has(channel)) { add(defaultAgentId, channel); } }