Harden local tool execution

This commit is contained in:
Georg Bauer
2026-07-26 14:36:38 +02:00
parent 171b041ba6
commit c9f0c3661c
7 changed files with 1018 additions and 141 deletions

View File

@@ -210,6 +210,8 @@ impl App {
return;
}
#[cfg(target_os = "macos")]
self.tool_cards.clear();
#[cfg(target_os = "macos")]
if !std::mem::take(&mut self.skip_compaction_once)
&& crate::compaction::should_compact(self.context_used, self.context_limit)
{
@@ -388,11 +390,47 @@ impl App {
return self.poll_tool_result_check();
}
#[cfg(target_os = "macos")]
if self.active_tools.is_some() {
while let Some(event) = self
.active_tools
.as_ref()
.and_then(crate::agent::try_tool_event)
{
match event {
crate::agent::ToolEvent::State {
index,
state,
result,
} => {
if let Some(card) = self.tool_cards.get_mut(index) {
card.state = state;
if result.is_some() {
card.result = result;
}
}
self.activity = Some(format!("Tool {} · {}", index + 1, state.label()));
}
crate::agent::ToolEvent::Approval {
index,
prompt,
decision,
} => {
if let Some(card) = self.tool_cards.get_mut(index) {
card.state = crate::agent::ToolLifecycle::AwaitingApproval;
}
self.pending_tool_approval = Some((prompt, decision));
self.activity = Some(format!("Tool {} · Awaiting approval", index + 1));
}
}
}
}
#[cfg(target_os = "macos")]
if let Some(active) = &self.active_tools {
match crate::agent::try_tool_result(active) {
Ok(Some(result)) => {
let cancelled = active.cancel.load(Ordering::Relaxed);
self.active_tools = None;
self.pending_tool_approval = None;
if cancelled {
self.generating = false;
self.activity = Some("Stopped".into());
@@ -453,6 +491,13 @@ impl App {
&& !message.user
{
message.append(reasoning, &content);
if !reasoning
&& self.tool_cards.is_empty()
&& crate::agent::has_tool_markup(&message.content)
{
self.tool_cards.push(crate::agent::ToolCard::streaming());
self.activity = Some("Parsing tool call…".into());
}
transcript_changed = true;
}
}
@@ -501,6 +546,7 @@ impl App {
Ok(_) => {
self.generating = false;
self.activity = None;
self.tool_cards.clear();
start_queued = !self.queued_inputs.is_empty()
|| self.manual_compaction_queued;
}
@@ -628,8 +674,13 @@ impl App {
self.agent_tools = Some((session_id, Arc::new(Mutex::new(tools))));
}
let tools = Arc::clone(&self.agent_tools.as_ref().unwrap().1);
self.tool_cards = calls
.iter()
.cloned()
.map(crate::agent::ToolCard::parsing)
.collect();
self.active_tools = Some(crate::agent::execute_async(tools, calls));
self.activity = Some("Running tools…".into());
self.activity = Some("Parsing tool calls…".into());
Ok(())
}
@@ -685,6 +736,7 @@ impl App {
.collect();
assistant.reasoning_open = assistant_reasoning;
self.conversation.push(assistant);
self.tool_cards.clear();
let idle_timeout = Duration::from_secs(self.config.idle_timeout_minutes.max(1) as u64 * 60);
self.active_generation = Some(
self.generation_service
@@ -704,6 +756,20 @@ impl App {
Ok(())
}
#[cfg(target_os = "macos")]
pub(super) fn stop_agent_jobs(&mut self) {
let Some((_, tools)) = &self.agent_tools else {
return;
};
let Ok(mut tools) = tools.try_lock() else {
return;
};
let failures = tools.stop_all_jobs();
if !failures.is_empty() {
self.error = Some(failures.join("\n"));
}
}
#[cfg(target_os = "macos")]
fn start_tool_result_check(
&mut self,