Fix cancellation regression: make edit_file_tool handle cancellation (#46527)

PR #46306 changed cancellation to wait for tools to complete before
returning. This was correct behavior - it allows tools like terminal to
capture their output on cancellation. The real issue was that many tools
didn't check for cancellation, so they would continue running until they
finished.

## The Problem

When the user pressed Escape to cancel during a tool operation, tools
would continue running because they never checked for the cancellation
signal. The thread correctly waited for tools to complete (so terminal
could capture output), but tools like edit_file, grep, fetch, etc. would
just keep going.

## The Fix

Add cancellation handling to all tools using the same pattern as
`terminal_tool`: use `select!` to race between the tool's main work and
`event_stream.cancelled_by_user()`. When cancelled, tools break out of
their loops or return early.

## All Tools Now Cancellation-Aware

| Tool | Change |
|------|--------|
| `edit_file_tool` | Checks cancellation in edit event processing loop |
| `terminal_tool` | Already handled cancellation |
| `grep_tool` | Checks cancellation in search result iteration loop |
| `fetch_tool` | Checks cancellation during HTTP fetch |
| `web_search_tool` | Checks cancellation during web search |
| `find_path_tool` | Checks cancellation during path search |
| `read_file_tool` | Checks cancellation during buffer open |
| `copy_path_tool` | Checks cancellation during file copy |
| `move_path_tool` | Checks cancellation during file move/rename |
| `delete_path_tool` | Checks cancellation during delete operation |
| `create_directory_tool` | Checks cancellation during directory
creation |
| `save_file_tool` | Checks cancellation during buffer open and save |
| `restore_file_from_disk_tool` | Checks cancellation during buffer open
and reload |
| `open_tool` | Checks cancellation during authorization |
| `diagnostics_tool` | Checks cancellation during buffer open |
| `ContextServerTool` (MCP) | Checks cancellation during external server
calls |

**Synchronous tools (no async work, return immediately):**
- `list_directory_tool` - Reads worktree snapshot synchronously
- `now_tool` - Returns current time immediately
- `thinking_tool` - Returns immediately

## MCP Tools Automatically Handled

MCP tools (user-defined tools via context servers) are now automatically
cancellation-aware without any user action. The `ContextServerTool`
wrapper races the external server request against
`event_stream.cancelled_by_user()`.

## Testing

- Added `CancellationAwareTool` test helper that mirrors the
cancellation pattern
- Updated `test_cancellation_aware_tool_responds_to_cancellation` to
properly await the cancel task and verify the tool detected cancellation

Release Notes:

- Fixed a regression where pressing Escape wouldn't immediately cancel
in-progress tool operations
ecc3928040Richard Feldman committed on 1/11/2026, 12:30:25 AM· committed by GitHubparentc6a38f2
17 files changedLine totals unavailable