fix(engine): register grep/find/ls alongside read/bash/edit/write for coding sessions

pi-coding-agent 0.70's createCodingTools preset only includes read/bash/edit/write,
but pi-claude-cli's tool-mapping translates Claude's Glob→find and Grep→grep.
Triage sessions running through Claude CLI hit "Tool find not found" the moment
the model called Glob and looped on the error.

Compose the built-in set explicitly so every tool referenced by tool-mapping.ts
is registered. Read-only sessions also gain ls (was silently dropped earlier
when readonly was redefined upstream).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
gsxdsm
2026-04-25 16:34:31 -07:00
parent f9302ac7c6
commit b9e084364f
3 changed files with 66 additions and 2 deletions

View File

@@ -75,9 +75,16 @@ vi.mock("@mariozechner/pi-coding-agent", () => ({
}), }),
}, },
createAgentSession: createAgentSessionMock, createAgentSession: createAgentSessionMock,
createBashTool: () => ({ name: "bash" }),
createCodingTools: createCodingToolsMock, createCodingTools: createCodingToolsMock,
createEditTool: () => ({ name: "edit" }),
createExtensionRuntime: createExtensionRuntimeMock, createExtensionRuntime: createExtensionRuntimeMock,
createFindTool: () => ({ name: "find" }),
createGrepTool: () => ({ name: "grep" }),
createLsTool: () => ({ name: "ls" }),
createReadOnlyTools: createReadOnlyToolsMock, createReadOnlyTools: createReadOnlyToolsMock,
createReadTool: () => ({ name: "read" }),
createWriteTool: () => ({ name: "write" }),
DefaultResourceLoader: class { DefaultResourceLoader: class {
async reload() { async reload() {
await reloadMock(); await reloadMock();
@@ -815,9 +822,16 @@ describe("createFnAgent", () => {
}), }),
}, },
createAgentSession: createAgentSessionMock, createAgentSession: createAgentSessionMock,
createBashTool: () => ({ name: "bash" }),
createCodingTools: createCodingToolsMock, createCodingTools: createCodingToolsMock,
createEditTool: () => ({ name: "edit" }),
createExtensionRuntime: createExtensionRuntimeMock, createExtensionRuntime: createExtensionRuntimeMock,
createFindTool: () => ({ name: "find" }),
createGrepTool: () => ({ name: "grep" }),
createLsTool: () => ({ name: "ls" }),
createReadOnlyTools: createReadOnlyToolsMock, createReadOnlyTools: createReadOnlyToolsMock,
createReadTool: () => ({ name: "read" }),
createWriteTool: () => ({ name: "write" }),
DefaultResourceLoader: class { DefaultResourceLoader: class {
constructor(options: any) { constructor(options: any) {
capturedResourceLoaderOptions = options; capturedResourceLoaderOptions = options;
@@ -893,9 +907,16 @@ describe("createFnAgent", () => {
}), }),
}, },
createAgentSession: createAgentSessionMock, createAgentSession: createAgentSessionMock,
createBashTool: () => ({ name: "bash" }),
createCodingTools: createCodingToolsMock, createCodingTools: createCodingToolsMock,
createEditTool: () => ({ name: "edit" }),
createExtensionRuntime: createExtensionRuntimeMock, createExtensionRuntime: createExtensionRuntimeMock,
createFindTool: () => ({ name: "find" }),
createGrepTool: () => ({ name: "grep" }),
createLsTool: () => ({ name: "ls" }),
createReadOnlyTools: createReadOnlyToolsMock, createReadOnlyTools: createReadOnlyToolsMock,
createReadTool: () => ({ name: "read" }),
createWriteTool: () => ({ name: "write" }),
DefaultResourceLoader: class { DefaultResourceLoader: class {
constructor(options: any) { constructor(options: any) {
capturedResourceLoaderOptions = options; capturedResourceLoaderOptions = options;
@@ -968,9 +989,16 @@ describe("createFnAgent", () => {
}), }),
}, },
createAgentSession: createAgentSessionMock, createAgentSession: createAgentSessionMock,
createBashTool: () => ({ name: "bash" }),
createCodingTools: createCodingToolsMock, createCodingTools: createCodingToolsMock,
createEditTool: () => ({ name: "edit" }),
createExtensionRuntime: createExtensionRuntimeMock, createExtensionRuntime: createExtensionRuntimeMock,
createFindTool: () => ({ name: "find" }),
createGrepTool: () => ({ name: "grep" }),
createLsTool: () => ({ name: "ls" }),
createReadOnlyTools: createReadOnlyToolsMock, createReadOnlyTools: createReadOnlyToolsMock,
createReadTool: () => ({ name: "read" }),
createWriteTool: () => ({ name: "write" }),
DefaultResourceLoader: class { DefaultResourceLoader: class {
constructor(options: any) { constructor(options: any) {
capturedResourceLoaderOptions = options; capturedResourceLoaderOptions = options;

View File

@@ -35,6 +35,13 @@ vi.mock("@mariozechner/pi-coding-agent", () => ({
})), })),
createCodingTools: vi.fn(() => []), createCodingTools: vi.fn(() => []),
createReadOnlyTools: vi.fn(() => []), createReadOnlyTools: vi.fn(() => []),
createReadTool: vi.fn(() => ({ name: "read" })),
createBashTool: vi.fn(() => ({ name: "bash" })),
createEditTool: vi.fn(() => ({ name: "edit" })),
createWriteTool: vi.fn(() => ({ name: "write" })),
createGrepTool: vi.fn(() => ({ name: "grep" })),
createFindTool: vi.fn(() => ({ name: "find" })),
createLsTool: vi.fn(() => ({ name: "ls" })),
createExtensionRuntime: vi.fn(), createExtensionRuntime: vi.fn(),
DefaultResourceLoader: vi.fn().mockImplementation(() => ({ DefaultResourceLoader: vi.fn().mockImplementation(() => ({
reload: vi.fn().mockResolvedValue(undefined), reload: vi.fn().mockResolvedValue(undefined),

View File

@@ -15,9 +15,16 @@ import { basename, dirname, join, relative, isAbsolute, resolve } from "node:pat
const execAsync = promisify(exec); const execAsync = promisify(exec);
import { import {
createAgentSession, createAgentSession,
createBashTool,
createCodingTools, createCodingTools,
createEditTool,
createExtensionRuntime, createExtensionRuntime,
createFindTool,
createGrepTool,
createLsTool,
createReadOnlyTools, createReadOnlyTools,
createReadTool,
createWriteTool,
DefaultResourceLoader, DefaultResourceLoader,
DefaultPackageManager, DefaultPackageManager,
discoverAndLoadExtensions, discoverAndLoadExtensions,
@@ -967,10 +974,32 @@ export async function createFnAgent(options: AgentOptions): Promise<AgentResult>
const modelRegistry = ModelRegistry.create(authStorage, getModelRegistryModelsPath()); const modelRegistry = ModelRegistry.create(authStorage, getModelRegistryModelsPath());
await registerExtensionProviders(options.cwd, modelRegistry); await registerExtensionProviders(options.cwd, modelRegistry);
// Build the pi built-in tool set. We deliberately do NOT use the bundled
// `createCodingTools` / `createReadOnlyTools` presets — they're missing
// tools that pi-claude-cli's Claude→pi name mapping depends on (Glob→find,
// Grep→grep). When a coding session ran via Claude CLI tried `Glob`, pi
// returned "Tool find not found" and the agent looped. Compose explicitly
// so every tool referenced by tool-mapping.ts is registered.
const tools = const tools =
options.tools === "readonly" options.tools === "readonly"
? createReadOnlyTools(options.cwd) ? [
: createCodingTools(options.cwd); createReadTool(options.cwd),
createGrepTool(options.cwd),
createFindTool(options.cwd),
createLsTool(options.cwd),
]
: [
createReadTool(options.cwd),
createBashTool(options.cwd),
createEditTool(options.cwd),
createWriteTool(options.cwd),
createGrepTool(options.cwd),
createFindTool(options.cwd),
createLsTool(options.cwd),
];
// Suppress lint about unused presets — kept in scope for incremental migration.
void createCodingTools;
void createReadOnlyTools;
// Detect if this is a worktree session and apply path boundaries // Detect if this is a worktree session and apply path boundaries
const worktreePath = options.cwd; const worktreePath = options.cwd;