fix(coding-agent): use tool-name allowlists and remove cwd-bound singletons
- treat tools as a global allowlist across built-in, extension, and SDK tools - remove process-cwd singleton tool usage from SDK and CLI paths - add regression coverage for extension tool filtering closes #3452 closes #2835
This commit is contained in:
@@ -13,6 +13,7 @@
|
|||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
|
- Fixed `ctx.ui.setWorkingIndicator()` custom frames to render verbatim instead of forcing the theme accent color, so extensions now own working-indicator coloring when they customize it ([#3467](https://github.com/badlogic/pi-mono/issues/3467))
|
||||||
- Fixed `pi update` reinstalling npm packages that are already at the latest published version by checking the installed package version before running `npm install <pkg>@latest` ([#3000](https://github.com/badlogic/pi-mono/issues/3000))
|
- Fixed `pi update` reinstalling npm packages that are already at the latest published version by checking the installed package version before running `npm install <pkg>@latest` ([#3000](https://github.com/badlogic/pi-mono/issues/3000))
|
||||||
- Fixed built-in tool wrapping to use the same extension-runner context path as extension tools, so built-in tools receive execution context and `read` can warn when the current model does not support images ([#3429](https://github.com/badlogic/pi-mono/issues/3429))
|
- Fixed built-in tool wrapping to use the same extension-runner context path as extension tools, so built-in tools receive execution context and `read` can warn when the current model does not support images ([#3429](https://github.com/badlogic/pi-mono/issues/3429))
|
||||||
- Fixed threaded `/resume` session relationships and current-session detection to canonicalize symlinked session paths during selector comparisons, so shared session directories no longer break parent-child matching or active-session delete protection ([#3364](https://github.com/badlogic/pi-mono/issues/3364))
|
- Fixed threaded `/resume` session relationships and current-session detection to canonicalize symlinked session paths during selector comparisons, so shared session directories no longer break parent-child matching or active-session delete protection ([#3364](https://github.com/badlogic/pi-mono/issues/3364))
|
||||||
|
|||||||
@@ -2049,8 +2049,16 @@ ctx.ui.setWorkingMessage("Thinking deeply...");
|
|||||||
ctx.ui.setWorkingMessage(); // Restore default
|
ctx.ui.setWorkingMessage(); // Restore default
|
||||||
|
|
||||||
// Working indicator (shown during streaming)
|
// Working indicator (shown during streaming)
|
||||||
ctx.ui.setWorkingIndicator({ frames: ["●"] }); // Static dot
|
ctx.ui.setWorkingIndicator({ frames: [ctx.ui.theme.fg("accent", "●")] }); // Static dot
|
||||||
ctx.ui.setWorkingIndicator({ frames: ["·", "•", "●", "•"], intervalMs: 120 });
|
ctx.ui.setWorkingIndicator({
|
||||||
|
frames: [
|
||||||
|
ctx.ui.theme.fg("dim", "·"),
|
||||||
|
ctx.ui.theme.fg("muted", "•"),
|
||||||
|
ctx.ui.theme.fg("accent", "●"),
|
||||||
|
ctx.ui.theme.fg("muted", "•"),
|
||||||
|
],
|
||||||
|
intervalMs: 120,
|
||||||
|
});
|
||||||
ctx.ui.setWorkingIndicator({ frames: [] }); // Hide indicator
|
ctx.ui.setWorkingIndicator({ frames: [] }); // Hide indicator
|
||||||
ctx.ui.setWorkingIndicator(); // Restore default spinner
|
ctx.ui.setWorkingIndicator(); // Restore default spinner
|
||||||
|
|
||||||
@@ -2098,6 +2106,8 @@ ctx.ui.setTheme(lightTheme!); // Or switch by Theme object
|
|||||||
ctx.ui.theme.fg("accent", "styled text"); // Access current theme
|
ctx.ui.theme.fg("accent", "styled text"); // Access current theme
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Custom working-indicator frames are rendered verbatim. If you want colors, add them to the frame strings yourself, for example with `ctx.ui.theme.fg(...)`.
|
||||||
|
|
||||||
### Custom Components
|
### Custom Components
|
||||||
|
|
||||||
For complex UI, use `ctx.ui.custom()`. This temporarily replaces the editor with your component until `done()` is called:
|
For complex UI, use `ctx.ui.custom()`. This temporarily replaces the editor with your component until `done()` is called:
|
||||||
|
|||||||
@@ -1,56 +1,44 @@
|
|||||||
/**
|
/**
|
||||||
* Tools Configuration
|
* Tools Configuration
|
||||||
*
|
*
|
||||||
* Use built-in tool sets or individual tools.
|
* Use tool names to choose which built-in tools are enabled.
|
||||||
*
|
*
|
||||||
* IMPORTANT: When using a custom `cwd`, you must use the tool factory functions
|
* Tool names are matched against all available tools. If you use a custom `cwd`,
|
||||||
* (createCodingTools, createReadOnlyTools, createReadTool, etc.) to ensure
|
* createAgentSession() applies that cwd when it builds the actual built-in tools.
|
||||||
* tools resolve paths relative to your cwd, not process.cwd().
|
|
||||||
*
|
*
|
||||||
* For custom tools, see 06-extensions.ts - custom tools are now registered
|
* For custom tools, see 06-extensions.ts - custom tools are registered via the
|
||||||
* via the extensions system using pi.registerTool().
|
* extensions system using pi.registerTool().
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {
|
import { createAgentSession, SessionManager } from "@mariozechner/pi-coding-agent";
|
||||||
bashTool,
|
|
||||||
createAgentSession,
|
|
||||||
createBashTool,
|
|
||||||
createCodingTools,
|
|
||||||
createGrepTool,
|
|
||||||
createReadTool,
|
|
||||||
grepTool,
|
|
||||||
readOnlyTools,
|
|
||||||
readTool,
|
|
||||||
SessionManager,
|
|
||||||
} from "@mariozechner/pi-coding-agent";
|
|
||||||
|
|
||||||
// Read-only mode (no edit/write) - uses process.cwd()
|
// Read-only mode (no edit/write)
|
||||||
await createAgentSession({
|
await createAgentSession({
|
||||||
tools: readOnlyTools,
|
tools: ["read", "grep", "find", "ls"],
|
||||||
sessionManager: SessionManager.inMemory(),
|
sessionManager: SessionManager.inMemory(),
|
||||||
});
|
});
|
||||||
console.log("Read-only session created");
|
console.log("Read-only session created");
|
||||||
|
|
||||||
// Custom tool selection - uses process.cwd()
|
// Custom tool selection
|
||||||
await createAgentSession({
|
await createAgentSession({
|
||||||
tools: [readTool, bashTool, grepTool],
|
tools: ["read", "bash", "grep"],
|
||||||
sessionManager: SessionManager.inMemory(),
|
sessionManager: SessionManager.inMemory(),
|
||||||
});
|
});
|
||||||
console.log("Custom tools session created");
|
console.log("Custom tools session created");
|
||||||
|
|
||||||
// With custom cwd - MUST use factory functions!
|
// With custom cwd
|
||||||
const customCwd = "/path/to/project";
|
const customCwd = "/path/to/project";
|
||||||
await createAgentSession({
|
await createAgentSession({
|
||||||
cwd: customCwd,
|
cwd: customCwd,
|
||||||
tools: createCodingTools(customCwd), // Tools resolve paths relative to customCwd
|
tools: ["read", "bash", "edit", "write"],
|
||||||
sessionManager: SessionManager.inMemory(),
|
sessionManager: SessionManager.inMemory(customCwd),
|
||||||
});
|
});
|
||||||
console.log("Custom cwd session created");
|
console.log("Custom cwd session created");
|
||||||
|
|
||||||
// Or pick specific tools for custom cwd
|
// Or pick specific tools for custom cwd
|
||||||
await createAgentSession({
|
await createAgentSession({
|
||||||
cwd: customCwd,
|
cwd: customCwd,
|
||||||
tools: [createReadTool(customCwd), createBashTool(customCwd), createGrepTool(customCwd)],
|
tools: ["read", "bash", "grep"],
|
||||||
sessionManager: SessionManager.inMemory(),
|
sessionManager: SessionManager.inMemory(customCwd),
|
||||||
});
|
});
|
||||||
console.log("Specific tools with custom cwd session created");
|
console.log("Specific tools with custom cwd session created");
|
||||||
|
|||||||
@@ -2,19 +2,13 @@
|
|||||||
* Full Control
|
* Full Control
|
||||||
*
|
*
|
||||||
* Replace everything - no discovery, explicit configuration.
|
* Replace everything - no discovery, explicit configuration.
|
||||||
*
|
|
||||||
* IMPORTANT: When providing `tools` with a custom `cwd`, use the tool factory
|
|
||||||
* functions (createReadTool, createBashTool, etc.) to ensure tools resolve
|
|
||||||
* paths relative to your cwd.
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { getModel } from "@mariozechner/pi-ai";
|
import { getModel } from "@mariozechner/pi-ai";
|
||||||
import {
|
import {
|
||||||
AuthStorage,
|
AuthStorage,
|
||||||
createAgentSession,
|
createAgentSession,
|
||||||
createBashTool,
|
|
||||||
createExtensionRuntime,
|
createExtensionRuntime,
|
||||||
createReadTool,
|
|
||||||
ModelRegistry,
|
ModelRegistry,
|
||||||
type ResourceLoader,
|
type ResourceLoader,
|
||||||
SessionManager,
|
SessionManager,
|
||||||
@@ -41,7 +35,6 @@ const settingsManager = SettingsManager.inMemory({
|
|||||||
retry: { enabled: true, maxRetries: 2 },
|
retry: { enabled: true, maxRetries: 2 },
|
||||||
});
|
});
|
||||||
|
|
||||||
// When using a custom cwd with explicit tools, use the factory functions
|
|
||||||
const cwd = process.cwd();
|
const cwd = process.cwd();
|
||||||
|
|
||||||
const resourceLoader: ResourceLoader = {
|
const resourceLoader: ResourceLoader = {
|
||||||
@@ -65,9 +58,8 @@ const { session } = await createAgentSession({
|
|||||||
authStorage,
|
authStorage,
|
||||||
modelRegistry,
|
modelRegistry,
|
||||||
resourceLoader,
|
resourceLoader,
|
||||||
// Use factory functions with the same cwd to ensure path resolution works correctly
|
tools: ["read", "bash"],
|
||||||
tools: [createReadTool(cwd), createBashTool(cwd)],
|
sessionManager: SessionManager.inMemory(cwd),
|
||||||
sessionManager: SessionManager.inMemory(),
|
|
||||||
settingsManager,
|
settingsManager,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import type { ThinkingLevel } from "@mariozechner/pi-agent-core";
|
|||||||
import chalk from "chalk";
|
import chalk from "chalk";
|
||||||
import { APP_NAME, CONFIG_DIR_NAME, ENV_AGENT_DIR } from "../config.js";
|
import { APP_NAME, CONFIG_DIR_NAME, ENV_AGENT_DIR } from "../config.js";
|
||||||
import type { ExtensionFlag } from "../core/extensions/types.js";
|
import type { ExtensionFlag } from "../core/extensions/types.js";
|
||||||
import { allTools, type ToolName } from "../core/tools/index.js";
|
|
||||||
|
|
||||||
export type Mode = "text" | "json" | "rpc";
|
export type Mode = "text" | "json" | "rpc";
|
||||||
|
|
||||||
@@ -27,7 +26,7 @@ export interface Args {
|
|||||||
fork?: string;
|
fork?: string;
|
||||||
sessionDir?: string;
|
sessionDir?: string;
|
||||||
models?: string[];
|
models?: string[];
|
||||||
tools?: ToolName[];
|
tools?: string[];
|
||||||
noTools?: boolean;
|
noTools?: boolean;
|
||||||
extensions?: string[];
|
extensions?: string[];
|
||||||
noExtensions?: boolean;
|
noExtensions?: boolean;
|
||||||
@@ -104,19 +103,10 @@ export function parseArgs(args: string[]): Args {
|
|||||||
} else if (arg === "--no-tools") {
|
} else if (arg === "--no-tools") {
|
||||||
result.noTools = true;
|
result.noTools = true;
|
||||||
} else if (arg === "--tools" && i + 1 < args.length) {
|
} else if (arg === "--tools" && i + 1 < args.length) {
|
||||||
const toolNames = args[++i].split(",").map((s) => s.trim());
|
result.tools = args[++i]
|
||||||
const validTools: ToolName[] = [];
|
.split(",")
|
||||||
for (const name of toolNames) {
|
.map((s) => s.trim())
|
||||||
if (name in allTools) {
|
.filter((name) => name.length > 0);
|
||||||
validTools.push(name as ToolName);
|
|
||||||
} else {
|
|
||||||
result.diagnostics.push({
|
|
||||||
type: "warning",
|
|
||||||
message: `Unknown tool "${name}". Valid tools: ${Object.keys(allTools).join(", ")}`,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
result.tools = validTools;
|
|
||||||
} else if (arg === "--thinking" && i + 1 < args.length) {
|
} else if (arg === "--thinking" && i + 1 < args.length) {
|
||||||
const level = args[++i];
|
const level = args[++i];
|
||||||
if (isValidThinkingLevel(level)) {
|
if (isValidThinkingLevel(level)) {
|
||||||
@@ -231,9 +221,9 @@ ${chalk.bold("Options:")}
|
|||||||
--no-session Don't save session (ephemeral)
|
--no-session Don't save session (ephemeral)
|
||||||
--models <patterns> Comma-separated model patterns for Ctrl+P cycling
|
--models <patterns> Comma-separated model patterns for Ctrl+P cycling
|
||||||
Supports globs (anthropic/*, *sonnet*) and fuzzy matching
|
Supports globs (anthropic/*, *sonnet*) and fuzzy matching
|
||||||
--no-tools Disable all built-in tools
|
--no-tools Disable all tools by default (built-in and extension)
|
||||||
--tools <tools> Comma-separated list of tools to enable (default: read,bash,edit,write)
|
--tools <tools> Comma-separated allowlist of tool names to enable
|
||||||
Available: read, bash, edit, write, grep, find, ls
|
Applies to built-in and extension tools
|
||||||
--thinking <level> Set thinking level: off, minimal, low, medium, high, xhigh
|
--thinking <level> Set thinking level: off, minimal, low, medium, high, xhigh
|
||||||
--extension, -e <path> Load an extension file (can be used multiple times)
|
--extension, -e <path> Load an extension file (can be used multiple times)
|
||||||
--no-extensions, -ne Disable extension discovery (explicit -e paths still work)
|
--no-extensions, -ne Disable extension discovery (explicit -e paths still work)
|
||||||
@@ -332,7 +322,7 @@ ${chalk.bold("Environment Variables:")}
|
|||||||
PI_SHARE_VIEWER_URL - Base URL for /share command (default: https://pi.dev/session/)
|
PI_SHARE_VIEWER_URL - Base URL for /share command (default: https://pi.dev/session/)
|
||||||
PI_AI_ANTIGRAVITY_VERSION - Override Antigravity User-Agent version (e.g., 1.23.0)
|
PI_AI_ANTIGRAVITY_VERSION - Override Antigravity User-Agent version (e.g., 1.23.0)
|
||||||
|
|
||||||
${chalk.bold("Available Tools (default: read, bash, edit, write):")}
|
${chalk.bold("Built-in Tool Names:")}
|
||||||
read - Read file contents
|
read - Read file contents
|
||||||
bash - Execute bash commands
|
bash - Execute bash commands
|
||||||
edit - Edit files with find/replace
|
edit - Edit files with find/replace
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import { DefaultResourceLoader, type DefaultResourceLoaderOptions, type Resource
|
|||||||
import { type CreateAgentSessionResult, createAgentSession } from "./sdk.js";
|
import { type CreateAgentSessionResult, createAgentSession } from "./sdk.js";
|
||||||
import type { SessionManager } from "./session-manager.js";
|
import type { SessionManager } from "./session-manager.js";
|
||||||
import { SettingsManager } from "./settings-manager.js";
|
import { SettingsManager } from "./settings-manager.js";
|
||||||
import type { Tool } from "./tools/index.js";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Non-fatal issues collected while creating services or sessions.
|
* Non-fatal issues collected while creating services or sessions.
|
||||||
@@ -53,7 +52,7 @@ export interface CreateAgentSessionFromServicesOptions {
|
|||||||
model?: Model<any>;
|
model?: Model<any>;
|
||||||
thinkingLevel?: ThinkingLevel;
|
thinkingLevel?: ThinkingLevel;
|
||||||
scopedModels?: Array<{ model: Model<any>; thinkingLevel?: ThinkingLevel }>;
|
scopedModels?: Array<{ model: Model<any>; thinkingLevel?: ThinkingLevel }>;
|
||||||
tools?: Tool[];
|
tools?: string[];
|
||||||
customTools?: ToolDefinition[];
|
customTools?: ToolDefinition[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -150,6 +150,8 @@ export interface AgentSessionConfig {
|
|||||||
modelRegistry: ModelRegistry;
|
modelRegistry: ModelRegistry;
|
||||||
/** Initial active built-in tool names. Default: [read, bash, edit, write] */
|
/** Initial active built-in tool names. Default: [read, bash, edit, write] */
|
||||||
initialActiveToolNames?: string[];
|
initialActiveToolNames?: string[];
|
||||||
|
/** Optional allowlist of tool names. When provided, only these tool names are exposed. */
|
||||||
|
allowedToolNames?: string[];
|
||||||
/**
|
/**
|
||||||
* Override base tools (useful for custom runtimes).
|
* Override base tools (useful for custom runtimes).
|
||||||
*
|
*
|
||||||
@@ -278,6 +280,7 @@ export class AgentSession {
|
|||||||
private _cwd: string;
|
private _cwd: string;
|
||||||
private _extensionRunnerRef?: { current?: ExtensionRunner };
|
private _extensionRunnerRef?: { current?: ExtensionRunner };
|
||||||
private _initialActiveToolNames?: string[];
|
private _initialActiveToolNames?: string[];
|
||||||
|
private _allowedToolNames?: Set<string>;
|
||||||
private _baseToolsOverride?: Record<string, AgentTool>;
|
private _baseToolsOverride?: Record<string, AgentTool>;
|
||||||
private _sessionStartEvent: SessionStartEvent;
|
private _sessionStartEvent: SessionStartEvent;
|
||||||
private _extensionUIContext?: ExtensionUIContext;
|
private _extensionUIContext?: ExtensionUIContext;
|
||||||
@@ -309,6 +312,7 @@ export class AgentSession {
|
|||||||
this._modelRegistry = config.modelRegistry;
|
this._modelRegistry = config.modelRegistry;
|
||||||
this._extensionRunnerRef = config.extensionRunnerRef;
|
this._extensionRunnerRef = config.extensionRunnerRef;
|
||||||
this._initialActiveToolNames = config.initialActiveToolNames;
|
this._initialActiveToolNames = config.initialActiveToolNames;
|
||||||
|
this._allowedToolNames = config.allowedToolNames ? new Set(config.allowedToolNames) : undefined;
|
||||||
this._baseToolsOverride = config.baseToolsOverride;
|
this._baseToolsOverride = config.baseToolsOverride;
|
||||||
this._sessionStartEvent = config.sessionStartEvent ?? { type: "session_start", reason: "startup" };
|
this._sessionStartEvent = config.sessionStartEvent ?? { type: "session_start", reason: "startup" };
|
||||||
|
|
||||||
@@ -2221,6 +2225,8 @@ export class AgentSession {
|
|||||||
private _refreshToolRegistry(options?: { activeToolNames?: string[]; includeAllExtensionTools?: boolean }): void {
|
private _refreshToolRegistry(options?: { activeToolNames?: string[]; includeAllExtensionTools?: boolean }): void {
|
||||||
const previousRegistryNames = new Set(this._toolRegistry.keys());
|
const previousRegistryNames = new Set(this._toolRegistry.keys());
|
||||||
const previousActiveToolNames = this.getActiveToolNames();
|
const previousActiveToolNames = this.getActiveToolNames();
|
||||||
|
const allowedToolNames = this._allowedToolNames;
|
||||||
|
const isAllowedTool = (name: string): boolean => !allowedToolNames || allowedToolNames.has(name);
|
||||||
|
|
||||||
const registeredTools = this._extensionRunner.getAllRegisteredTools();
|
const registeredTools = this._extensionRunner.getAllRegisteredTools();
|
||||||
const allCustomTools = [
|
const allCustomTools = [
|
||||||
@@ -2229,9 +2235,11 @@ export class AgentSession {
|
|||||||
definition,
|
definition,
|
||||||
sourceInfo: createSyntheticSourceInfo(`<sdk:${definition.name}>`, { source: "sdk" }),
|
sourceInfo: createSyntheticSourceInfo(`<sdk:${definition.name}>`, { source: "sdk" }),
|
||||||
})),
|
})),
|
||||||
];
|
].filter((tool) => isAllowedTool(tool.definition.name));
|
||||||
const definitionRegistry = new Map<string, ToolDefinitionEntry>(
|
const definitionRegistry = new Map<string, ToolDefinitionEntry>(
|
||||||
Array.from(this._baseToolDefinitions.entries()).map(([name, definition]) => [
|
Array.from(this._baseToolDefinitions.entries())
|
||||||
|
.filter(([name]) => isAllowedTool(name))
|
||||||
|
.map(([name, definition]) => [
|
||||||
name,
|
name,
|
||||||
{
|
{
|
||||||
definition,
|
definition,
|
||||||
@@ -2265,7 +2273,9 @@ export class AgentSession {
|
|||||||
const runner = this._extensionRunner;
|
const runner = this._extensionRunner;
|
||||||
const wrappedExtensionTools = wrapRegisteredTools(allCustomTools, runner);
|
const wrappedExtensionTools = wrapRegisteredTools(allCustomTools, runner);
|
||||||
const wrappedBuiltInTools = wrapRegisteredTools(
|
const wrappedBuiltInTools = wrapRegisteredTools(
|
||||||
Array.from(this._baseToolDefinitions.values()).map((definition) => ({
|
Array.from(this._baseToolDefinitions.values())
|
||||||
|
.filter((definition) => isAllowedTool(definition.name))
|
||||||
|
.map((definition) => ({
|
||||||
definition,
|
definition,
|
||||||
sourceInfo: createSyntheticSourceInfo(`<builtin:${definition.name}>`, { source: "builtin" }),
|
sourceInfo: createSyntheticSourceInfo(`<builtin:${definition.name}>`, { source: "builtin" }),
|
||||||
})),
|
})),
|
||||||
@@ -2278,11 +2288,17 @@ export class AgentSession {
|
|||||||
}
|
}
|
||||||
this._toolRegistry = toolRegistry;
|
this._toolRegistry = toolRegistry;
|
||||||
|
|
||||||
const nextActiveToolNames = options?.activeToolNames
|
const nextActiveToolNames = (
|
||||||
? [...options.activeToolNames]
|
options?.activeToolNames ? [...options.activeToolNames] : [...previousActiveToolNames]
|
||||||
: [...previousActiveToolNames];
|
).filter((name) => isAllowedTool(name));
|
||||||
|
|
||||||
if (options?.includeAllExtensionTools) {
|
if (allowedToolNames) {
|
||||||
|
for (const toolName of this._toolRegistry.keys()) {
|
||||||
|
if (allowedToolNames.has(toolName)) {
|
||||||
|
nextActiveToolNames.push(toolName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (options?.includeAllExtensionTools) {
|
||||||
for (const tool of wrappedExtensionTools) {
|
for (const tool of wrappedExtensionTools) {
|
||||||
nextActiveToolNames.push(tool.name);
|
nextActiveToolNames.push(tool.name);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,9 +16,6 @@ import { SettingsManager } from "./settings-manager.js";
|
|||||||
import { isInstallTelemetryEnabled } from "./telemetry.js";
|
import { isInstallTelemetryEnabled } from "./telemetry.js";
|
||||||
import { time } from "./timings.js";
|
import { time } from "./timings.js";
|
||||||
import {
|
import {
|
||||||
allTools,
|
|
||||||
bashTool,
|
|
||||||
codingTools,
|
|
||||||
createBashTool,
|
createBashTool,
|
||||||
createCodingTools,
|
createCodingTools,
|
||||||
createEditTool,
|
createEditTool,
|
||||||
@@ -28,16 +25,8 @@ import {
|
|||||||
createReadOnlyTools,
|
createReadOnlyTools,
|
||||||
createReadTool,
|
createReadTool,
|
||||||
createWriteTool,
|
createWriteTool,
|
||||||
editTool,
|
|
||||||
findTool,
|
|
||||||
grepTool,
|
|
||||||
lsTool,
|
|
||||||
readOnlyTools,
|
|
||||||
readTool,
|
|
||||||
type Tool,
|
|
||||||
type ToolName,
|
type ToolName,
|
||||||
withFileMutationQueue,
|
withFileMutationQueue,
|
||||||
writeTool,
|
|
||||||
} from "./tools/index.js";
|
} from "./tools/index.js";
|
||||||
|
|
||||||
export interface CreateAgentSessionOptions {
|
export interface CreateAgentSessionOptions {
|
||||||
@@ -58,8 +47,14 @@ export interface CreateAgentSessionOptions {
|
|||||||
/** Models available for cycling (Ctrl+P in interactive mode) */
|
/** Models available for cycling (Ctrl+P in interactive mode) */
|
||||||
scopedModels?: Array<{ model: Model<any>; thinkingLevel?: ThinkingLevel }>;
|
scopedModels?: Array<{ model: Model<any>; thinkingLevel?: ThinkingLevel }>;
|
||||||
|
|
||||||
/** Built-in tools to use. Default: codingTools [read, bash, edit, write] */
|
/**
|
||||||
tools?: Tool[];
|
* Optional allowlist of tool names.
|
||||||
|
*
|
||||||
|
* When omitted, pi enables the default built-in tools (read, bash, edit, write)
|
||||||
|
* and leaves extension/custom tools enabled.
|
||||||
|
* When provided, only the listed tool names are enabled.
|
||||||
|
*/
|
||||||
|
tools?: string[];
|
||||||
/** Custom tools to register (in addition to built-in tools). */
|
/** Custom tools to register (in addition to built-in tools). */
|
||||||
customTools?: ToolDefinition[];
|
customTools?: ToolDefinition[];
|
||||||
|
|
||||||
@@ -102,17 +97,6 @@ export type { Skill } from "./skills.js";
|
|||||||
export type { Tool } from "./tools/index.js";
|
export type { Tool } from "./tools/index.js";
|
||||||
|
|
||||||
export {
|
export {
|
||||||
// Pre-built tools (use process.cwd())
|
|
||||||
readTool,
|
|
||||||
bashTool,
|
|
||||||
editTool,
|
|
||||||
writeTool,
|
|
||||||
grepTool,
|
|
||||||
findTool,
|
|
||||||
lsTool,
|
|
||||||
codingTools,
|
|
||||||
readOnlyTools,
|
|
||||||
allTools as allBuiltInTools,
|
|
||||||
withFileMutationQueue,
|
withFileMutationQueue,
|
||||||
// Tool factories (for custom cwd)
|
// Tool factories (for custom cwd)
|
||||||
createCodingTools,
|
createCodingTools,
|
||||||
@@ -185,7 +169,7 @@ function getOpenRouterAttributionHeaders(
|
|||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
export async function createAgentSession(options: CreateAgentSessionOptions = {}): Promise<CreateAgentSessionResult> {
|
export async function createAgentSession(options: CreateAgentSessionOptions = {}): Promise<CreateAgentSessionResult> {
|
||||||
const cwd = options.cwd ?? process.cwd();
|
const cwd = options.cwd ?? options.sessionManager?.getCwd() ?? process.cwd();
|
||||||
const agentDir = options.agentDir ?? getDefaultAgentDir();
|
const agentDir = options.agentDir ?? getDefaultAgentDir();
|
||||||
let resourceLoader = options.resourceLoader;
|
let resourceLoader = options.resourceLoader;
|
||||||
|
|
||||||
@@ -261,9 +245,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|||||||
}
|
}
|
||||||
|
|
||||||
const defaultActiveToolNames: ToolName[] = ["read", "bash", "edit", "write"];
|
const defaultActiveToolNames: ToolName[] = ["read", "bash", "edit", "write"];
|
||||||
const initialActiveToolNames: ToolName[] = options.tools
|
const initialActiveToolNames: string[] = options.tools ? [...options.tools] : defaultActiveToolNames;
|
||||||
? options.tools.map((t) => t.name).filter((n): n is ToolName => n in allTools)
|
|
||||||
: defaultActiveToolNames;
|
|
||||||
|
|
||||||
let agent: Agent;
|
let agent: Agent;
|
||||||
|
|
||||||
@@ -384,6 +366,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|||||||
customTools: options.customTools,
|
customTools: options.customTools,
|
||||||
modelRegistry,
|
modelRegistry,
|
||||||
initialActiveToolNames,
|
initialActiveToolNames,
|
||||||
|
allowedToolNames: options.tools,
|
||||||
extensionRunnerRef,
|
extensionRunnerRef,
|
||||||
sessionStartEvent: options.sessionStartEvent,
|
sessionStartEvent: options.sessionStartEvent,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -5,8 +5,6 @@ export {
|
|||||||
type BashToolDetails,
|
type BashToolDetails,
|
||||||
type BashToolInput,
|
type BashToolInput,
|
||||||
type BashToolOptions,
|
type BashToolOptions,
|
||||||
bashTool,
|
|
||||||
bashToolDefinition,
|
|
||||||
createBashTool,
|
createBashTool,
|
||||||
createBashToolDefinition,
|
createBashToolDefinition,
|
||||||
createLocalBashOperations,
|
createLocalBashOperations,
|
||||||
@@ -18,8 +16,6 @@ export {
|
|||||||
type EditToolDetails,
|
type EditToolDetails,
|
||||||
type EditToolInput,
|
type EditToolInput,
|
||||||
type EditToolOptions,
|
type EditToolOptions,
|
||||||
editTool,
|
|
||||||
editToolDefinition,
|
|
||||||
} from "./edit.js";
|
} from "./edit.js";
|
||||||
export { withFileMutationQueue } from "./file-mutation-queue.js";
|
export { withFileMutationQueue } from "./file-mutation-queue.js";
|
||||||
export {
|
export {
|
||||||
@@ -29,8 +25,6 @@ export {
|
|||||||
type FindToolDetails,
|
type FindToolDetails,
|
||||||
type FindToolInput,
|
type FindToolInput,
|
||||||
type FindToolOptions,
|
type FindToolOptions,
|
||||||
findTool,
|
|
||||||
findToolDefinition,
|
|
||||||
} from "./find.js";
|
} from "./find.js";
|
||||||
export {
|
export {
|
||||||
createGrepTool,
|
createGrepTool,
|
||||||
@@ -39,8 +33,6 @@ export {
|
|||||||
type GrepToolDetails,
|
type GrepToolDetails,
|
||||||
type GrepToolInput,
|
type GrepToolInput,
|
||||||
type GrepToolOptions,
|
type GrepToolOptions,
|
||||||
grepTool,
|
|
||||||
grepToolDefinition,
|
|
||||||
} from "./grep.js";
|
} from "./grep.js";
|
||||||
export {
|
export {
|
||||||
createLsTool,
|
createLsTool,
|
||||||
@@ -49,8 +41,6 @@ export {
|
|||||||
type LsToolDetails,
|
type LsToolDetails,
|
||||||
type LsToolInput,
|
type LsToolInput,
|
||||||
type LsToolOptions,
|
type LsToolOptions,
|
||||||
lsTool,
|
|
||||||
lsToolDefinition,
|
|
||||||
} from "./ls.js";
|
} from "./ls.js";
|
||||||
export {
|
export {
|
||||||
createReadTool,
|
createReadTool,
|
||||||
@@ -59,8 +49,6 @@ export {
|
|||||||
type ReadToolDetails,
|
type ReadToolDetails,
|
||||||
type ReadToolInput,
|
type ReadToolInput,
|
||||||
type ReadToolOptions,
|
type ReadToolOptions,
|
||||||
readTool,
|
|
||||||
readToolDefinition,
|
|
||||||
} from "./read.js";
|
} from "./read.js";
|
||||||
export {
|
export {
|
||||||
DEFAULT_MAX_BYTES,
|
DEFAULT_MAX_BYTES,
|
||||||
@@ -78,80 +66,90 @@ export {
|
|||||||
type WriteOperations,
|
type WriteOperations,
|
||||||
type WriteToolInput,
|
type WriteToolInput,
|
||||||
type WriteToolOptions,
|
type WriteToolOptions,
|
||||||
writeTool,
|
|
||||||
writeToolDefinition,
|
|
||||||
} from "./write.js";
|
} from "./write.js";
|
||||||
|
|
||||||
import type { AgentTool } from "@mariozechner/pi-agent-core";
|
import type { AgentTool } from "@mariozechner/pi-agent-core";
|
||||||
import type { ToolDefinition } from "../extensions/types.js";
|
import type { ToolDefinition } from "../extensions/types.js";
|
||||||
import {
|
import { type BashToolOptions, createBashTool, createBashToolDefinition } from "./bash.js";
|
||||||
type BashToolOptions,
|
import { createEditTool, createEditToolDefinition, type EditToolOptions } from "./edit.js";
|
||||||
bashTool,
|
import { createFindTool, createFindToolDefinition, type FindToolOptions } from "./find.js";
|
||||||
bashToolDefinition,
|
import { createGrepTool, createGrepToolDefinition, type GrepToolOptions } from "./grep.js";
|
||||||
createBashTool,
|
import { createLsTool, createLsToolDefinition, type LsToolOptions } from "./ls.js";
|
||||||
createBashToolDefinition,
|
import { createReadTool, createReadToolDefinition, type ReadToolOptions } from "./read.js";
|
||||||
} from "./bash.js";
|
import { createWriteTool, createWriteToolDefinition, type WriteToolOptions } from "./write.js";
|
||||||
import { createEditTool, createEditToolDefinition, editTool, editToolDefinition } from "./edit.js";
|
|
||||||
import { createFindTool, createFindToolDefinition, findTool, findToolDefinition } from "./find.js";
|
|
||||||
import { createGrepTool, createGrepToolDefinition, grepTool, grepToolDefinition } from "./grep.js";
|
|
||||||
import { createLsTool, createLsToolDefinition, lsTool, lsToolDefinition } from "./ls.js";
|
|
||||||
import {
|
|
||||||
createReadTool,
|
|
||||||
createReadToolDefinition,
|
|
||||||
type ReadToolOptions,
|
|
||||||
readTool,
|
|
||||||
readToolDefinition,
|
|
||||||
} from "./read.js";
|
|
||||||
import { createWriteTool, createWriteToolDefinition, writeTool, writeToolDefinition } from "./write.js";
|
|
||||||
|
|
||||||
export type Tool = AgentTool<any>;
|
export type Tool = AgentTool<any>;
|
||||||
export type ToolDef = ToolDefinition<any, any>;
|
export type ToolDef = ToolDefinition<any, any>;
|
||||||
|
export type ToolName = "read" | "bash" | "edit" | "write" | "grep" | "find" | "ls";
|
||||||
export const codingTools: Tool[] = [readTool, bashTool, editTool, writeTool];
|
export const allToolNames: Set<ToolName> = new Set(["read", "bash", "edit", "write", "grep", "find", "ls"]);
|
||||||
export const readOnlyTools: Tool[] = [readTool, grepTool, findTool, lsTool];
|
|
||||||
|
|
||||||
export const allTools = {
|
|
||||||
read: readTool,
|
|
||||||
bash: bashTool,
|
|
||||||
edit: editTool,
|
|
||||||
write: writeTool,
|
|
||||||
grep: grepTool,
|
|
||||||
find: findTool,
|
|
||||||
ls: lsTool,
|
|
||||||
};
|
|
||||||
|
|
||||||
export const allToolDefinitions = {
|
|
||||||
read: readToolDefinition,
|
|
||||||
bash: bashToolDefinition,
|
|
||||||
edit: editToolDefinition,
|
|
||||||
write: writeToolDefinition,
|
|
||||||
grep: grepToolDefinition,
|
|
||||||
find: findToolDefinition,
|
|
||||||
ls: lsToolDefinition,
|
|
||||||
};
|
|
||||||
|
|
||||||
export type ToolName = keyof typeof allTools;
|
|
||||||
|
|
||||||
export interface ToolsOptions {
|
export interface ToolsOptions {
|
||||||
read?: ReadToolOptions;
|
read?: ReadToolOptions;
|
||||||
bash?: BashToolOptions;
|
bash?: BashToolOptions;
|
||||||
|
write?: WriteToolOptions;
|
||||||
|
edit?: EditToolOptions;
|
||||||
|
grep?: GrepToolOptions;
|
||||||
|
find?: FindToolOptions;
|
||||||
|
ls?: LsToolOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createToolDefinition(toolName: ToolName, cwd: string, options?: ToolsOptions): ToolDef {
|
||||||
|
switch (toolName) {
|
||||||
|
case "read":
|
||||||
|
return createReadToolDefinition(cwd, options?.read);
|
||||||
|
case "bash":
|
||||||
|
return createBashToolDefinition(cwd, options?.bash);
|
||||||
|
case "edit":
|
||||||
|
return createEditToolDefinition(cwd, options?.edit);
|
||||||
|
case "write":
|
||||||
|
return createWriteToolDefinition(cwd, options?.write);
|
||||||
|
case "grep":
|
||||||
|
return createGrepToolDefinition(cwd, options?.grep);
|
||||||
|
case "find":
|
||||||
|
return createFindToolDefinition(cwd, options?.find);
|
||||||
|
case "ls":
|
||||||
|
return createLsToolDefinition(cwd, options?.ls);
|
||||||
|
default:
|
||||||
|
throw new Error(`Unknown tool name: ${toolName}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createTool(toolName: ToolName, cwd: string, options?: ToolsOptions): Tool {
|
||||||
|
switch (toolName) {
|
||||||
|
case "read":
|
||||||
|
return createReadTool(cwd, options?.read);
|
||||||
|
case "bash":
|
||||||
|
return createBashTool(cwd, options?.bash);
|
||||||
|
case "edit":
|
||||||
|
return createEditTool(cwd, options?.edit);
|
||||||
|
case "write":
|
||||||
|
return createWriteTool(cwd, options?.write);
|
||||||
|
case "grep":
|
||||||
|
return createGrepTool(cwd, options?.grep);
|
||||||
|
case "find":
|
||||||
|
return createFindTool(cwd, options?.find);
|
||||||
|
case "ls":
|
||||||
|
return createLsTool(cwd, options?.ls);
|
||||||
|
default:
|
||||||
|
throw new Error(`Unknown tool name: ${toolName}`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createCodingToolDefinitions(cwd: string, options?: ToolsOptions): ToolDef[] {
|
export function createCodingToolDefinitions(cwd: string, options?: ToolsOptions): ToolDef[] {
|
||||||
return [
|
return [
|
||||||
createReadToolDefinition(cwd, options?.read),
|
createReadToolDefinition(cwd, options?.read),
|
||||||
createBashToolDefinition(cwd, options?.bash),
|
createBashToolDefinition(cwd, options?.bash),
|
||||||
createEditToolDefinition(cwd),
|
createEditToolDefinition(cwd, options?.edit),
|
||||||
createWriteToolDefinition(cwd),
|
createWriteToolDefinition(cwd, options?.write),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createReadOnlyToolDefinitions(cwd: string, options?: ToolsOptions): ToolDef[] {
|
export function createReadOnlyToolDefinitions(cwd: string, options?: ToolsOptions): ToolDef[] {
|
||||||
return [
|
return [
|
||||||
createReadToolDefinition(cwd, options?.read),
|
createReadToolDefinition(cwd, options?.read),
|
||||||
createGrepToolDefinition(cwd),
|
createGrepToolDefinition(cwd, options?.grep),
|
||||||
createFindToolDefinition(cwd),
|
createFindToolDefinition(cwd, options?.find),
|
||||||
createLsToolDefinition(cwd),
|
createLsToolDefinition(cwd, options?.ls),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -159,11 +157,11 @@ export function createAllToolDefinitions(cwd: string, options?: ToolsOptions): R
|
|||||||
return {
|
return {
|
||||||
read: createReadToolDefinition(cwd, options?.read),
|
read: createReadToolDefinition(cwd, options?.read),
|
||||||
bash: createBashToolDefinition(cwd, options?.bash),
|
bash: createBashToolDefinition(cwd, options?.bash),
|
||||||
edit: createEditToolDefinition(cwd),
|
edit: createEditToolDefinition(cwd, options?.edit),
|
||||||
write: createWriteToolDefinition(cwd),
|
write: createWriteToolDefinition(cwd, options?.write),
|
||||||
grep: createGrepToolDefinition(cwd),
|
grep: createGrepToolDefinition(cwd, options?.grep),
|
||||||
find: createFindToolDefinition(cwd),
|
find: createFindToolDefinition(cwd, options?.find),
|
||||||
ls: createLsToolDefinition(cwd),
|
ls: createLsToolDefinition(cwd, options?.ls),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -171,23 +169,28 @@ export function createCodingTools(cwd: string, options?: ToolsOptions): Tool[] {
|
|||||||
return [
|
return [
|
||||||
createReadTool(cwd, options?.read),
|
createReadTool(cwd, options?.read),
|
||||||
createBashTool(cwd, options?.bash),
|
createBashTool(cwd, options?.bash),
|
||||||
createEditTool(cwd),
|
createEditTool(cwd, options?.edit),
|
||||||
createWriteTool(cwd),
|
createWriteTool(cwd, options?.write),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createReadOnlyTools(cwd: string, options?: ToolsOptions): Tool[] {
|
export function createReadOnlyTools(cwd: string, options?: ToolsOptions): Tool[] {
|
||||||
return [createReadTool(cwd, options?.read), createGrepTool(cwd), createFindTool(cwd), createLsTool(cwd)];
|
return [
|
||||||
|
createReadTool(cwd, options?.read),
|
||||||
|
createGrepTool(cwd, options?.grep),
|
||||||
|
createFindTool(cwd, options?.find),
|
||||||
|
createLsTool(cwd, options?.ls),
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createAllTools(cwd: string, options?: ToolsOptions): Record<ToolName, Tool> {
|
export function createAllTools(cwd: string, options?: ToolsOptions): Record<ToolName, Tool> {
|
||||||
return {
|
return {
|
||||||
read: createReadTool(cwd, options?.read),
|
read: createReadTool(cwd, options?.read),
|
||||||
bash: createBashTool(cwd, options?.bash),
|
bash: createBashTool(cwd, options?.bash),
|
||||||
edit: createEditTool(cwd),
|
edit: createEditTool(cwd, options?.edit),
|
||||||
write: createWriteTool(cwd),
|
write: createWriteTool(cwd, options?.write),
|
||||||
grep: createGrepTool(cwd),
|
grep: createGrepTool(cwd, options?.grep),
|
||||||
find: createFindTool(cwd),
|
find: createFindTool(cwd, options?.find),
|
||||||
ls: createLsTool(cwd),
|
ls: createLsTool(cwd, options?.ls),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,7 +39,6 @@ import {
|
|||||||
import { SessionManager } from "./core/session-manager.js";
|
import { SessionManager } from "./core/session-manager.js";
|
||||||
import { SettingsManager } from "./core/settings-manager.js";
|
import { SettingsManager } from "./core/settings-manager.js";
|
||||||
import { printTimings, resetTimings, time } from "./core/timings.js";
|
import { printTimings, resetTimings, time } from "./core/timings.js";
|
||||||
import { allTools } from "./core/tools/index.js";
|
|
||||||
import { runMigrations, showDeprecationWarnings } from "./migrations.js";
|
import { runMigrations, showDeprecationWarnings } from "./migrations.js";
|
||||||
import { InteractiveMode, runPrintMode, runRpcMode } from "./modes/index.js";
|
import { InteractiveMode, runPrintMode, runRpcMode } from "./modes/index.js";
|
||||||
import { ExtensionSelectorComponent } from "./modes/interactive/components/extension-selector.js";
|
import { ExtensionSelectorComponent } from "./modes/interactive/components/extension-selector.js";
|
||||||
@@ -368,14 +367,10 @@ function buildSessionOptions(
|
|||||||
// Tools
|
// Tools
|
||||||
if (parsed.noTools) {
|
if (parsed.noTools) {
|
||||||
// --no-tools: start with no built-in tools
|
// --no-tools: start with no built-in tools
|
||||||
// --tools can still add specific ones back
|
// --tools can still add specific ones back, including extension tools.
|
||||||
if (parsed.tools && parsed.tools.length > 0) {
|
options.tools = parsed.tools && parsed.tools.length > 0 ? [...parsed.tools] : [];
|
||||||
options.tools = parsed.tools.map((name) => allTools[name]);
|
|
||||||
} else {
|
|
||||||
options.tools = [];
|
|
||||||
}
|
|
||||||
} else if (parsed.tools) {
|
} else if (parsed.tools) {
|
||||||
options.tools = parsed.tools.map((name) => allTools[name]);
|
options.tools = [...parsed.tools];
|
||||||
}
|
}
|
||||||
|
|
||||||
return { options, cliThinkingFromModel, diagnostics };
|
return { options, cliThinkingFromModel, diagnostics };
|
||||||
|
|||||||
@@ -22,7 +22,6 @@ import {
|
|||||||
} from "../src/core/agent-session-runtime.js";
|
} from "../src/core/agent-session-runtime.js";
|
||||||
import { AuthStorage } from "../src/core/auth-storage.js";
|
import { AuthStorage } from "../src/core/auth-storage.js";
|
||||||
import { SessionManager } from "../src/core/session-manager.js";
|
import { SessionManager } from "../src/core/session-manager.js";
|
||||||
import { codingTools } from "../src/core/tools/index.js";
|
|
||||||
import { API_KEY } from "./utilities.js";
|
import { API_KEY } from "./utilities.js";
|
||||||
|
|
||||||
describe.skipIf(!API_KEY)("AgentSession forking", () => {
|
describe.skipIf(!API_KEY)("AgentSession forking", () => {
|
||||||
@@ -40,7 +39,6 @@ describe.skipIf(!API_KEY)("AgentSession forking", () => {
|
|||||||
if (runtimeHost) {
|
if (runtimeHost) {
|
||||||
await runtimeHost.dispose();
|
await runtimeHost.dispose();
|
||||||
}
|
}
|
||||||
process.chdir(tmpdir());
|
|
||||||
if (tempDir && existsSync(tempDir)) {
|
if (tempDir && existsSync(tempDir)) {
|
||||||
rmSync(tempDir, { recursive: true });
|
rmSync(tempDir, { recursive: true });
|
||||||
}
|
}
|
||||||
@@ -73,7 +71,7 @@ describe.skipIf(!API_KEY)("AgentSession forking", () => {
|
|||||||
sessionManager,
|
sessionManager,
|
||||||
sessionStartEvent,
|
sessionStartEvent,
|
||||||
model,
|
model,
|
||||||
tools: codingTools,
|
tools: ["read", "bash", "edit", "write"],
|
||||||
})),
|
})),
|
||||||
services,
|
services,
|
||||||
diagnostics: services.diagnostics,
|
diagnostics: services.diagnostics,
|
||||||
|
|||||||
@@ -76,7 +76,6 @@ describe("AgentSessionRuntime session lifecycle events", () => {
|
|||||||
cleanups.push(async () => {
|
cleanups.push(async () => {
|
||||||
await runtimeHost.dispose();
|
await runtimeHost.dispose();
|
||||||
faux.unregister();
|
faux.unregister();
|
||||||
process.chdir(tmpdir());
|
|
||||||
if (existsSync(tempDir)) {
|
if (existsSync(tempDir)) {
|
||||||
rmSync(tempDir, { recursive: true, force: true });
|
rmSync(tempDir, { recursive: true, force: true });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import { AgentSession } from "../src/core/agent-session.js";
|
|||||||
import { ModelRegistry } from "../src/core/model-registry.js";
|
import { ModelRegistry } from "../src/core/model-registry.js";
|
||||||
import { SessionManager } from "../src/core/session-manager.js";
|
import { SessionManager } from "../src/core/session-manager.js";
|
||||||
import { SettingsManager } from "../src/core/settings-manager.js";
|
import { SettingsManager } from "../src/core/settings-manager.js";
|
||||||
import { codingTools } from "../src/core/tools/index.js";
|
import { createCodingTools } from "../src/core/tools/index.js";
|
||||||
import {
|
import {
|
||||||
API_KEY,
|
API_KEY,
|
||||||
createTestResourceLoader,
|
createTestResourceLoader,
|
||||||
@@ -70,7 +70,7 @@ describe.skipIf(!HAS_ANTIGRAVITY_AUTH)("Compaction with thinking models (Antigra
|
|||||||
initialState: {
|
initialState: {
|
||||||
model,
|
model,
|
||||||
systemPrompt: "You are a helpful assistant. Be concise.",
|
systemPrompt: "You are a helpful assistant. Be concise.",
|
||||||
tools: codingTools,
|
tools: createCodingTools(process.cwd()),
|
||||||
thinkingLevel,
|
thinkingLevel,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -168,7 +168,7 @@ describe.skipIf(!HAS_ANTHROPIC_AUTH)("Compaction with thinking models (Anthropic
|
|||||||
initialState: {
|
initialState: {
|
||||||
model,
|
model,
|
||||||
systemPrompt: "You are a helpful assistant. Be concise.",
|
systemPrompt: "You are a helpful assistant. Be concise.",
|
||||||
tools: codingTools,
|
tools: createCodingTools(process.cwd()),
|
||||||
thinkingLevel,
|
thinkingLevel,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { existsSync, mkdirSync, rmSync } from "node:fs";
|
import { existsSync, mkdirSync, realpathSync, rmSync } from "node:fs";
|
||||||
import { tmpdir } from "node:os";
|
import { tmpdir } from "node:os";
|
||||||
import { join } from "node:path";
|
import { join } from "node:path";
|
||||||
import { getModel } from "@mariozechner/pi-ai";
|
import { getModel } from "@mariozechner/pi-ai";
|
||||||
@@ -63,4 +63,33 @@ describe("createAgentSession session manager defaults", () => {
|
|||||||
|
|
||||||
session.dispose();
|
session.dispose();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("derives cwd from an explicit sessionManager when cwd is omitted", async () => {
|
||||||
|
const model = getModel("anthropic", "claude-sonnet-4-5");
|
||||||
|
expect(model).toBeTruthy();
|
||||||
|
|
||||||
|
const sessionCwd = join(tempDir, "session-project");
|
||||||
|
mkdirSync(sessionCwd, { recursive: true });
|
||||||
|
const sessionManager = SessionManager.inMemory(sessionCwd);
|
||||||
|
const { session } = await createAgentSession({
|
||||||
|
agentDir,
|
||||||
|
model: model!,
|
||||||
|
sessionManager,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(session.sessionManager).toBe(sessionManager);
|
||||||
|
expect(session.systemPrompt).toContain(`Current working directory: ${sessionCwd}`);
|
||||||
|
|
||||||
|
const bashTool = session.agent.state.tools.find((tool) => tool.name === "bash");
|
||||||
|
expect(bashTool).toBeTruthy();
|
||||||
|
const result = await bashTool!.execute("test", { command: "pwd" });
|
||||||
|
const output = result.content
|
||||||
|
.filter((item): item is { type: "text"; text: string } => item.type === "text")
|
||||||
|
.map((item) => item.text)
|
||||||
|
.join("");
|
||||||
|
|
||||||
|
expect(realpathSync(output.trim())).toBe(realpathSync(sessionCwd));
|
||||||
|
|
||||||
|
session.dispose();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -28,7 +28,6 @@ describe("AgentSessionRuntime characterization", () => {
|
|||||||
while (cleanups.length > 0) {
|
while (cleanups.length > 0) {
|
||||||
await cleanups.pop()?.();
|
await cleanups.pop()?.();
|
||||||
}
|
}
|
||||||
process.chdir(tmpdir());
|
|
||||||
});
|
});
|
||||||
|
|
||||||
async function createRuntimeForTest(
|
async function createRuntimeForTest(
|
||||||
@@ -391,7 +390,7 @@ describe("AgentSessionRuntime characterization", () => {
|
|||||||
await expect(runtime.fork("missing-entry")).rejects.toThrow("Invalid entry ID for forking");
|
await expect(runtime.fork("missing-entry")).rejects.toThrow("Invalid entry ID for forking");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("updates process.cwd() on cross-cwd session replacement", async () => {
|
it("updates the runtime session cwd on cross-cwd session replacement", async () => {
|
||||||
const firstDir = join(tmpdir(), `pi-runtime-cwd-a-${Date.now()}-${Math.random().toString(36).slice(2)}`);
|
const firstDir = join(tmpdir(), `pi-runtime-cwd-a-${Date.now()}-${Math.random().toString(36).slice(2)}`);
|
||||||
const secondDir = join(tmpdir(), `pi-runtime-cwd-b-${Date.now()}-${Math.random().toString(36).slice(2)}`);
|
const secondDir = join(tmpdir(), `pi-runtime-cwd-b-${Date.now()}-${Math.random().toString(36).slice(2)}`);
|
||||||
mkdirSync(firstDir, { recursive: true });
|
mkdirSync(firstDir, { recursive: true });
|
||||||
@@ -459,8 +458,8 @@ describe("AgentSessionRuntime characterization", () => {
|
|||||||
|
|
||||||
await runtime.switchSession(otherSessionFile);
|
await runtime.switchSession(otherSessionFile);
|
||||||
|
|
||||||
expect(realpathSync(process.cwd())).toBe(realpathSync(secondDir));
|
|
||||||
expect(realpathSync(runtime.session.sessionManager.getCwd())).toBe(realpathSync(secondDir));
|
expect(realpathSync(runtime.session.sessionManager.getCwd())).toBe(realpathSync(secondDir));
|
||||||
|
expect(realpathSync(runtime.cwd)).toBe(realpathSync(secondDir));
|
||||||
});
|
});
|
||||||
|
|
||||||
it("restores model and thinking state from the destination session", async () => {
|
it("restores model and thinking state from the destination session", async () => {
|
||||||
|
|||||||
@@ -0,0 +1,94 @@
|
|||||||
|
import { existsSync, mkdirSync, rmSync } from "node:fs";
|
||||||
|
import { tmpdir } from "node:os";
|
||||||
|
import { join } from "node:path";
|
||||||
|
import { getModel } from "@mariozechner/pi-ai";
|
||||||
|
import { Type } from "@sinclair/typebox";
|
||||||
|
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
||||||
|
import { DefaultResourceLoader } from "../../../src/core/resource-loader.js";
|
||||||
|
import { createAgentSession } from "../../../src/core/sdk.js";
|
||||||
|
import { SessionManager } from "../../../src/core/session-manager.js";
|
||||||
|
import { SettingsManager } from "../../../src/core/settings-manager.js";
|
||||||
|
|
||||||
|
describe("regression #2835: tool allowlists filter extension tools", () => {
|
||||||
|
let tempDir: string;
|
||||||
|
let agentDir: string;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
tempDir = join(tmpdir(), `pi-tools-filter-${Date.now()}-${Math.random().toString(36).slice(2)}`);
|
||||||
|
agentDir = join(tempDir, "agent");
|
||||||
|
mkdirSync(agentDir, { recursive: true });
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
if (tempDir && existsSync(tempDir)) {
|
||||||
|
rmSync(tempDir, { recursive: true, force: true });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
async function createSession(allowedToolNames?: string[]) {
|
||||||
|
const settingsManager = SettingsManager.create(tempDir, agentDir);
|
||||||
|
const sessionManager = SessionManager.inMemory(tempDir);
|
||||||
|
const resourceLoader = new DefaultResourceLoader({
|
||||||
|
cwd: tempDir,
|
||||||
|
agentDir,
|
||||||
|
settingsManager,
|
||||||
|
extensionFactories: [
|
||||||
|
(pi) => {
|
||||||
|
pi.on("session_start", () => {
|
||||||
|
pi.registerTool({
|
||||||
|
name: "dynamic_tool",
|
||||||
|
label: "Dynamic Tool",
|
||||||
|
description: "Tool registered from session_start",
|
||||||
|
promptSnippet: "Run dynamic test behavior",
|
||||||
|
parameters: Type.Object({}),
|
||||||
|
execute: async () => ({
|
||||||
|
content: [{ type: "text", text: "ok" }],
|
||||||
|
details: {},
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
await resourceLoader.reload();
|
||||||
|
|
||||||
|
const { session } = await createAgentSession({
|
||||||
|
cwd: tempDir,
|
||||||
|
agentDir,
|
||||||
|
model: getModel("anthropic", "claude-sonnet-4-5")!,
|
||||||
|
settingsManager,
|
||||||
|
sessionManager,
|
||||||
|
resourceLoader,
|
||||||
|
tools: allowedToolNames,
|
||||||
|
});
|
||||||
|
await session.bindExtensions({});
|
||||||
|
return session;
|
||||||
|
}
|
||||||
|
|
||||||
|
it("allows only explicitly listed built-in and extension tools", async () => {
|
||||||
|
const session = await createSession(["read", "dynamic_tool"]);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
session
|
||||||
|
.getAllTools()
|
||||||
|
.map((tool) => tool.name)
|
||||||
|
.sort(),
|
||||||
|
).toEqual(["dynamic_tool", "read"]);
|
||||||
|
expect(session.getActiveToolNames().sort()).toEqual(["dynamic_tool", "read"]);
|
||||||
|
expect(session.systemPrompt).toContain("- read: Read file contents");
|
||||||
|
expect(session.systemPrompt).toContain("- dynamic_tool: Run dynamic test behavior");
|
||||||
|
expect(session.systemPrompt).not.toContain("- bash:");
|
||||||
|
expect(session.systemPrompt).not.toContain("- edit:");
|
||||||
|
session.dispose();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("disables all tools when the allowlist is empty", async () => {
|
||||||
|
const session = await createSession([]);
|
||||||
|
|
||||||
|
expect(session.getAllTools()).toEqual([]);
|
||||||
|
expect(session.getActiveToolNames()).toEqual([]);
|
||||||
|
expect(session.systemPrompt).toContain("Available tools:\n(none)");
|
||||||
|
expect(session.systemPrompt).not.toContain("dynamic_tool");
|
||||||
|
session.dispose();
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user