From 4f2d24f463803e4a65df14ca4c78472b8f70957b Mon Sep 17 00:00:00 2001 From: Ayaan Zaidi Date: Tue, 21 Apr 2026 10:34:28 +0530 Subject: [PATCH] fix(agents): honor explicit cron tool allowlists --- .../pi-embedded-runner/run/attempt.test.ts | 11 ++++++++++ src/agents/pi-embedded-runner/run/attempt.ts | 20 +++++++++++-------- ...tools.create-openclaw-coding-tools.test.ts | 17 ++++++++++++++++ src/agents/pi-tools.ts | 8 ++++---- 4 files changed, 44 insertions(+), 12 deletions(-) diff --git a/src/agents/pi-embedded-runner/run/attempt.test.ts b/src/agents/pi-embedded-runner/run/attempt.test.ts index 9cf5fcb1a20..ace2e34d3e8 100644 --- a/src/agents/pi-embedded-runner/run/attempt.test.ts +++ b/src/agents/pi-embedded-runner/run/attempt.test.ts @@ -10,6 +10,7 @@ import { buildAfterTurnRuntimeContextFromUsage, composeSystemPromptWithHookContext, decodeHtmlEntitiesInObject, + applyEmbeddedAttemptToolsAllow, isPrimaryBootstrapRun, mergeOrphanedTrailingUserPrompt, prependSystemPromptAddition, @@ -61,6 +62,16 @@ async function invokeWrappedTestStream( return await Promise.resolve(wrappedFn({} as never, {} as never, {} as never)); } +describe("applyEmbeddedAttemptToolsAllow", () => { + it("keeps explicit toolsAllow authoritative after force-added tools are built", () => { + const tools = [{ name: "exec" }, { name: "read" }, { name: "message" }]; + + expect( + applyEmbeddedAttemptToolsAllow(tools, ["exec", "read"]).map((tool) => tool.name), + ).toEqual(["exec", "read"]); + }); +}); + describe("resolvePromptBuildHookResult", () => { function createLegacyOnlyHookRunner() { return { diff --git a/src/agents/pi-embedded-runner/run/attempt.ts b/src/agents/pi-embedded-runner/run/attempt.ts index 9540e0f4c91..b650ddd6c93 100644 --- a/src/agents/pi-embedded-runner/run/attempt.ts +++ b/src/agents/pi-embedded-runner/run/attempt.ts @@ -403,6 +403,17 @@ function summarizeSessionContext(messages: AgentMessage[]): { }; } +export function applyEmbeddedAttemptToolsAllow( + tools: T[], + toolsAllow?: string[], +): T[] { + if (!toolsAllow || toolsAllow.length === 0) { + return tools; + } + const allowSet = new Set(toolsAllow); + return tools.filter((tool) => allowSet.has(tool.name)); +} + export async function runEmbeddedAttempt( params: EmbeddedRunAttemptParams, ): Promise { @@ -536,14 +547,7 @@ export async function runEmbeddedAttempt( abortSessionForYield?.(); }, }); - if (params.toolsAllow && params.toolsAllow.length > 0) { - const allowSet = new Set(params.toolsAllow); - if (params.forceMessageTool) { - allowSet.add("message"); - } - return allTools.filter((tool) => allowSet.has(tool.name)); - } - return allTools; + return applyEmbeddedAttemptToolsAllow(allTools, params.toolsAllow); })(); const toolsEnabled = supportsModelTools(params.model); const bootstrapHasFileAccess = toolsEnabled && toolsRaw.some((tool) => tool.name === "read"); diff --git a/src/agents/pi-tools.create-openclaw-coding-tools.test.ts b/src/agents/pi-tools.create-openclaw-coding-tools.test.ts index 5863d476175..673c8fdfab9 100644 --- a/src/agents/pi-tools.create-openclaw-coding-tools.test.ts +++ b/src/agents/pi-tools.create-openclaw-coding-tools.test.ts @@ -279,6 +279,23 @@ describe("createOpenClawCodingTools", () => { expect(cronTools.some((tool) => tool.name === "message")).toBe(true); }); + it("can keep message available when a cron route needs it under a provider coding profile", () => { + const providerProfileTools = createOpenClawCodingTools({ + config: { tools: { byProvider: { openai: { profile: "coding" } } } }, + modelProvider: "openai", + modelId: "gpt-5.4", + }); + expect(providerProfileTools.some((tool) => tool.name === "message")).toBe(false); + + const cronTools = createOpenClawCodingTools({ + config: { tools: { byProvider: { openai: { profile: "coding" } } } }, + modelProvider: "openai", + modelId: "gpt-5.4", + forceMessageTool: true, + }); + expect(cronTools.some((tool) => tool.name === "message")).toBe(true); + }); + it("expands group shorthands in global tool policy", () => { const tools = createOpenClawCodingTools({ config: { tools: { allow: ["group:fs"] } }, diff --git a/src/agents/pi-tools.ts b/src/agents/pi-tools.ts index 00316565289..3d17775f415 100644 --- a/src/agents/pi-tools.ts +++ b/src/agents/pi-tools.ts @@ -387,10 +387,10 @@ export function createOpenClawCodingTools(options?: { ...(profileAlsoAllow ?? []), ...runtimeProfileAlsoAllow, ]); - const providerProfilePolicyWithAlsoAllow = mergeAlsoAllowPolicy( - providerProfilePolicy, - providerProfileAlsoAllow, - ); + const providerProfilePolicyWithAlsoAllow = mergeAlsoAllowPolicy(providerProfilePolicy, [ + ...(providerProfileAlsoAllow ?? []), + ...runtimeProfileAlsoAllow, + ]); // Prefer sessionKey for process isolation scope to prevent cross-session process visibility/killing. // Fallback to agentId if no sessionKey is available (e.g. legacy or global contexts). const scopeKey =