fix: explain memory compaction tool allowlist warnings

Fixes #82941.
This commit is contained in:
Galin Iliev
2026-05-16 23:25:00 -07:00
committed by GitHub
parent aaadf721e3
commit aca258a8a9
4 changed files with 68 additions and 8 deletions

View File

@@ -15,6 +15,7 @@ Docs: https://docs.openclaw.ai
- CLI/plugins: have `openclaw plugins doctor` warn when a configured runtime needs a missing owner plugin, sharing the same install mapping as `openclaw doctor --fix`. Fixes #81326. (#81674) Thanks @Zavianx.
- Agents/Codex: route OpenAI runs that resolve to `openai-codex` through the Codex provider and bootstrap OpenClaw's stored OAuth profile into the Codex harness when the harness owns transport, so `openai/*` model refs no longer fail with `No API key found for openai-codex` despite an existing Codex OAuth profile. (#82864) Thanks @ragesaq.
- Agents/ACP: distinguish prompt-submitted and runtime-active child stalls from true interactive waits, including redacted proxy-env diagnostics for Codex ACP no-output runs. Fixes #44810.
- Agents/memory: explain that memory-triggered compaction exposes only `read` and append-only `write` when configured core tools are unavailable in `tools.allow` warnings. Fixes #82941. Thanks @galiniliev.
- Agents/skills: apply the full effective tool policy pipeline to inline `command-dispatch: tool` skill dispatch before owner-only filtering, preserving configured allow, deny, sandbox, sender, group, and subagent restrictions. (#78525)
- Channel accounts: keep top-level default channel accounts visible when named accounts are added alongside default credential material, so mixed legacy/new account configs keep resolving `default` instead of silently dropping it.
- Codex/Telegram: synthesize native Codex tool progress from final turn snapshots so Telegram `/verbose` stays visible when command events arrive only at completion.

View File

@@ -990,6 +990,10 @@ export function createOpenClawCodingTools(options?: {
toolsForMemoryFlush.push(tool);
}
}
const unavailableCoreToolReason =
isMemoryFlushRun && memoryFlushWritePath
? "memory-triggered compaction runs expose only read and append-only write"
: undefined;
const toolsForMessageProvider = filterToolsByMessageProvider(
toolsForMemoryFlush,
options?.messageProvider,
@@ -1031,10 +1035,19 @@ export function createOpenClawCodingTools(options?: {
groupPolicy: groupPolicyWithToolSearchControls,
senderPolicy: senderPolicyWithToolSearchControls,
agentId,
unavailableCoreToolReason,
}),
{ policy: sandboxToolPolicyWithToolSearchControls, label: "sandbox tools.allow" },
{ policy: subagentPolicyWithToolSearchControls, label: "subagent tools.allow" },
{ policy: inheritedToolPolicy, label: "inherited tools" },
{
policy: sandboxToolPolicyWithToolSearchControls,
label: "sandbox tools.allow",
unavailableCoreToolReason,
},
{
policy: subagentPolicyWithToolSearchControls,
label: "subagent tools.allow",
unavailableCoreToolReason,
},
{ policy: inheritedToolPolicy, label: "inherited tools", unavailableCoreToolReason },
],
});
if (shouldInheritEffectiveToolAllowlist) {

View File

@@ -13,6 +13,7 @@ function runAllowlistWarningStep(params: {
label: string;
suppressUnavailableCoreToolWarning?: boolean;
suppressUnavailableCoreToolWarningAllowlist?: string[];
unavailableCoreToolReason?: string;
}) {
const warnings: string[] = [];
const tools = [{ name: "exec" }] as unknown as DummyTool[];
@@ -28,6 +29,7 @@ function runAllowlistWarningStep(params: {
suppressUnavailableCoreToolWarning: params.suppressUnavailableCoreToolWarning,
suppressUnavailableCoreToolWarningAllowlist:
params.suppressUnavailableCoreToolWarningAllowlist,
unavailableCoreToolReason: params.unavailableCoreToolReason,
},
],
});
@@ -107,6 +109,18 @@ describe("tool-policy-pipeline", () => {
]);
});
test("includes the active reason for unavailable core tool warnings", () => {
const warnings = runAllowlistWarningStep({
allow: ["apply_patch", "wat"],
label: "tools.allow",
unavailableCoreToolReason:
"memory-triggered compaction runs expose only read and append-only write",
});
expect(warnings).toEqual([
"tools: tools.allow allowlist contains unknown entries (apply_patch, wat). Some entries are shipped core tools but unavailable here: memory-triggered compaction runs expose only read and append-only write; other entries won't match any tool unless the plugin is enabled.",
]);
});
test("default profile steps suppress unavailable baseline profile entries", () => {
const warnings: string[] = [];
const profilePolicy = resolveToolProfilePolicy("coding");

View File

@@ -34,6 +34,7 @@ export type ToolPolicyPipelineStep = {
stripPluginOnlyAllowlist?: boolean;
suppressUnavailableCoreToolWarning?: boolean;
suppressUnavailableCoreToolWarningAllowlist?: string[];
unavailableCoreToolReason?: string;
};
export function buildDefaultToolPolicyPipelineSteps(params: {
@@ -50,16 +51,19 @@ export function buildDefaultToolPolicyPipelineSteps(params: {
groupPolicy?: ToolPolicyLike;
senderPolicy?: ToolPolicyLike;
agentId?: string;
unavailableCoreToolReason?: string;
}): ToolPolicyPipelineStep[] {
const agentId = params.agentId?.trim();
const profile = params.profile?.trim();
const providerProfile = params.providerProfile?.trim();
const unavailableCoreToolReason = params.unavailableCoreToolReason?.trim();
return [
{
policy: params.profilePolicy,
label: profile ? `tools.profile (${profile})` : "tools.profile",
stripPluginOnlyAllowlist: true,
suppressUnavailableCoreToolWarningAllowlist: params.profileUnavailableCoreWarningAllowlist,
unavailableCoreToolReason,
},
{
policy: params.providerProfilePolicy,
@@ -69,25 +73,44 @@ export function buildDefaultToolPolicyPipelineSteps(params: {
stripPluginOnlyAllowlist: true,
suppressUnavailableCoreToolWarningAllowlist:
params.providerProfileUnavailableCoreWarningAllowlist,
unavailableCoreToolReason,
},
{
policy: params.globalPolicy,
label: "tools.allow",
stripPluginOnlyAllowlist: true,
unavailableCoreToolReason,
},
{ policy: params.globalPolicy, label: "tools.allow", stripPluginOnlyAllowlist: true },
{
policy: params.globalProviderPolicy,
label: "tools.byProvider.allow",
stripPluginOnlyAllowlist: true,
unavailableCoreToolReason,
},
{
policy: params.agentPolicy,
label: agentId ? `agents.${agentId}.tools.allow` : "agent tools.allow",
stripPluginOnlyAllowlist: true,
unavailableCoreToolReason,
},
{
policy: params.agentProviderPolicy,
label: agentId ? `agents.${agentId}.tools.byProvider.allow` : "agent tools.byProvider.allow",
stripPluginOnlyAllowlist: true,
unavailableCoreToolReason,
},
{
policy: params.groupPolicy,
label: "group tools.allow",
stripPluginOnlyAllowlist: true,
unavailableCoreToolReason,
},
{
policy: params.senderPolicy,
label: "tools.toolsBySender",
stripPluginOnlyAllowlist: true,
unavailableCoreToolReason,
},
{ policy: params.groupPolicy, label: "group tools.allow", stripPluginOnlyAllowlist: true },
{ policy: params.senderPolicy, label: "tools.toolsBySender", stripPluginOnlyAllowlist: true },
];
}
@@ -145,6 +168,7 @@ export function applyToolPolicyPipeline(params: {
pluginOnlyAllowlist: resolved.pluginOnlyAllowlist,
hasGatedCoreEntries: warnableGatedCoreEntries.length > 0,
hasOtherEntries: otherEntries.length > 0,
unavailableCoreToolReason: step.unavailableCoreToolReason,
});
const warning = `tools: ${step.label} allowlist contains unknown entries (${entries}). ${suffix}`;
if (rememberToolPolicyWarning(warning)) {
@@ -172,15 +196,23 @@ function describeUnknownAllowlistSuffix(params: {
pluginOnlyAllowlist: boolean;
hasGatedCoreEntries: boolean;
hasOtherEntries: boolean;
unavailableCoreToolReason?: string;
}): string {
const preface = params.pluginOnlyAllowlist
? "Allowlist contains only plugin entries; core tools will not be available."
: "";
const unavailableCoreToolReason = params.unavailableCoreToolReason?.trim();
const unavailableCoreDetail = unavailableCoreToolReason
? `These entries are shipped core tools but unavailable here: ${unavailableCoreToolReason}.`
: "These entries are shipped core tools but unavailable in the current runtime/provider/model/config.";
const mixedUnavailableCoreDetail = unavailableCoreToolReason
? `Some entries are shipped core tools but unavailable here: ${unavailableCoreToolReason}; other entries won't match any tool unless the plugin is enabled.`
: "Some entries are shipped core tools but unavailable in the current runtime/provider/model/config; other entries won't match any tool unless the plugin is enabled.";
const detail =
params.hasGatedCoreEntries && params.hasOtherEntries
? "Some entries are shipped core tools but unavailable in the current runtime/provider/model/config; other entries won't match any tool unless the plugin is enabled."
? mixedUnavailableCoreDetail
: params.hasGatedCoreEntries
? "These entries are shipped core tools but unavailable in the current runtime/provider/model/config."
? unavailableCoreDetail
: "These entries won't match any tool unless the plugin is enabled.";
return preface ? `${preface} ${detail}` : detail;
}