fix: ACP session/prompt now drives the full tool-execution loop
CI / All (ubuntu-latest) (push) Failing after 25s
CI / All (macos-latest) (push) Has been cancelled
CI / All (windows-latest) (push) Has been cancelled

run_prompt_turn previously performed a single completion and returned,
leaving tool calls unexecuted. It now mirrors start_directive's loop:
call -> execute tools -> merge results -> continue until no tool results,
then check the pending-agents guardrail before returning.

The pending-agents guardrail injects a reminder prompt when sub-agents
are pending, matching the same loop termination semantics as --headless.
The session is NOT exited between turns (unlike start_directive) so
multi-turn ACP conversations retain history across session/prompt calls.

Manual gate (tool-probe agent, fs_write + fs_read tools):
  id 3 result: {"output":"DONE:probe.txt","stopReason":"end_turn"}
  probe.txt exists: YES, content: hello
This commit is contained in:
2026-07-28 15:42:51 -06:00
parent 06b2c384e3
commit 7f7ea758a7
+23 -5
View File
@@ -1,6 +1,7 @@
use super::types::{METHOD_NOT_FOUND, PARSE_ERROR, Request, Response};
use crate::client::call_chat_completions_streaming;
use crate::config::{Input, RenderMode, RequestContext};
use crate::function::supervisor::{GuardrailAction, check_pending_agents_guardrail};
use crate::utils;
use crate::utils::AbortSignal;
use anyhow::Result;
@@ -198,16 +199,33 @@ async fn run_prompt_turn(
abort: AbortSignal,
) -> Result<String> {
ctx.render_mode = RenderMode::Silent;
let input = Input::from_str(ctx, text, None)?;
let mut input = Input::from_str(ctx, text, None)?;
loop {
ctx.before_chat_completion(&input)?;
let client = input.create_client()?;
let (output, tool_results) =
call_chat_completions_streaming(&input, client.as_ref(), ctx, abort).await?;
call_chat_completions_streaming(&input, client.as_ref(), ctx, abort.clone()).await?;
let app = Arc::clone(&ctx.app.config);
ctx.after_chat_completion(app.as_ref(), &input, &output, &tool_results)?;
Ok(output)
if !tool_results.is_empty() {
input = input.merge_tool_results(output, tool_results);
continue;
}
match check_pending_agents_guardrail(ctx) {
GuardrailAction::Inject(prompt) => {
input = Input::from_str(ctx, &prompt, None)?;
}
GuardrailAction::ForceTerminate(ids) => {
warn!(
"Pending-agent guardrail force-cancelled {} agent(s): {:?}",
ids.len(),
ids
);
return Ok(output);
}
GuardrailAction::NoAction => return Ok(output),
}
}
}
fn handle_session_cancel(state: &mut AcpServerState) {