fix(orchestrator): corrige la delegation inter-agent GLM/OpenCode sans reponse Final
La detection d'absence de reponse structuree finale (structured_no_reply_error) ne couvrait pas AgentSessionError::Decode ni le message "aucun final textuel exploitable" emis par l'adaptateur OpenCode, ce qui laissait idea_ask_agent planter silencieusement sur les profils GLM/OpenCode au lieu de retomber sur le chemin de secours prevu. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@ -79,12 +79,17 @@ fn submit_config_for_profile(profile: &AgentProfile) -> SubmitConfig {
|
||||
}
|
||||
|
||||
fn structured_no_reply_error(err: &domain::ports::AgentSessionError) -> bool {
|
||||
matches!(
|
||||
err,
|
||||
match err {
|
||||
domain::ports::AgentSessionError::Io(message)
|
||||
if message.contains("sans événement Final")
|
||||
| domain::ports::AgentSessionError::Decode(message) => {
|
||||
message.contains("sans événement Final")
|
||||
|| message.contains("without a structured final")
|
||||
)
|
||||
|| message.contains("aucun final textuel exploitable")
|
||||
}
|
||||
domain::ports::AgentSessionError::Start(_) | domain::ports::AgentSessionError::Timeout => {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn bound_task_text(value: &str, max_bytes: usize) -> String {
|
||||
|
||||
@ -1170,6 +1170,7 @@ impl TestMailbox {
|
||||
enum TestCompletion {
|
||||
Replied(String),
|
||||
NoReply,
|
||||
DecodeNoFinal,
|
||||
Cancelled,
|
||||
}
|
||||
|
||||
@ -1467,6 +1468,9 @@ impl AgentSession for CompletionSession {
|
||||
TestCompletion::NoReply => Err(AgentSessionError::Io(
|
||||
"target returned without a structured final".to_owned(),
|
||||
)),
|
||||
TestCompletion::DecodeNoFinal => Err(AgentSessionError::Decode(
|
||||
"OpenCode n'a produit aucun final textuel exploitable".to_owned(),
|
||||
)),
|
||||
TestCompletion::Cancelled => Err(AgentSessionError::Io(
|
||||
"structured test turn cancelled".to_owned(),
|
||||
)),
|
||||
@ -2134,6 +2138,28 @@ async fn ask_target_returns_to_prompt_without_reply_is_a_typed_error() {
|
||||
assert_eq!(fx.mailbox.pending(&aid(1)), 0, "head retired by completion");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn structured_opencode_decode_without_final_is_a_typed_no_reply_error() {
|
||||
let agent = scratch_agent(aid(1), "architect", "agents/architect.md");
|
||||
let fx = ask_fixture(FakeContexts::with_agent(&agent, "# persona"));
|
||||
seed_live_pty(&fx.sessions, aid(1), sid(800));
|
||||
|
||||
let svc = Arc::clone(&fx.service);
|
||||
let ask = tokio::spawn(async move { svc.dispatch(&project(), cmd(ASK_JSON)).await });
|
||||
await_until(|| fx.mailbox.pending(&aid(1)) == 1).await;
|
||||
|
||||
fx.mailbox
|
||||
.completions()
|
||||
.push(aid(1), TestCompletion::DecodeNoFinal);
|
||||
|
||||
let err = timeout(TEST_GUARD, ask)
|
||||
.await
|
||||
.expect("ask completes promptly, not after the long timeout")
|
||||
.expect("join ok")
|
||||
.expect_err("OpenCode no-final decode is a typed no-reply error");
|
||||
assert_eq!(err.code(), "TARGET_RETURNED_NO_REPLY", "got {err:?}");
|
||||
}
|
||||
|
||||
/// An `idea_reply` that lands **first** still wins over a (later) no-reply completion:
|
||||
/// the completion then finds the head gone and is a no-op (idempotent race).
|
||||
#[tokio::test]
|
||||
|
||||
Reference in New Issue
Block a user