Files: src/tool_system/tools/web_fetch.py, src/tool_system/tools/web_search.py
Background: PR #135 (esc-cancel-subagents) and PRs #144-#148 wired abort_controller through Bash, Read, Grep/Glob, Anthropic streaming, and OpenAI-compatible streaming. PR #135 explicitly defers:
WebFetch / WebSearch: bounded by urllib.request.urlopen(timeout=15). Cleanest abort requires a watchdog thread that closes the socket — separate PR.
Verification: grep -rn abort_controller src/tool_system/tools/web_fetch.py src/tool_system/tools/web_search.py returns nothing on current main (post-#265 Tavily/web_fetch rewrite).
Impact: ESC during a slow WebFetch → spinner keeps showing and the agent doesn't accept input until the 15s timeout fires. With Read/Bash/Grep now cancelling within ~50-100ms, this is the longest interactive stall in the tool set.
Fix sketch: Run the request in a worker thread, register lambda: response.close() on the abort controller (same pattern as PR #148's OpenAI-compat work), poll the abort flag at the read() chunk boundary.
Files:
src/tool_system/tools/web_fetch.py,src/tool_system/tools/web_search.pyBackground: PR #135 (esc-cancel-subagents) and PRs #144-#148 wired
abort_controllerthrough Bash, Read, Grep/Glob, Anthropic streaming, and OpenAI-compatible streaming. PR #135 explicitly defers:Verification:
grep -rn abort_controller src/tool_system/tools/web_fetch.py src/tool_system/tools/web_search.pyreturns nothing on currentmain(post-#265 Tavily/web_fetch rewrite).Impact: ESC during a slow WebFetch → spinner keeps showing and the agent doesn't accept input until the 15s timeout fires. With Read/Bash/Grep now cancelling within ~50-100ms, this is the longest interactive stall in the tool set.
Fix sketch: Run the request in a worker thread, register
lambda: response.close()on the abort controller (same pattern as PR #148's OpenAI-compat work), poll the abort flag at theread()chunk boundary.