formulahendry / formulahendry/vscode-acp
Chat webview spinner never stops after a heavy turn completes; Stop button dead; only Reload Window recovers (v0.2.0)
- Dominant language
- TypeScript
- Stars
- 378
- Forks
- 76
- PR merge metrics
- No merged PRs in 30d
Description
## Summary
The chat webview gets permanently stuck in the "processing" (spinner) state after an agent turn that involves a large number of tool calls. The agent has **fully completed** the turn (verified in the agent's server logs), but the UI keeps spinning, the Stop button does nothing, and the only recovery is `Developer: Reload Window` (or killing the agent process). Recurred 3 times in one session; short turns in the same session rendered fine.
## Environment
| | |
|---|---|
| OS | Windows 10 |
| VS Code | 1.132.0 |
| Extension | **ACP Client v0.2.0** (`formulahendry.acp-client`, latest published) |
| Agent | Hermes Agent v0.15.2 running `hermes acp` (ACP JSON-RPC server) |
| Model | deepseek-v4-pro via DeepSeek API (streaming) |
## Steps to reproduce
1. Connect an ACP agent (e.g. `hermes acp`).
2. Send a prompt that drives a **heavy** turn: ~59 tool calls and a long streaming response.
3. Wait for the agent to finish.
4. Observe: the spinner never stops; the response may render but the UI stays in the processing state; clicking Stop does nothing.
5. Only `Developer: Reload Window` recovers.
Short turns (1–3 tool calls) in the same session complete normally — so the failure correlates with turn size / message volume.
## Evidence: the agent (server) side is clean
From the agent's log (`agent.log`), the heavy turn ended **normally** — twice (main turn, then an automatic follow-up "review" turn):
```
2026-08-07 19:50:54,027 ... agent.conversation_loop: Turn ended: reason=text_response(finish_reason=stop)
model=deepseek-v4-pro api_calls=58/90 tool_turns=59 last_msg_role=assistant response_len=233
2026-08-07 19:52:12,875 ... agent.conversation_loop: Turn ended: reason=text_response(finish_reason=stop)
model=deepseek-v4-pro api_calls=5/16 tool_turns=63 last_msg_role=assistant response_len=646
```
- Both turns ended with `finish_reason=stop`; the ACP `PromptResponse` was produced.
- After 19:52:12 the server process stayed **idle with zero errors** — the answer was ready and waiting.
- The UI stayed stuck anyway. The user's manual cancel did not arrive until **20:02:14** (`Cancelled session ...`), i.e. the webview was stuck for **10+ minutes after the server had already finished**.
- Control: two earlier turns in the same session (19:40:58 and 19:42:34, few tool calls) completed and rendered fine.
So the final response reached the extension host, but the webview never finalized → the spinner never stops.
## Root-cause hypothesis
The webview's processing state is driven by `promptStart`/`promptEnd`, and the extension host sends `promptEnd` only after `sessionManager.sendPrompt(...)` resolves. Under a heavy turn the extension receives a large burst of streaming `session_update` messages (tool start/complete + text deltas — 59 tool calls here, dozens of chunks). Under load, updates from the agent can be delayed or dropped (for reference, the agent's ACP adapter blocks each executor thread up to 5 s per chunk, `future.result(timeout=5)` on a 4-thread pool, silently swallowing timeouts). If the webview's internal state machine depends on receiving some intermediate notification and that message is lost, the final `promptEnd` alone cannot return the UI to the idle state — permanent spinner, dead Stop button.
## Suggested fixes (client side)
1. **Hard safety timeout**: if the webview is in "processing" state and receives no `session_update` for N seconds while the prompt has already resolved, force `setProcessing(false)`.
2. **Make Stop a hard UI reset**: `connection.cancel()` is currently a silent no-op when no turn is active. When the UI is stale, Stop should also reset the webview state — otherwise the only escape is Reload Window.
3. **Make `promptEnd` render-finalization independent of intermediate messages**: if the JSON-RPC prompt response arrived, always finalize the UI state even when some streaming chunks were missed.
Happy to provide more logs or test against a debug build. Thanks for the great extension!
Contributor guide
No contributing guide indexed for this repository
Research direction
Start at the webview processing-state handling for promptStart/promptEnd and the extension-host path around sessionManager.sendPrompt; trace how session_update bursts and prompt completion affect setProcessing(false). Check the Stop handler's connection.cancel() path, reproduce with a heavy multi-tool turn, and consider the issue done when the UI returns to idle and Stop/recovery work without Reload Window.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript, vscode
- Domain
- devtools, frontend
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 55/100