




















@@ -1,11 +1,16 @@
11import { resolveAgentConfig } from "../../../agents/agent-scope-config.js";
2+import { DEFAULT_MODEL, DEFAULT_PROVIDER } from "../../../agents/defaults.js";
3+import { parseModelRef } from "../../../agents/model-selection-normalize.js";
4+import { normalizeProviderId } from "../../../agents/provider-id.js";
25import { pickSandboxToolPolicy } from "../../../agents/sandbox-tool-policy.js";
36import { isToolAllowedByPolicies } from "../../../agents/tool-policy-match.js";
47import { mergeAlsoAllowPolicy, resolveToolProfilePolicy } from "../../../agents/tool-policy.js";
8+import { resolveAgentModelPrimaryValue } from "../../../config/model-input.js";
59import type { OpenClawConfig } from "../../../config/types.openclaw.js";
610import type { AgentToolsConfig, ToolsConfig } from "../../../config/types.tools.js";
711import { collectChannelRouteTargets } from "../../../routing/channel-route-targets.js";
812import { createLazyImportLoader } from "../../../shared/lazy-promise.js";
13+import { normalizeLowercaseStringOrEmpty } from "../../../shared/string-coerce.js";
9141015type ChannelDoctorModule = typeof import("./channel-doctor.js");
1116@@ -88,40 +93,171 @@ function hasConfiguredSafeBins(cfg: OpenClawConfig): boolean {
8893}
89949095type VisibleReplyPolicyProvenance = "default" | "global-explicit" | "group-explicit";
96+type ToolPolicyConfig = {
97+allow?: string[];
98+alsoAllow?: string[];
99+deny?: string[];
100+profile?: string;
101+};
102+103+function normalizeProviderPolicyKey(value: string): string {
104+const normalized = normalizeLowercaseStringOrEmpty(value);
105+const slashIndex = normalized.indexOf("/");
106+if (slashIndex <= 0) {
107+return normalizeProviderId(normalized);
108+}
109+const provider = normalizeProviderId(normalized.slice(0, slashIndex));
110+const modelId = normalized.slice(slashIndex + 1);
111+return modelId ? `${provider}/${modelId}` : provider;
112+}
113+114+function isCanonicalProviderPolicyKey(value: string): boolean {
115+return normalizeLowercaseStringOrEmpty(value) === normalizeProviderPolicyKey(value);
116+}
117+118+function resolveProviderToolPolicy(params: {
119+byProvider?: Record<string, ToolPolicyConfig>;
120+modelProvider: string;
121+modelId: string;
122+}): ToolPolicyConfig | undefined {
123+if (!params.byProvider) {
124+return undefined;
125+}
126+const lookup = new Map<string, { canonical: boolean; value: ToolPolicyConfig }>();
127+for (const [key, value] of Object.entries(params.byProvider)) {
128+const normalized = normalizeProviderPolicyKey(key);
129+if (!normalized) {
130+continue;
131+}
132+const canonical = isCanonicalProviderPolicyKey(key);
133+const existing = lookup.get(normalized);
134+if (!existing || (canonical && !existing.canonical)) {
135+lookup.set(normalized, { canonical, value });
136+}
137+}
138+139+const provider = normalizeProviderPolicyKey(params.modelProvider);
140+const modelId = normalizeLowercaseStringOrEmpty(params.modelId);
141+const fullModelId = modelId ? `${provider}/${modelId}` : undefined;
142+return (fullModelId ? lookup.get(fullModelId)?.value : undefined) ?? lookup.get(provider)?.value;
143+}
9114492145function resolveMessageToolAvailability(params: {
146+cfg: OpenClawConfig;
147+agentId?: string;
93148globalTools?: ToolsConfig;
94149agentTools?: AgentToolsConfig;
150+runtimeAlsoAllow?: string[];
95151}): boolean {
152+const agentConfig = params.agentId ? resolveAgentConfig(params.cfg, params.agentId) : undefined;
153+const modelRef = resolvePrimaryModelRef(params.cfg, agentConfig?.model);
154+const providerPolicy = resolveProviderToolPolicy({
155+byProvider: params.globalTools?.byProvider,
156+modelProvider: modelRef.provider,
157+modelId: modelRef.model,
158+});
159+const agentProviderPolicy = resolveProviderToolPolicy({
160+byProvider: params.agentTools?.byProvider,
161+modelProvider: modelRef.provider,
162+modelId: modelRef.model,
163+});
96164const profile = params.agentTools?.profile ?? params.globalTools?.profile;
97-const profileAlsoAllow = Array.isArray(params.agentTools?.alsoAllow)
165+const configuredAlsoAllow = Array.isArray(params.agentTools?.alsoAllow)
98166 ? params.agentTools.alsoAllow
99167 : Array.isArray(params.globalTools?.alsoAllow)
100168 ? params.globalTools.alsoAllow
101- : undefined;
169+ : [];
170+const providerAlsoAllow = Array.isArray(agentProviderPolicy?.alsoAllow)
171+ ? agentProviderPolicy.alsoAllow
172+ : Array.isArray(providerPolicy?.alsoAllow)
173+ ? providerPolicy.alsoAllow
174+ : [];
175+const profileAlsoAllow = [...configuredAlsoAllow, ...(params.runtimeAlsoAllow ?? [])];
176+const providerProfileAlsoAllow = [...providerAlsoAllow, ...(params.runtimeAlsoAllow ?? [])];
102177const profilePolicy = mergeAlsoAllowPolicy(resolveToolProfilePolicy(profile), profileAlsoAllow);
178+const providerProfilePolicy = mergeAlsoAllowPolicy(
179+resolveToolProfilePolicy(agentProviderPolicy?.profile ?? providerPolicy?.profile),
180+providerProfileAlsoAllow,
181+);
103182return isToolAllowedByPolicies("message", [
104183profilePolicy,
184+providerProfilePolicy,
185+pickSandboxToolPolicy(providerPolicy),
186+pickSandboxToolPolicy(agentProviderPolicy),
105187pickSandboxToolPolicy(params.globalTools),
106188pickSandboxToolPolicy(params.agentTools),
107189]);
108190}
109191110-function collectMessageToolUnavailableTargets(cfg: OpenClawConfig): string[] {
192+const SOURCE_REPLY_RUNTIME_MESSAGE_ALLOW = ["message"];
193+194+function resolvePrimaryModelRef(
195+cfg: OpenClawConfig,
196+agentModel?: NonNullable<ReturnType<typeof resolveAgentConfig>>["model"],
197+): { provider: string; model: string } {
198+const raw =
199+resolveAgentModelPrimaryValue(agentModel) ??
200+resolveAgentModelPrimaryValue(cfg.agents?.defaults?.model) ??
201+DEFAULT_MODEL;
202+return (
203+parseModelRef(raw, DEFAULT_PROVIDER, { allowPluginNormalization: false }) ?? {
204+provider: DEFAULT_PROVIDER,
205+model: DEFAULT_MODEL,
206+}
207+);
208+}
209+210+function resolveSourceReplyMessageToolAvailability(params: {
211+cfg: OpenClawConfig;
212+agentId?: string;
213+globalTools?: ToolsConfig;
214+agentTools?: AgentToolsConfig;
215+}): boolean {
216+return resolveMessageToolAvailability({
217+ ...params,
218+runtimeAlsoAllow: SOURCE_REPLY_RUNTIME_MESSAGE_ALLOW,
219+});
220+}
221+222+function sourceReplyRuntimeMayAllowMessageTool(cfg: OpenClawConfig): boolean {
223+const groupPolicy = resolveGroupVisibleReplyProvenance(cfg);
224+if (hasChannels(cfg) && groupPolicy.value === "message_tool") {
225+return true;
226+}
227+if (cfg.messages?.visibleReplies === "message_tool") {
228+return true;
229+}
230+return false;
231+}
232+233+function collectMessageToolUnavailableTargets(
234+cfg: OpenClawConfig,
235+options: { sourceReplyRuntimeGrant?: boolean } = {},
236+): string[] {
111237const agents = listAgentRecords(cfg);
112238if (agents.length === 0) {
113-return resolveMessageToolAvailability({ globalTools: cfg.tools })
114- ? []
115- : ["default tool policy"];
239+const available = options.sourceReplyRuntimeGrant
240+ ? resolveSourceReplyMessageToolAvailability({ cfg, globalTools: cfg.tools })
241+ : resolveMessageToolAvailability({ cfg, globalTools: cfg.tools });
242+return available ? [] : ["default tool policy"];
116243}
117-return agents.flatMap((agent) =>
118-resolveMessageToolAvailability({
119-globalTools: cfg.tools,
120-agentTools: agent.tools as AgentToolsConfig | undefined,
121-})
122- ? []
123- : [`agent "${typeof agent.id === "string" ? agent.id : "unknown"}"`],
124-);
244+return agents.flatMap((agent) => {
245+const agentId = typeof agent.id === "string" ? agent.id : "unknown";
246+const available = options.sourceReplyRuntimeGrant
247+ ? resolveSourceReplyMessageToolAvailability({
248+ cfg,
249+ agentId,
250+globalTools: cfg.tools,
251+agentTools: agent.tools as AgentToolsConfig | undefined,
252+})
253+ : resolveMessageToolAvailability({
254+ cfg,
255+ agentId,
256+globalTools: cfg.tools,
257+agentTools: agent.tools as AgentToolsConfig | undefined,
258+});
259+return available ? [] : [`agent "${agentId}"`];
260+});
125261}
126262127263function resolveGroupVisibleReplyProvenance(cfg: OpenClawConfig): {
@@ -160,16 +296,16 @@ function formatTargets(targets: string[]): string {
160296}
161297162298export function collectVisibleReplyToolPolicyWarnings(cfg: OpenClawConfig): string[] {
163-const targets = collectMessageToolUnavailableTargets(cfg);
164-if (targets.length === 0) {
165-return [];
166-}
167299const groupPolicy = resolveGroupVisibleReplyProvenance(cfg);
168300const warnings: string[] = [];
169301if (groupPolicy.value === "message_tool") {
170302if (groupPolicy.provenance === "default" && !hasChannels(cfg)) {
171303return warnings;
172304}
305+const targets = collectMessageToolUnavailableTargets(cfg, { sourceReplyRuntimeGrant: true });
306+if (targets.length === 0) {
307+return warnings;
308+}
173309const targetSummary = formatTargets(targets);
174310if (groupPolicy.provenance === "default") {
175311warnings.push(
@@ -184,6 +320,10 @@ export function collectVisibleReplyToolPolicyWarnings(cfg: OpenClawConfig): stri
184320185321const globalVisibleReplies = cfg.messages?.visibleReplies;
186322if (globalVisibleReplies === "message_tool" && groupPolicy.path !== "messages.visibleReplies") {
323+const targets = collectMessageToolUnavailableTargets(cfg, { sourceReplyRuntimeGrant: true });
324+if (targets.length === 0) {
325+return warnings;
326+}
187327warnings.push(
188328`- messages.visibleReplies is set to "message_tool", but the message tool is unavailable for ${formatTargets(
189329 targets,
@@ -206,7 +346,21 @@ function formatChannelList(channels: string[]): string {
206346export function collectChannelBoundMessageToolPolicyWarnings(cfg: OpenClawConfig): string[] {
207347return collectChannelRouteTargets(cfg).flatMap((target) => {
208348const agentTools = resolveAgentConfig(cfg, target.agentId)?.tools;
209-if (resolveMessageToolAvailability({ globalTools: cfg.tools, agentTools })) {
349+const runtimeMayAllowMessage = sourceReplyRuntimeMayAllowMessageTool(cfg);
350+const messageToolAvailable = runtimeMayAllowMessage
351+ ? resolveSourceReplyMessageToolAvailability({
352+ cfg,
353+agentId: target.agentId,
354+globalTools: cfg.tools,
355+ agentTools,
356+})
357+ : resolveMessageToolAvailability({
358+ cfg,
359+agentId: target.agentId,
360+globalTools: cfg.tools,
361+ agentTools,
362+});
363+if (messageToolAvailable) {
210364return [];
211365}
212366return [
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。