From cfcf7e6a36654b842ad4fc2aaa4bbcc4a7ed1fdf Mon Sep 17 00:00:00 2001 From: zhangshuw <739066728@qq.com> Date: Mon, 18 May 2026 15:26:13 +0800 Subject: [PATCH] fix(conductor): surface LLM error to user when conductor falls silent (#342) (#414) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When a single LLM provider goes down mid-task (auth, network, quota, etc.), the conductor falls completely silent: MixinSession yields errors as text chunks (not Python exceptions), so the existing try/except guard in conductor_loop never triggers, and the conductor's only way to talk to the user (LLM-decided POST /chat tool call) is dead. Add a tiny watchdog: monitor_conductor_queue now returns the conductor agent's final done text, and conductor_loop scans the tail-1000-char window for "!!!Error:" — the prefix llmcore uses for unrecoverable errors. Tail window (not whole text) avoids false positives from earlier turns; using a character window (not last-line) matches ga.do_no_tool's content[-100:] style and tolerates the decoration (fence + "[Info] Final response to user.") that do_no_tool appends after the error chunk when its own short window check misses. When detected, push the matched error line as a system chat message so the user knows what happened. A dedup check prevents repeated warnings if multiple events fail in succession. +16 -4 lines. Only frontends/conductor.py touched. No agent_loop / llmcore changes; other frontends unaffected. --- frontends/conductor.py | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/frontends/conductor.py b/frontends/conductor.py index eb37ec3f..8a15f4e2 100644 --- a/frontends/conductor.py +++ b/frontends/conductor.py @@ -288,13 +288,14 @@ def _auto_cleanup_loop(): if to_abort: push_cards() -def monitor_conductor_queue(dq: "queue.Queue"): - """Conductor output is discarded. Visible reply must go through POST /chat only.""" +def monitor_conductor_queue(dq: "queue.Queue") -> str: + """Block until done. Conductor output is not surfaced to users; the returned + text is for caller-side error detection only (#342).""" while True: item = dq.get() if "done" in item: print(f"Conductor task done") - break + return item.get("done", "") or "" def conductor_loop(): global conductor_agent, conductor_started @@ -321,7 +322,18 @@ def conductor_loop(): prompt = conductor_prompt_from_events(events) dq = conductor_agent.put_task(prompt, source="conductor") # Block here until conductor finishes — serializes execution - monitor_conductor_queue(dq) + done_text = monitor_conductor_queue(dq) + # Fallback: if conductor's last turn ended with an LLM error (yielded as + # text by MixinSession instead of raised), surface it directly (#342). + # Use tail-window substring check (same style as ga.do_no_tool's + # content[-100:]); window covers the error line plus any decoration + # (fence + "[Info] Final response to user.") appended by do_no_tool. + tail = (done_text or '')[-1000:] + if '!!!Error:' in tail: + last = chat_messages[-1] if chat_messages else None + if not (last and last.get('role') == 'system' and last.get('msg', '').startswith('⚠ LLM')): + err = next((l for l in reversed(tail.splitlines()) if l.startswith('!!!Error:')), '') + add_chat(f"⚠ LLM 暂不可用:{err[:200]}", role="system") except Exception as e: add_chat(f"Conductor error: {e}", role="system")