fix: keep custom pi tools executable

This commit is contained in:
Peter Steinberger
2026-04-22 08:43:10 +01:00
parent 4431d6c5d0
commit bee2e0f38f
5 changed files with 48 additions and 6 deletions

View File

@@ -132,7 +132,7 @@ import {
buildEmbeddedSystemPrompt,
createSystemPromptOverride,
} from "./system-prompt.js";
import { collectAllowedToolNames } from "./tool-name-allowlist.js";
import { collectAllowedToolNames, toSessionToolAllowlist } from "./tool-name-allowlist.js";
import {
logProviderToolSchemaDiagnostics,
normalizeProviderToolSchemas,
@@ -839,10 +839,14 @@ export async function compactEmbeddedPiSessionDirect(
contextTokenBudget: ctxInfo.tokens,
});
const { builtInTools, customTools } = splitSdkTools({
const { customTools } = splitSdkTools({
tools: effectiveTools,
sandboxEnabled: !!sandbox?.enabled,
});
// Pi treats `tools` as a name allowlist. Compaction uses the same custom
// tool path as normal turns, so pass names here to keep those tools active
// across compaction retries.
const sessionToolAllowlist = toSessionToolAllowlist(allowedToolNames);
const providerStreamFn = resolveCompactionProviderStream({
effectiveModel,
@@ -880,7 +884,7 @@ export async function compactEmbeddedPiSessionDirect(
modelRegistry,
model: effectiveModel,
thinkingLevel: mapThinkingLevel(thinkLevel),
tools: builtInTools,
tools: sessionToolAllowlist,
customTools,
sessionManager,
settingsManager,

View File

@@ -5,7 +5,7 @@ export type EmbeddedAgentSessionOptions = {
modelRegistry: unknown;
model: unknown;
thinkingLevel: unknown;
tools: readonly unknown[];
tools: readonly string[];
customTools: readonly unknown[];
sessionManager: unknown;
settingsManager: unknown;

View File

@@ -167,7 +167,7 @@ import {
createSystemPromptOverride,
} from "../system-prompt.js";
import { dropThinkingBlocks } from "../thinking.js";
import { collectAllowedToolNames } from "../tool-name-allowlist.js";
import { collectAllowedToolNames, toSessionToolAllowlist } from "../tool-name-allowlist.js";
import {
installContextEngineLoopHook,
installToolResultContextGuard,
@@ -1121,6 +1121,11 @@ export async function runEmbeddedAttempt(
: [];
const allCustomTools = [...customTools, ...clientToolDefs];
// Pi's `tools` option is a name allowlist, not the tool definitions.
// OpenClaw registers local tools through `customTools`, so passing the
// same names here keeps custom tools executable instead of silently
// filtering them out with an empty allowlist.
const sessionToolAllowlist = toSessionToolAllowlist(allowedToolNames);
({ session } = await createEmbeddedAgentSessionWithResourceLoader({
createAgentSession: async (options) =>
@@ -1132,7 +1137,7 @@ export async function runEmbeddedAttempt(
modelRegistry: params.modelRegistry,
model: params.model,
thinkingLevel: mapThinkingLevel(params.thinkLevel),
tools: builtInTools,
tools: sessionToolAllowlist,
customTools: allCustomTools,
sessionManager,
settingsManager,

View File

@@ -0,0 +1,29 @@
import { describe, expect, it } from "vitest";
import { createStubTool } from "../test-helpers/pi-tool-stubs.js";
import { collectAllowedToolNames, toSessionToolAllowlist } from "./tool-name-allowlist.js";
describe("tool name allowlists", () => {
it("collects local and client tool names", () => {
const names = collectAllowedToolNames({
tools: [createStubTool("read"), createStubTool("memory_search")],
clientTools: [
{
type: "function",
function: {
name: "image_generate",
description: "Generate an image",
parameters: { type: "object", properties: {} },
},
},
],
});
expect([...names]).toEqual(["read", "memory_search", "image_generate"]);
});
it("builds a stable Pi session allowlist from custom tool names", () => {
const allowlist = toSessionToolAllowlist(new Set(["write", "read", "read", "edit"]));
expect(allowlist).toEqual(["edit", "read", "write"]);
});
});

View File

@@ -24,3 +24,7 @@ export function collectAllowedToolNames(params: {
}
return names;
}
export function toSessionToolAllowlist(allowedToolNames: Iterable<string>): string[] {
return [...new Set(allowedToolNames)].toSorted((a, b) => a.localeCompare(b));
}