/** * Tool Execution Service * * Defines and executes file manipulation tools for LLM-driven code generation. * These tools allow the LLM to actually create, modify, and delete files. * * Tools supported: * - write: Create or overwrite files * - read_file: Read file contents * - search_replace: Make targeted edits to files * - delete_file: Remove files * - run_terminal_cmd: Execute shell commands * - list_dir: List directory contents * - glob_file_search: Find files by pattern * - grep: Search file contents * - codebase_search: Semantic code search * - query_session: Query previous session data for context reuse * * ## Failure vs Guidance * * Tool results are classified by stage-executor as either a *failure* (content * starts with `"Error:"`) or a *success*. To avoid inflating the failure counter * with recoverable LLM mistakes, tools in this service follow a strict policy: * * **Throw / return `"Error:…"`** — only for genuine system faults: unknown tool * name, filesystem permission errors, unexpected exceptions propagated from the * OS, or commands that exit with code ≥ 2. * * **Return a plain string** — for all *guidance* situations where the LLM made a * correctable mistake and should retry with better arguments: * - Missing or invalid arguments * - File / directory not found * - File too large → suggests selective extraction * - Structured file read via read_file → suggests jq/yq * - Protected-file overwrite attempt → suggests read-first * - search_replace old_str not found → suggests reading the file first * - rg/grep exit code 1 (no matches) → returns "No matches found" * - Blocked path access * * This keeps the hard-stop threshold (MAX_TOOL_FAILURES_BEFORE_HARD_STOP in * stage-executor) meaningful: it only fires when tools are genuinely broken, * not when the LLM is working through normal search/navigation patterns. */ import { type EffectivePermissions } from '../security/permission-propagation.service.js'; import type { MCPClientManagerService } from '../mcp/mcp-client-manager.service.js'; import type { LLMToolCall, LLMToolDefinition, LLMToolResult } from '../types/llm.types.js'; import { type MCPToolHandler } from '../mcp/mcp-tool-handler.js'; import { type AllowedTool } from '../types/command.types.js'; import { type IdempotencyOptions } from '../types/idempotency.types.js'; import { type TraceContext } from '../types/tracing.types.js'; import { type SimulatedOperation } from './tools/dry-run-simulator.service.js'; export declare class ToolExecutionService { private readonly astToolsService; private readonly hookExecutionService; private readonly idempotencyStore; private readonly logger; private readonly lspToolsService; private mcpClientManager; private mcpToolHandler; private readonly searchToolsService; private readonly sessionToolsService; private readonly tracer; private readonly workingDir; /** * Session ID for scoping idempotency keys * When set, idempotency is scoped to the current session */ private sessionId?; /** * Trace context for distributed tracing * When set, tool execution spans are linked to the parent trace */ private traceContext?; /** * Idempotency options for tool execution */ private idempotencyOptions; /** * Tracks files that have been read in this session. * Used to allow writes to protected files only if they were read first, * preventing blind overwrites while still allowing intentional updates. */ private readonly readFiles; /** * Cached result of rg (ripgrep) availability check. * null = not yet checked, true/false = result of check. */ private rgAvailableCache; /** * Dry-run mode flag * When enabled, state-changing tools are simulated instead of executed */ private dryRunMode; /** * Dry-run simulator instance (lazy-initialized) */ private dryRunSimulator; /** * Effective permission constraints propagated from the parent execution context. * Writes and deletes are blocked for paths in forbidden_paths; paths matching * requires_approval_for are queued for human confirmation. */ private effectiveConstraints; constructor(workingDir?: string); /** * Set the trace context for distributed tracing * Tool execution spans will be children of this context */ setTraceContext(context: TraceContext): void; /** * Get the current trace context */ getTraceContext(): TraceContext | undefined; /** * Set the session ID for idempotency scoping * This should be called at the start of a command execution */ setSessionId(sessionId: string): void; /** * Configure idempotency options */ setIdempotencyOptions(options: IdempotencyOptions): void; /** * Set the MCP tool handler for executing MCP tools */ setMCPToolHandler(handler: MCPToolHandler): void; /** * Set the MCP client manager for generating MCP tool definitions */ setMCPClientManager(clientManager: MCPClientManagerService): void; /** * Disable idempotency for the current execution * Useful when you want to force re-execution of all tools */ disableIdempotency(): void; /** * Enable idempotency for the current execution */ enableIdempotency(): void; /** * Enable or disable dry-run mode * In dry-run mode, state-changing tools are simulated instead of executed */ setDryRunMode(enabled: boolean): void; /** * Check if dry-run mode is enabled */ isDryRunMode(): boolean; /** * Set the effective permission constraints for this execution context. * Must be called before any tool execution to enforce path restrictions. */ setEffectiveConstraints(constraints: EffectivePermissions): void; /** * Get simulated operations from dry-run mode */ getSimulatedOperations(): SimulatedOperation[]; /** * Clear simulated operations */ clearSimulatedOperations(): void; /** * Get tool definitions for the specified allowed tools * Built-in tools are returned from static definitions. * MCP tools are generated as gateway tools that route to external MCP servers. */ getToolDefinitions(allowedTools: AllowedTool[]): LLMToolDefinition[]; /** * Build the description for an MCP tool definition */ private buildMCPToolDescription; /** * Get the tool_name parameter description based on available tools */ private getToolNameDescription; /** * Generate a gateway tool definition for an MCP tool * This creates a tool that accepts tool_name and arguments parameters, * allowing the LLM to call any tool on the connected MCP server. */ private generateMCPToolDefinition; /** * Reset state for a new command execution * Clears pending writes and resets confirmation state * Should be called at the start of each command execution */ resetForNewCommand(): void; /** * Invalidate all idempotency records for the current session * Call this when session state changes significantly */ invalidateSessionIdempotency(): Promise; /** * Invalidate idempotency records for a specific tool * Useful when external changes affect tool results */ invalidateToolIdempotency(toolName: string): number; /** * Get idempotency store statistics */ getIdempotencyStats(): { max_records: number; record_count: number; store_dir: string; }; /** * Execute a tool call and return the result * * For idempotent tools (write, search_replace, delete_file, run_terminal_cmd), * checks the idempotency store first and returns cached result if available. * This prevents duplicate operations when the same tool call is retried. * * In dry-run mode, state-changing tools are simulated instead of executed. * Read-only tools execute normally even in dry-run mode. */ executeTool(toolCall: LLMToolCall): Promise; /** * Internal method to execute a tool with span tracking */ private executeToolWithSpan; /** * Execute multiple tool calls in parallel */ executeTools(toolCalls: LLMToolCall[]): Promise; /** * Route tool execution to the appropriate handler */ private executeToolByName; /** * Execute an MCP tool call * Routes the call to MCPToolHandler for connection management and execution */ private executeMcpTool; /** * Paths that require confirmation before writing * These are typically documentation/knowledge-base paths where user review is important */ private static readonly CONFIRM_WRITE_PATHS; /** * Pending writes that require confirmation * These are queued during pipeline execution and processed at the end */ private pendingWrites; /** * Write content to a file */ private executeWrite; /** * Check if there are pending writes that need confirmation */ hasPendingWrites(): boolean; /** * Get the count of pending writes */ getPendingWritesCount(): number; /** * Process all pending writes with user confirmation * Called at the end of pipeline execution * Returns the number of files successfully written */ flushPendingWrites(): Promise<{ skipped: number; written: number; }>; /** * Maximum file size to read (1MB) - prevents reading extremely large files * that could cause context overflow */ private static readonly MAX_READ_FILE_SIZE; /** * Paths that should not be read (contain sensitive or very large data) */ private static readonly BLOCKED_READ_PATHS; /** * Protected files that should not be overwritten if they already exist. * The write tool will reject attempts to overwrite these files and suggest * using search_replace instead. */ private static readonly PROTECTED_FILES; /** * Read file contents */ private executeReadFile; /** * Returns guidance for structured files (JSON/YAML/TOML/XML) that should be * read with jq/yq rather than read_file, or null if the file is not structured. * * Returns a string rather than throwing so the LLM receives actionable guidance * without the result being counted as a tool failure. */ private structuredFileGuidance; /** * Returns guidance for reading large files (>100 lines) with selective CLI tools, * or null if the file is within the line limit. * * Returns a string rather than throwing so the LLM receives actionable guidance * without the result being counted as a tool failure. */ private lineCountGuidance; /** * Search and replace in a file */ private executeSearchReplace; /** * Delete a file */ private executeDeleteFile; /** * Check whether rg (ripgrep) is available in the augmented PATH. * Result is cached so the check only runs once per service instance. */ private checkRgAvailable; /** * Translate an `rg` command to an equivalent `grep` command. * Called when ripgrep is not available on the user's system. * Handles the flag subset the LLM is instructed to use. */ private static translateRgToGrep; /** * Build a PATH string that includes the package-local vendor/bin directory * (populated by scripts/postinstall.mjs) and user-local bin directories. * * VENDOR_BIN takes priority so the pinned tool versions bundled with valora * are used instead of whatever happens to be on the user's system PATH. * * Compatible with Linux, macOS, Windows, and devcontainer environments. */ private buildAugmentedPath; /** * Resolve a command string, transparently translating rg to grep when * ripgrep is not available on the user's system. */ private resolveCommand; /** * Execute a terminal command */ private executeTerminalCmd; /** * List directory contents */ private executeListDir; /** * Web search (placeholder - would integrate with actual web search) */ private executeWebSearch; /** * Resolve a path relative to the working directory */ private resolvePath; /** * Validate and resolve a path for write operations. * Validates both the original path and the resolved full path against forbidden paths, * and — unlike the read-only `resolvePath()` used by read_file/list_dir, which is * deliberately not cwd-scoped — requires the resolved path to stay inside * `workingDir`. `resolvePath()`'s own absolute-verbatim/naive-concat logic runs * first so a relative `path` resolves against `workingDir` rather than * `process.cwd()` (which can differ, e.g. in exploration contexts); the result * is already absolute, so `InputValidator.validatePath`'s own `path.resolve()` * only normalises it and never falls back to `process.cwd()`. * * @param path - The path to validate and resolve * @param operation - The operation being attempted (e.g., "write to", "delete", "modify") * @returns The resolved full path * @throws Error if the path is in a forbidden location or outside the working directory */ private validateAndResolvePath; /** * Get a summary of tool arguments for logging * Returns the primary argument value that identifies what the tool is operating on */ private getToolArgSummary; } /** * Returns true if a command is an exploratory/probing command that commonly * exits with code 1 to signal "not found" or "false" rather than an error. * * - `which`, `command -v`, `type` — command existence checks * - `test`, `[` — file/condition tests * - `fd` — file finder (same convention as rg) * - Piped commands where the first segment is `cd` — directory probing */ export declare function isExploratoryExitCode(command: string): boolean; export declare function getToolExecutionService(workingDir?: string): ToolExecutionService; //# sourceMappingURL=tool-execution.service.d.ts.map