feat(tools): M2+M3 — ToolRegistry, FileReadTool, FileEditTool, real permission callback#33
Conversation
- ToolRegistry: static getAllBaseTools/filterToolsByDenyRules/assembleToolPool/findByName - FileReadTool: workspace-safe file read with offset/limit pagination and 2MB byte cap - runToolUseBatch: concurrent-safe partition (safe group parallel, unsafe serial) - Fix: replace limitedParallelism with Semaphore for correct coroutine-level concurrency bound - Tests: ToolExecutorBatchTest (8), ToolRegistryTest (8) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- FileEditTool: workspace-safe string replacement with unified diff preview; checkPermissions returns Ask (DEFAULT) / Allow (ACCEPT_EDITS/BYPASS) - ToolExecutionContext: add onPermissionAsked suspend callback (default auto-allow preserves pre-M3 behaviour for existing callers) - runToolUse: wire real callback — emits PermissionAsked then suspends; emits Failed(PERMISSION) if callback returns false - ToolRegistry: register FileEditTool - Tests: FileEditToolTest (22) covering validate/permissions/preview/call/diff/callback Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
PR Reviewer Guide 🔍Here are some key observations to aid the review process:
|
PR Code Suggestions ✨Explore these optional code suggestions:
|
- McpTypes: JSON-RPC 2.0 wire types + MCP protocol shapes (McpToolSpec, McpCallResult)
- McpClient: stdio JSON-RPC client; connect() does initialize handshake + tools/list;
call() dispatches tools/call with CompletableDeferred response matching;
fromStreams() factory enables injection of fake pipes in tests
- McpProxyTool: factory that wraps a McpToolSpec as Tool<JsonElement, McpCallResult>
with naming convention mcp__{server}__{tool}; always returns Ask from checkPermissions
- McpConnectionManager: Disposable lifecycle manager; addServer() spawns process + registers
proxy tools; removeServer() disconnects and deregisters; dispose() tears down all
- Tests: McpClientTest (5) via PipedInputStream/PipedOutputStream fake server;
McpProxyToolTest (12) via MockK; 17 new tests
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…patch layer Replace the legacy ToolCallDispatcher / ToolSpecs / executors dispatch stack with runToolUseBatch from the tools/ layer. ChatService now builds tool definitions directly from ToolRegistry.assembleToolPool() and handles approval via the onPermissionAsked suspend callback wired to the existing pendingApprovals gate. Deleted: ToolCallDispatcher, ToolRegistry (execution), ToolSpecs, ToolExecutor (execution interface), ToolResult, ToolSpec, ToolContext, PermissionMode, ToolHook, FileChangeReview, FileWriteLock, PostEditPipeline, InlineChangeHighlighter, executors/, hooks/, review/ directories. Kept: CommandExecutionService, ExecutionResult, ShellPlatform (still used by BashTool and ChatService system prompt). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Cancellation: track runningToolMsgId during tool batch execution so cancelStream() works while tools are running; check flag before starting the follow-up model round - Whitelist: populate ToolPermissionContext.alwaysAllow from SettingsState.commandWhitelist so previously approved commands skip the approval dialog - Whitelist persistence: restore addToWhitelist behavior in onApprovalResponse; command stored in pendingApprovalCommands inside the onPermissionAsked callback - Timeout: add commandTimeoutSeconds to ToolExecutionContext; BashTool uses ctx.commandTimeoutSeconds as default when the model omits timeoutSeconds (nullable Int field), so SettingsState.commandTimeoutSeconds is honored Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
User description
Summary
ToolRegistry(static pool management),FileReadTool(offset/limit pagination, 2MB cap, workspace path check),runToolUseBatch(safe-parallel / unsafe-serial partition)limitedParallelism(n)withSemaphore(n)— the former limits threads, not coroutines; a suspended tool releases its thread and bypasses the boundFileEditTool(string replacement + unified diff preview,Askin DEFAULT mode /Allowin ACCEPT_EDITS/BYPASS),onPermissionAskedsuspend callback inToolExecutionContext,runToolUsenow suspends onAskand emitsFailed(PERMISSION)on denialTest plan
ToolExecutorBatchTest(8 tests): empty batch, LOOKUP failure, safe parallel, unsafe serial, mixed, maxConcurrency bound, parse isolation, per-id orderingToolRegistryTest(8 tests): base tools include Bash/FileEdit/FileRead, assembleToolPool sort+dedupe, deny rules exact+parametrized, findByName by name+aliasFileEditToolTest(22 tests): validate, workspace path guard, oldString-not-found deny, Ask/Allow per mode, preview diff, call replaceFirst/replaceAll/write, metadata flags, permission denied via callback, PermissionAsked event ordering, buildUnifiedDiff unit testsAll 38 tests pass.
🤖 Generated with Claude Code
PR Type
Enhancement, Bug fix
Description
Add ToolRegistry with getAllBaseTools, filterToolsByDenyRules, assembleToolPool, and findByName
Add FileReadTool with offset/limit pagination, 2MB byte cap, and workspace path validation
Add FileEditTool with string replacement and unified diff preview for permission Ask mode
Fix concurrency bug: replace limitedParallelism with Semaphore for correct coroutine-level throttling
Add real permission callback (onPermissionAsked) to ToolExecutionContext that suspends on Ask
Add runToolUseBatch for safe-parallel / unsafe-serial multi-tool execution
Diagram Walkthrough
flowchart LR A[ToolUseBatch] --> B{Classify by isConcurrencySafe} B -->|Safe| C[Semaphore Parallel] B -->|Unsafe| D[Serial Execution] C --> E[Tool Updates Flow] D --> E F[Permission Check] --> G{Result} G -->|Ask| H[onPermissionAsked Callback] G -->|Allow| I[Execute Tool] H -->|Allowed| I H -->|Denied| J[Failed(PERMISSION)]File Walkthrough
5 files
Add onPermissionAsked suspend callbackAdd runToolUseBatch and fix concurrencyCreate ToolRegistry for tool pool managementCreate FileEditTool with diff previewCreate FileReadTool with pagination4 files
Add batch executor testsAdd ToolRegistry testsAdd FileEditTool testsAdd FileReadTool tests