From 7087c0508ea9260abaa31b0cd8f206d3b11cf21b Mon Sep 17 00:00:00 2001 From: Olcan Date: Fri, 25 Apr 2025 14:05:58 -0700 Subject: more consistent confirmations, TODO to improve write confirmations, drop "description" from execution confirmation, add confirmation to new (still dummy) shell tool (#176) --- packages/server/src/tools/shell.ts | 50 +++++++++++++++++++++++++++++++---- packages/server/src/tools/terminal.ts | 2 -- packages/server/src/tools/tools.ts | 1 - 3 files changed, 45 insertions(+), 8 deletions(-) (limited to 'packages/server/src/tools') diff --git a/packages/server/src/tools/shell.ts b/packages/server/src/tools/shell.ts index 7af6e703..bf4cf810 100644 --- a/packages/server/src/tools/shell.ts +++ b/packages/server/src/tools/shell.ts @@ -4,10 +4,15 @@ * SPDX-License-Identifier: Apache-2.0 */ -import path from 'path'; import fs from 'fs'; import { Config } from '../config/config.js'; -import { BaseTool, ToolResult } from './tools.js'; +import { + BaseTool, + ToolResult, + ToolCallConfirmationDetails, + ToolExecuteConfirmationDetails, + ToolConfirmationOutcome, +} from './tools.js'; import toolParameterSchema from './shell.json' with { type: 'json' }; export interface ShellToolParams { @@ -17,10 +22,11 @@ export interface ShellToolParams { export class ShellTool extends BaseTool { static Name: string = 'execute_bash_command'; - private readonly rootDirectory: string; private readonly config: Config; + private cwd: string; + private whitelist: Set = new Set(); - constructor(rootDirectory: string, config: Config) { + constructor(config: Config) { const toolDisplayName = 'Shell'; const descriptionUrl = new URL('shell.md', import.meta.url); const toolDescription = fs.readFileSync(descriptionUrl, 'utf-8'); @@ -31,7 +37,41 @@ export class ShellTool extends BaseTool { toolParameterSchema, ); this.config = config; - this.rootDirectory = path.resolve(rootDirectory); + this.cwd = config.getTargetDir(); + } + + getDescription(params: ShellToolParams): string { + return params.description || `Execute \`${params.command}\` in ${this.cwd}`; + } + + validateToolParams(_params: ShellToolParams): string | null { + // TODO: validate the command here + return null; + } + + async shouldConfirmExecute( + params: ShellToolParams, + ): Promise { + const rootCommand = + params.command + .trim() + .split(/[\s;&&|]+/)[0] + ?.split(/[/\\]/) + .pop() || 'unknown'; + if (this.whitelist.has(rootCommand)) { + return false; + } + const confirmationDetails: ToolExecuteConfirmationDetails = { + title: 'Confirm Shell Command', + command: params.command, + rootCommand, + onConfirm: async (outcome: ToolConfirmationOutcome) => { + if (outcome === ToolConfirmationOutcome.ProceedAlways) { + this.whitelist.add(rootCommand); + } + }, + }; + return confirmationDetails; } async execute(_params: ShellToolParams): Promise { diff --git a/packages/server/src/tools/terminal.ts b/packages/server/src/tools/terminal.ts index 0d5c3d96..64b8e652 100644 --- a/packages/server/src/tools/terminal.ts +++ b/packages/server/src/tools/terminal.ts @@ -253,12 +253,10 @@ Use this tool for running build steps (\`npm install\`, \`make\`), linters (\`es if (this.shouldAlwaysExecuteCommands.get(rootCommand)) { return false; } - const description = this.getDescription(params); const confirmationDetails: ToolExecuteConfirmationDetails = { title: 'Confirm Shell Command', command: params.command, rootCommand, - description: `Execute in '${this.currentCwd}':\n${description}`, onConfirm: async (outcome: ToolConfirmationOutcome) => { if (outcome === ToolConfirmationOutcome.ProceedAlways) { this.shouldAlwaysExecuteCommands.set(rootCommand, true); diff --git a/packages/server/src/tools/tools.ts b/packages/server/src/tools/tools.ts index ed7c017a..448ea206 100644 --- a/packages/server/src/tools/tools.ts +++ b/packages/server/src/tools/tools.ts @@ -181,7 +181,6 @@ export interface ToolExecuteConfirmationDetails extends ToolCallConfirmationDetails { command: string; rootCommand: string; - description: string; } export enum ToolConfirmationOutcome { -- cgit v1.2.3