feat(acp): forward meta/locations fields and use parentToolUseId for precise Agent child grouping
Forward the previously-dropped `locations` and `meta` fields from ACP SDK ToolCall/ToolCallUpdate events through to the frontend. The meta field carries `claudeCode.parentToolUseId` which enables precise parent-child matching for concurrent Agent tool calls during streaming. - Forward locations/meta in Rust AcpEvent types and connection handlers - Use parentToolUseId for exact agent→child mapping, with position-based fallback for agents that don't provide it (Codex, OpenCode) - Replace `any` types with proper ToolCallMeta / unknown types - Add runtime guards for meta field parsing (defensive against unexpected shapes from different agents) - Cache inferLiveToolName results per tool_call_id to avoid redundant computation across Phase 1 and Phase 2 - Lazy-construct agentStats only when children exist
This commit is contained in:
@@ -1554,6 +1554,8 @@ fn emit_terminal_output_update(
|
||||
raw_input: None,
|
||||
raw_output: Some(output),
|
||||
raw_output_append: Some(append),
|
||||
locations: None,
|
||||
meta: None,
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -2501,6 +2503,12 @@ fn emit_conversation_update(
|
||||
.map(|text| resolve_live_tool_input(&text, cwd));
|
||||
let raw_output = json_value_to_text(&tc.raw_output)
|
||||
.map(|text| structurize_live_output(&text));
|
||||
let locations = if tc.locations.is_empty() {
|
||||
None
|
||||
} else {
|
||||
serde_json::to_value(&tc.locations).ok()
|
||||
};
|
||||
let meta = tc.meta.map(serde_json::Value::Object);
|
||||
crate::web::event_bridge::emit_event(
|
||||
emitter,
|
||||
"acp://event",
|
||||
@@ -2513,6 +2521,8 @@ fn emit_conversation_update(
|
||||
content,
|
||||
raw_input,
|
||||
raw_output,
|
||||
locations,
|
||||
meta,
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -2526,6 +2536,13 @@ fn emit_conversation_update(
|
||||
.map(|text| resolve_live_tool_input(&text, cwd));
|
||||
let raw_output = json_value_to_text(&tcu.fields.raw_output)
|
||||
.map(|text| structurize_live_output(&text));
|
||||
let locations = tcu
|
||||
.fields
|
||||
.locations
|
||||
.as_ref()
|
||||
.filter(|l| !l.is_empty())
|
||||
.and_then(|l| serde_json::to_value(l).ok());
|
||||
let meta = tcu.meta.clone().map(serde_json::Value::Object);
|
||||
crate::web::event_bridge::emit_event(
|
||||
emitter,
|
||||
"acp://event",
|
||||
@@ -2538,6 +2555,8 @@ fn emit_conversation_update(
|
||||
raw_input,
|
||||
raw_output,
|
||||
raw_output_append: None,
|
||||
locations,
|
||||
meta,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -63,6 +63,10 @@ pub enum AcpEvent {
|
||||
content: Option<String>,
|
||||
raw_input: Option<String>,
|
||||
raw_output: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
locations: Option<serde_json::Value>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
meta: Option<serde_json::Value>,
|
||||
},
|
||||
/// Tool call status/content updated
|
||||
ToolCallUpdate {
|
||||
@@ -75,6 +79,10 @@ pub enum AcpEvent {
|
||||
raw_output: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
raw_output_append: Option<bool>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
locations: Option<serde_json::Value>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
meta: Option<serde_json::Value>,
|
||||
},
|
||||
/// Agent requests permission
|
||||
PermissionRequest {
|
||||
|
||||
Reference in New Issue
Block a user