fix(delegate): route subagent progress lines through _safe_print for ACP stdio

delegate_task's per-task completion display emitted lines like
"✓ [1/3] Research done (17.92s)" via a bare print(). Under ACP (and any
headless JSON-RPC stdio host where AIAgent routes human output to stderr
via a custom _print_fn), these landed on stdout and corrupted the
protocol frame stream, surfacing as "Failed to parse JSON message: ✓
[3/3] …" in the ACP adapter.

Add _emit_parent_console() which prefers parent_agent._safe_print (the
same hook AIAgent uses for every other user-facing print) and falls back
to print() only when no router is wired up or it raises. CLI behavior is
unchanged.

The PR's other fix (preset toolset expansion) is already covered on main
by _expand_parent_toolsets(), so only the stdio-safe printing change is
salvaged here.
This commit is contained in:
Markus Phan 2026-06-30 03:02:03 -07:00 committed by Teknium
parent eeb4735078
commit cd9f5cc671
3 changed files with 63 additions and 3 deletions

View file

@ -800,6 +800,24 @@ def _strip_blocked_tools(toolsets: List[str]) -> List[str]:
return [t for t in toolsets if t not in blocked_toolset_names]
def _emit_parent_console(parent_agent, line: str) -> None:
"""Emit a human-readable progress line to the parent's console.
Routes through ``parent_agent._safe_print`` when available so headless
stdio hosts (ACP, gateway API) can redirect non-protocol output to
stderr via their configured ``_print_fn``. A bare ``print()`` would
otherwise land on stdout and corrupt JSON-RPC framing.
"""
printer = getattr(parent_agent, "_safe_print", None)
if callable(printer):
try:
printer(line)
return
except Exception:
pass
print(line)
def _build_child_progress_callback(
task_index: int,
goal: str,
@ -2610,9 +2628,9 @@ def delegate_task(
try:
spinner_ref.print_above(completion_line)
except Exception:
print(f" {completion_line}")
_emit_parent_console(parent_agent, f" {completion_line}")
else:
print(f" {completion_line}")
_emit_parent_console(parent_agent, f" {completion_line}")
# Update spinner text to show remaining count
if spinner_ref and remaining > 0: