fix: honor sandboxed built-in tools

This commit is contained in:
Peter Steinberger
2026-01-07 06:12:56 +00:00
parent 03928106c7
commit 50dec39d13
3 changed files with 99 additions and 17 deletions

View File

@@ -1,6 +1,10 @@
import type { AgentTool } from "@mariozechner/pi-agent-core";
import { Type } from "@sinclair/typebox";
import { describe, expect, it } from "vitest";
import { buildEmbeddedSandboxInfo } from "./pi-embedded-runner.js";
import {
buildEmbeddedSandboxInfo,
splitSdkTools,
} from "./pi-embedded-runner.js";
import type { SandboxContext } from "./sandbox.js";
describe("buildEmbeddedSandboxInfo", () => {
@@ -45,3 +49,52 @@ describe("buildEmbeddedSandboxInfo", () => {
});
});
});
function createStubTool(name: string): AgentTool {
return {
name,
label: name,
description: "",
parameters: Type.Object({}),
execute: async () => ({ content: [], details: {} }),
};
}
describe("splitSdkTools", () => {
const tools = [
createStubTool("read"),
createStubTool("bash"),
createStubTool("edit"),
createStubTool("write"),
createStubTool("browser"),
];
it("routes built-ins to custom tools when sandboxed", () => {
const { builtInTools, customTools } = splitSdkTools({
tools,
sandboxEnabled: true,
});
expect(builtInTools).toEqual([]);
expect(customTools.map((tool) => tool.name)).toEqual([
"read",
"bash",
"edit",
"write",
"browser",
]);
});
it("keeps built-ins as SDK tools when not sandboxed", () => {
const { builtInTools, customTools } = splitSdkTools({
tools,
sandboxEnabled: false,
});
expect(builtInTools.map((tool) => tool.name)).toEqual([
"read",
"bash",
"edit",
"write",
]);
expect(customTools.map((tool) => tool.name)).toEqual(["browser"]);
});
});

View File

@@ -1,7 +1,11 @@
import fs from "node:fs/promises";
import os from "node:os";
import type { AgentMessage, ThinkingLevel } from "@mariozechner/pi-agent-core";
import type {
AgentMessage,
AgentTool,
ThinkingLevel,
} from "@mariozechner/pi-agent-core";
import type { Api, AssistantMessage, Model } from "@mariozechner/pi-ai";
import {
buildSystemPrompt,
@@ -12,6 +16,7 @@ import {
SettingsManager,
type Skill,
} from "@mariozechner/pi-coding-agent";
import type { TSchema } from "@sinclair/typebox";
import { resolveHeartbeatPrompt } from "../auto-reply/heartbeat.js";
import type {
ReasoningLevel,
@@ -249,6 +254,33 @@ export function buildEmbeddedSandboxInfo(
};
}
const BUILT_IN_TOOL_NAMES = new Set(["read", "bash", "edit", "write"]);
type AnyAgentTool = AgentTool<TSchema, unknown>;
export function splitSdkTools(options: {
tools: AnyAgentTool[];
sandboxEnabled: boolean;
}): {
builtInTools: AnyAgentTool[];
customTools: ReturnType<typeof toToolDefinitions>;
} {
// SDK rebuilds built-ins from cwd; route sandboxed versions as custom tools.
const { tools, sandboxEnabled } = options;
if (sandboxEnabled) {
return {
builtInTools: [],
customTools: toToolDefinitions(tools),
};
}
return {
builtInTools: tools.filter((tool) => BUILT_IN_TOOL_NAMES.has(tool.name)),
customTools: toToolDefinitions(
tools.filter((tool) => !BUILT_IN_TOOL_NAMES.has(tool.name)),
),
};
}
export function queueEmbeddedPiMessage(
sessionId: string,
text: string,
@@ -528,11 +560,10 @@ export async function compactEmbeddedPiSession(params: {
agentDir,
);
const builtInToolNames = new Set(["read", "bash", "edit", "write"]);
const builtInTools = tools.filter((t) => builtInToolNames.has(t.name));
const customTools = toToolDefinitions(
tools.filter((t) => !builtInToolNames.has(t.name)),
);
const { builtInTools, customTools } = splitSdkTools({
tools,
sandboxEnabled: !!sandbox?.enabled,
});
const { session } = await createAgentSession({
cwd: resolvedWorkspace,
@@ -829,14 +860,10 @@ export async function runEmbeddedPiAgent(params: {
agentDir,
);
// Split tools into built-in (recognized by pi-coding-agent SDK) and custom (clawdbot-specific)
const builtInToolNames = new Set(["read", "bash", "edit", "write"]);
const builtInTools = tools.filter((t) =>
builtInToolNames.has(t.name),
);
const customTools = toToolDefinitions(
tools.filter((t) => !builtInToolNames.has(t.name)),
);
const { builtInTools, customTools } = splitSdkTools({
tools,
sandboxEnabled: !!sandbox?.enabled,
});
const { session } = await createAgentSession({
cwd: resolvedWorkspace,

View File

@@ -162,7 +162,9 @@ describe("subscribeEmbeddedPiSession", () => {
expect(onBlockReply).toHaveBeenCalledTimes(1);
const payload = onBlockReply.mock.calls[0][0];
expect(payload.text).toBe("_Reasoning:_\n_Because it helps_\n\nFinal answer");
expect(payload.text).toBe(
"_Reasoning:_\n_Because it helps_\n\nFinal answer",
);
});
it("emits block replies on text_end and does not duplicate on message_end", () => {