Implement local sub-agent execution for OSS/Bedrock mode

- Fix start_agent tool mapping: route to Tool::StartAgent instead of
  dead-end Tool::Subagent so tool calls become executable actions
- Block start_agent until child finishes: parent waits for child
  conversation to complete and receives full output as tool result
- Fix tool result delivery: add StartAgent/StartAgentV2 cases to
  extract_tool_result_content so the model actually sees agent output
- Support parallel agent spawning: change StartAgent action phase from
  Serial to Parallel, and track multiple pending agents via Vec
- Mark child conversations as Success on EndTurn: emit
  ConversationStatus::Success when a child stream ends with no actions
- Skip orchestration SSE in local mode: prevent app freeze from trying
  to connect to non-existent server
- Remove send_message_to_agent and suggest_next_prompt from tool list:
  these require server infrastructure that doesn't exist in OSS mode
- Add "Waiting for sub-agents..." status message while agents process
- Fix child agent pane close: actually dismiss instead of re-hiding,
  track dismissed IDs to prevent re-creation on restart
- Fix cache_miss_tokens calculation and show per-block cache stats
- Add [tool-debug] logging throughout tool invocation pipeline

Bump version to 1.6.0.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Ryan Ward
2026-06-03 13:29:09 -05:00
co-authored by Claude Opus 4.6
parent 17ecf67970
commit a309006458
18 changed files with 458 additions and 114 deletions
+24 -3
View File
@@ -888,6 +888,10 @@ pub struct PaneGroup {
/// be revealed from the parent's status card.
child_agent_panes: HashMap<AIConversationId, PaneId>,
/// Child agent conversations that the user has explicitly closed.
/// Prevents them from being re-created on app restart.
dismissed_child_agents: HashSet<AIConversationId>,
/// Tab-level custom title set via the rename-tab flow.
custom_title: Option<String>,
}
@@ -3001,6 +3005,7 @@ impl PaneGroup {
is_right_panel_maximized: false,
pending_ambient_agent_conversation_restorations: HashMap::new(),
child_agent_panes: HashMap::new(),
dismissed_child_agents: HashSet::new(),
custom_title: None,
};
@@ -3052,14 +3057,18 @@ impl PaneGroup {
// Check in-memory children (live conversations).
for child in history_model.child_conversations_of(parent_id) {
let child_id = child.id();
if !self.child_agent_panes.contains_key(&child_id) {
if !self.child_agent_panes.contains_key(&child_id)
&& !self.dismissed_child_agents.contains(&child_id)
{
children_to_create.insert(child_id);
}
}
// Check the startup index for children not yet in memory.
for &child_id in history_model.child_conversation_ids_of(&parent_id) {
if !self.child_agent_panes.contains_key(&child_id) {
if !self.child_agent_panes.contains_key(&child_id)
&& !self.dismissed_child_agents.contains(&child_id)
{
children_to_create.insert(child_id);
}
}
@@ -4448,16 +4457,28 @@ impl PaneGroup {
return;
}
// If this pane is a child agent, re-hide it instead of closing it.
// If this pane is a child agent, remove it from tracking and
// discard it fully so it doesn't reappear on re-launch.
if self.is_child_agent_pane(pane_id) {
let dismissed_id = self
.child_agent_panes
.iter()
.find(|(_, p)| **p == pane_id)
.map(|(id, _)| *id);
if let Some(id) = dismissed_id {
self.dismissed_child_agents.insert(id);
}
self.child_agent_panes.retain(|_, &mut p| p != pane_id);
if !self.panes.is_pane_hidden(&pane_id) {
self.panes.hide_pane_for_child_agent(pane_id);
}
self.panes.remove_hidden_pane(pane_id);
self.focus_next_terminal_pane_and_activate_session(
pane_id,
PaneRemovalReason::Close,
ctx,
);
self.pane_contents.remove(&pane_id);
self.handle_pane_count_change(ctx);
ctx.emit(Event::TerminalViewStateChanged);
ctx.emit(Event::AppStateChanged);