mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 07:40:44 +00:00
fix: restore Pi embedded tool allowlist
Restore the Pi embedded session tool allowlist for OpenAI/OpenAI Codex GPT-5 runs and compaction sessions after Pi 0.68.1 began treating session tools as a global allowlist. Local validation: pnpm check:changed. GitHub validation: check/check-additional/node shards green; parity gate red on unrelated config.patch stale/rate-limit QA harness scenario after plugins.allow restart.
This commit is contained in:
@@ -84,6 +84,7 @@ Docs: https://docs.openclaw.ai
|
||||
- Pairing: remove stale pending requests for a device when that paired device is deleted, so an old repair approval cannot recreate the removed device from leftover state.
|
||||
- Security/dotenv: block workspace `.env` overrides for Matrix, Mattermost, IRC, and Synology endpoint settings so cloned workspaces cannot redirect bundled connector traffic through local endpoint config. (#70240) Thanks @drobison00.
|
||||
- Telegram: require the same `/models` authorization for group model-picker callbacks, so unauthorized participants can no longer browse or change the session model through inline buttons. (#70235) Thanks @drobison00.
|
||||
- Agents/Pi: keep the filtered tool-name allowlist active for embedded OpenAI/OpenAI Codex GPT-5 runs and compaction sessions, so bundled and client tools still execute after the Pi `0.68.1` session-tool allowlist change instead of stopping at plan-only replies with no tool call. (#70281) Thanks @jalehman.
|
||||
|
||||
## 2026.4.21
|
||||
|
||||
|
||||
@@ -132,7 +132,11 @@ import {
|
||||
buildEmbeddedSystemPrompt,
|
||||
createSystemPromptOverride,
|
||||
} from "./system-prompt.js";
|
||||
import { collectAllowedToolNames } from "./tool-name-allowlist.js";
|
||||
import {
|
||||
collectAllowedToolNames,
|
||||
collectRegisteredToolNames,
|
||||
toSessionToolAllowlist,
|
||||
} from "./tool-name-allowlist.js";
|
||||
import {
|
||||
logProviderToolSchemaDiagnostics,
|
||||
normalizeProviderToolSchemas,
|
||||
@@ -839,12 +843,15 @@ export async function compactEmbeddedPiSessionDirect(
|
||||
contextTokenBudget: ctxInfo.tokens,
|
||||
});
|
||||
|
||||
const { builtInTools, customTools } = splitSdkTools({
|
||||
const { customTools } = splitSdkTools({
|
||||
tools: effectiveTools,
|
||||
sandboxEnabled: !!sandbox?.enabled,
|
||||
});
|
||||
// OpenClaw registers filtered tools through `customTools`; keep Pi's
|
||||
// built-in tool list empty so the SDK does not re-enable defaults.
|
||||
// Pi 0.68.1 uses `tools` as a global allowlist across built-in and
|
||||
// custom tools. Keep the built-in tool list empty, but still pass the
|
||||
// exact registered custom-tool names so our OpenClaw-managed
|
||||
// registrations remain active without broadening the session boundary.
|
||||
const sessionToolAllowlist = toSessionToolAllowlist(collectRegisteredToolNames(customTools));
|
||||
|
||||
const providerStreamFn = resolveCompactionProviderStream({
|
||||
effectiveModel,
|
||||
@@ -882,7 +889,7 @@ export async function compactEmbeddedPiSessionDirect(
|
||||
modelRegistry,
|
||||
model: effectiveModel,
|
||||
thinkingLevel: mapThinkingLevel(thinkLevel),
|
||||
tools: builtInTools,
|
||||
tools: sessionToolAllowlist,
|
||||
customTools,
|
||||
sessionManager,
|
||||
settingsManager,
|
||||
|
||||
@@ -167,7 +167,12 @@ import {
|
||||
createSystemPromptOverride,
|
||||
} from "../system-prompt.js";
|
||||
import { dropThinkingBlocks } from "../thinking.js";
|
||||
import { collectAllowedToolNames } from "../tool-name-allowlist.js";
|
||||
import {
|
||||
collectAllowedToolNames,
|
||||
collectRegisteredToolNames,
|
||||
PI_RESERVED_TOOL_NAMES,
|
||||
toSessionToolAllowlist,
|
||||
} from "../tool-name-allowlist.js";
|
||||
import {
|
||||
installContextEngineLoopHook,
|
||||
installToolResultContextGuard,
|
||||
@@ -1062,7 +1067,7 @@ export async function runEmbeddedAttempt(
|
||||
// Get hook runner early so it's available when creating tools
|
||||
const hookRunner = getGlobalHookRunner();
|
||||
|
||||
const { builtInTools, customTools } = splitSdkTools({
|
||||
const { customTools } = splitSdkTools({
|
||||
tools: effectiveTools,
|
||||
sandboxEnabled: !!sandbox?.enabled,
|
||||
});
|
||||
@@ -1099,7 +1104,7 @@ export async function runEmbeddedAttempt(
|
||||
);
|
||||
const clientToolNameConflicts = findClientToolNameConflicts({
|
||||
tools: clientTools ?? [],
|
||||
existingToolNames: coreBuiltinToolNames,
|
||||
existingToolNames: [...coreBuiltinToolNames, ...PI_RESERVED_TOOL_NAMES],
|
||||
});
|
||||
if (clientToolNameConflicts.length > 0) {
|
||||
throw createClientToolNameConflictError(clientToolNameConflicts);
|
||||
@@ -1121,8 +1126,14 @@ export async function runEmbeddedAttempt(
|
||||
: [];
|
||||
|
||||
const allCustomTools = [...customTools, ...clientToolDefs];
|
||||
// OpenClaw registers filtered tools through `customTools`; keep Pi's
|
||||
// built-in tool list empty so the SDK does not re-enable defaults.
|
||||
// Pi 0.68.1 uses `tools` as a global allowlist across built-in and
|
||||
// custom tools. Keep the built-in tool list empty, but still pass the
|
||||
// exact registered custom-tool names so our OpenClaw-managed
|
||||
// registrations remain active without widening the session boundary to
|
||||
// raw client-provided names.
|
||||
const sessionToolAllowlist = toSessionToolAllowlist(
|
||||
collectRegisteredToolNames(allCustomTools),
|
||||
);
|
||||
|
||||
({ session } = await createEmbeddedAgentSessionWithResourceLoader({
|
||||
createAgentSession: async (options) =>
|
||||
@@ -1134,7 +1145,7 @@ export async function runEmbeddedAttempt(
|
||||
modelRegistry: params.modelRegistry,
|
||||
model: params.model,
|
||||
thinkingLevel: mapThinkingLevel(params.thinkLevel),
|
||||
tools: builtInTools,
|
||||
tools: sessionToolAllowlist,
|
||||
customTools: allCustomTools,
|
||||
sessionManager,
|
||||
settingsManager,
|
||||
@@ -1315,10 +1326,9 @@ export async function runEmbeddedAttempt(
|
||||
}
|
||||
|
||||
const cacheObservabilityEnabled = Boolean(cacheTrace) || log.isEnabled("debug");
|
||||
const promptCacheToolNames = collectPromptCacheToolNames([
|
||||
...builtInTools,
|
||||
...allCustomTools,
|
||||
] as Array<{ name?: string }>);
|
||||
const promptCacheToolNames = collectPromptCacheToolNames(
|
||||
allCustomTools as Array<{ name?: string }>,
|
||||
);
|
||||
let promptCacheChangesForTurn: PromptCacheChange[] | null = null;
|
||||
|
||||
if (cacheTrace) {
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { createStubTool } from "../test-helpers/pi-tool-stubs.js";
|
||||
import { collectAllowedToolNames, toSessionToolAllowlist } from "./tool-name-allowlist.js";
|
||||
import {
|
||||
collectAllowedToolNames,
|
||||
collectRegisteredToolNames,
|
||||
PI_RESERVED_TOOL_NAMES,
|
||||
toSessionToolAllowlist,
|
||||
} from "./tool-name-allowlist.js";
|
||||
|
||||
describe("tool name allowlists", () => {
|
||||
it("collects local and client tool names", () => {
|
||||
@@ -26,4 +31,40 @@ describe("tool name allowlists", () => {
|
||||
|
||||
expect(allowlist).toEqual(["edit", "read", "write"]);
|
||||
});
|
||||
|
||||
it("collects exact registered custom-tool names for the Pi session allowlist", () => {
|
||||
const allowlist = toSessionToolAllowlist(
|
||||
collectRegisteredToolNames([
|
||||
{ name: "exec" },
|
||||
{ name: "read" },
|
||||
{ name: "exec" },
|
||||
{ name: "image_generate" },
|
||||
]),
|
||||
);
|
||||
|
||||
expect(allowlist).toEqual(["exec", "image_generate", "read"]);
|
||||
});
|
||||
|
||||
it("pins the reserved Pi built-in tool namespace used by client conflict checks", () => {
|
||||
expect(PI_RESERVED_TOOL_NAMES).toEqual(["bash", "edit", "find", "grep", "ls", "read", "write"]);
|
||||
});
|
||||
|
||||
it("keeps collected run allowlists broader than the Pi session allowlist source", () => {
|
||||
const allowlist = toSessionToolAllowlist(
|
||||
collectAllowedToolNames({
|
||||
tools: [createStubTool("exec"), createStubTool("read"), createStubTool("exec")],
|
||||
clientTools: [
|
||||
{
|
||||
type: "function",
|
||||
function: {
|
||||
name: "image_generate",
|
||||
parameters: { type: "object", properties: {} },
|
||||
},
|
||||
},
|
||||
],
|
||||
}),
|
||||
);
|
||||
|
||||
expect(allowlist).toEqual(["exec", "image_generate", "read"]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
import type { AgentTool } from "@mariozechner/pi-agent-core";
|
||||
import type { ClientToolDefinition } from "./run/params.js";
|
||||
|
||||
/**
|
||||
* Pi built-in tools that remain present in the embedded runtime even when
|
||||
* OpenClaw routes execution through custom tool definitions.
|
||||
*/
|
||||
export const PI_RESERVED_TOOL_NAMES = ["bash", "edit", "find", "grep", "ls", "read", "write"];
|
||||
|
||||
function addName(names: Set<string>, value: unknown): void {
|
||||
if (typeof value !== "string") {
|
||||
return;
|
||||
@@ -25,6 +31,17 @@ export function collectAllowedToolNames(params: {
|
||||
return names;
|
||||
}
|
||||
|
||||
/**
|
||||
* Collect the exact tool names registered with Pi for this session.
|
||||
*/
|
||||
export function collectRegisteredToolNames(tools: Array<{ name?: string }>): Set<string> {
|
||||
const names = new Set<string>();
|
||||
for (const tool of tools) {
|
||||
addName(names, tool.name);
|
||||
}
|
||||
return names;
|
||||
}
|
||||
|
||||
export function toSessionToolAllowlist(allowedToolNames: Iterable<string>): string[] {
|
||||
return [...new Set(allowedToolNames)].toSorted((a, b) => a.localeCompare(b));
|
||||
}
|
||||
|
||||
@@ -203,6 +203,15 @@ describe("client tool name conflict checks", () => {
|
||||
).toEqual(["Weather", "weather"]);
|
||||
});
|
||||
|
||||
it("detects collisions with reserved Pi built-in tool names", () => {
|
||||
expect(
|
||||
findClientToolNameConflicts({
|
||||
tools: [makeClientTool("Bash"), makeClientTool("grep")],
|
||||
existingToolNames: ["bash", "edit", "find", "grep", "ls", "read", "write"],
|
||||
}),
|
||||
).toEqual(["Bash", "grep"]);
|
||||
});
|
||||
|
||||
it("wraps conflict errors with a stable prefix", () => {
|
||||
const err = createClientToolNameConflictError(["exec", "Web_Search"]);
|
||||
expect(err.message).toBe(`${CLIENT_TOOL_NAME_CONFLICT_PREFIX} exec, Web_Search`);
|
||||
|
||||
Reference in New Issue
Block a user