mirror of
https://github.com/agentclientprotocol/claude-agent-acp.git
synced 2026-10-27 12:40:32 +00:00
fix: Report tool_progress heartbeats against the tool call they describe (#916)
Heartbeat beats reach the bridge under a derived `<tool_use_id>-heartbeat-<n>` id that no `tool_call` was ever emitted for, and the handler forwarded it verbatim. Clients that expect a `tool_call_update` to reference a tool call they have been told about saw a reference they could not resolve, and since the counter makes every beat a fresh id, nothing deduplicated them: a ten-minute Bash call produced roughly twenty phantom entries. Zed renders each as a failed "Tool call not found" card; compozy/compozy#257 and agent-of-empires/agent-of-empires#3084 report the same shape. Strip the derived suffix so the beat is reported against the call it describes, and drop it when that call is not in `emittedToolCalls`. Ids enter that set at `tool_use` and leave it at `tool_result`, so the guard covers both an id that was never announced and a straggling beat that races past completion, which would otherwise flip a call the client has seen finish back to `in_progress`. The SDK sets `heartbeat: true` on these messages, but that flag alone cannot recover the original id, and `parent_tool_use_id` is the subagent parent rather than the call being reported on, so the suffix still has to be stripped. `tool_progress` had no test coverage. Add cases for the retarget across a climbing counter, the not-in-flight drop, meta pass-through, and an unsuffixed id being left alone. Fixes #913 --------- Signed-off-by: Daan De Meyer <daan@amutable.com> Co-authored-by: Ben Brandt <benjamin.j.brandt@gmail.com> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -3762,11 +3762,30 @@ export class ClaudeAcpAgent {
|
||||
break;
|
||||
}
|
||||
case "tool_progress": {
|
||||
// Not every beat reports under the id of a tool call the client has
|
||||
// seen: heartbeats derive `<tool_use_id>-heartbeat-<n>`, and the
|
||||
// `agent_api_retry` beats behind `subagentRetry` report under
|
||||
// `agent_<assistant_message_id>`. Forwarding those verbatim leaves the
|
||||
// client resolving an id it has never been told about (the same trap
|
||||
// `ensureToolCallEmitted` documents for #851). The SDK stamps
|
||||
// `parent_tool_use_id` with the executing tool's real id whenever the
|
||||
// beat doesn't carry one of its own, so fall back to it rather than
|
||||
// pattern-matching each synthetic id shape. Beats that do report a real
|
||||
// id (a subagent's `bash_progress`, whose parent is the spawning Agent
|
||||
// call) keep resolving to that id.
|
||||
const toolCallId = session.emittedToolCalls.has(message.tool_use_id)
|
||||
? message.tool_use_id
|
||||
: message.parent_tool_use_id;
|
||||
// Ids leave `emittedToolCalls` at `tool_result`, so this also stops a
|
||||
// beat that races past completion from reopening a finished call.
|
||||
if (toolCallId === null || !session.emittedToolCalls.has(toolCallId)) {
|
||||
break;
|
||||
}
|
||||
await sendUpdate({
|
||||
sessionId: message.session_id,
|
||||
update: {
|
||||
sessionUpdate: "tool_call_update",
|
||||
toolCallId: message.tool_use_id,
|
||||
toolCallId,
|
||||
status: "in_progress",
|
||||
_meta: {
|
||||
claudeCode: {
|
||||
|
||||
Reference in New Issue
Block a user