feat: long-running session improvements

- Hang fix: 120s timeout on compression LLM call + raw_stream
  channel-close handling (None => break instead of spinning)
- Supervisor effective active count: stop counting finished
  JoinHandles as occupying capacity slots
- Universal tool result size cap: truncate_if_needed() on
  ToolResult, applied in eval_tool_calls() after escalation block;
  configurable via max_tool_result_chars in AppConfig/AgentConfig
- Windowed compression: compression_keep_last config param keeps
  the N most recent messages visible after compression
- Fix pre-existing flaky test: add #[serial] to
  handle_list_available_unrestricted_when_no_whitelist so it does
  not race with TestConfigDirGuard-based tests that temporarily
  populate the agents data dir
This commit is contained in:
2026-07-23 12:25:54 -06:00
parent d51bdd3086
commit 54c5079cb7
9 changed files with 118 additions and 19 deletions
+34 -3
View File
@@ -58,6 +58,13 @@ impl Supervisor {
self.handles.len()
}
pub fn effective_active_count(&self) -> usize {
self.handles
.values()
.filter(|h| !h.join_handle.is_finished())
.count()
}
pub fn max_concurrent(&self) -> usize {
self.max_concurrent
}
@@ -75,10 +82,10 @@ impl Supervisor {
}
pub fn register(&mut self, handle: AgentHandle) -> Result<()> {
if self.handles.len() >= self.max_concurrent {
if self.effective_active_count() >= self.max_concurrent {
bail!(
"Cannot spawn agent: at capacity ({}/{})",
self.handles.len(),
self.effective_active_count(),
self.max_concurrent
);
}
@@ -188,8 +195,32 @@ mod tests {
#[test]
fn supervisor_register_rejects_at_capacity() {
// Keep the runtime alive in this scope so the spawned task is never
// polled (current_thread only polls inside block_on), keeping
// join_handle.is_finished() == false and the slot occupied.
let rt = tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.unwrap();
let join_handle = rt.spawn(async {
Ok::<AgentResult, anyhow::Error>(AgentResult {
id: "done".into(),
agent_name: "test".into(),
output: "result".into(),
exit_status: AgentExitStatus::Completed,
})
});
let running_handle = AgentHandle {
id: "a1".to_string(),
agent_name: "explore".to_string(),
depth: 1,
inbox: Arc::new(Inbox::new()),
abort_signal: create_abort_signal(),
join_handle,
child_supervisor: None,
};
let mut sup = Supervisor::new(1, 3);
sup.register(make_handle("a1", "explore", 1)).unwrap();
sup.register(running_handle).unwrap();
let result = sup.register(make_handle("a2", "coder", 1));
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("at capacity"));