fix(display): fix subagent progress tree-view visual nits
Two fixes to the subagent progress display from PR #186: 1. Task index prefix: show 1-indexed prefix ([1], [2], ...) for ALL tasks in batch mode (task_count > 1). Single tasks get no prefix. Previously task 0 had no prefix while others did, making batch output confusing. 2. Completion indicator: use spinner.print_above() instead of raw print() for per-task completion lines (✓ [1/2] ...). Raw print collided with the active spinner, mushing the completion text onto the spinner line. Now prints cleanly above. Added task_count parameter to _build_child_progress_callback and _run_single_child. Updated tests accordingly.
This commit is contained in:
parent
4ec386cc72
commit
41d8a80226
2 changed files with 34 additions and 16 deletions
|
|
@ -170,8 +170,8 @@ class TestBuildChildProgressCallback:
|
||||||
|
|
||||||
parent_cb.assert_not_called()
|
parent_cb.assert_not_called()
|
||||||
|
|
||||||
def test_task_index_prefix_in_output(self):
|
def test_task_index_prefix_in_batch_mode(self):
|
||||||
"""Multi-task mode should show task index prefix."""
|
"""Batch mode (task_count > 1) should show 1-indexed prefix for all tasks."""
|
||||||
buf = io.StringIO()
|
buf = io.StringIO()
|
||||||
spinner = KawaiiSpinner("delegating")
|
spinner = KawaiiSpinner("delegating")
|
||||||
spinner._out = buf
|
spinner._out = buf
|
||||||
|
|
@ -181,15 +181,22 @@ class TestBuildChildProgressCallback:
|
||||||
parent._delegate_spinner = spinner
|
parent._delegate_spinner = spinner
|
||||||
parent.tool_progress_callback = None
|
parent.tool_progress_callback = None
|
||||||
|
|
||||||
# task_index > 0 should add prefix
|
# task_index=0 in a batch of 3 → prefix "[1]"
|
||||||
cb = _build_child_progress_callback(2, parent)
|
cb0 = _build_child_progress_callback(0, parent, task_count=3)
|
||||||
cb("web_search", "test")
|
cb0("web_search", "test")
|
||||||
|
|
||||||
output = buf.getvalue()
|
output = buf.getvalue()
|
||||||
assert "[2]" in output
|
assert "[1]" in output
|
||||||
|
|
||||||
def test_task_index_zero_no_prefix(self):
|
# task_index=2 in a batch of 3 → prefix "[3]"
|
||||||
"""Single task (index 0) should not show index prefix."""
|
buf.truncate(0)
|
||||||
|
buf.seek(0)
|
||||||
|
cb2 = _build_child_progress_callback(2, parent, task_count=3)
|
||||||
|
cb2("web_search", "test")
|
||||||
|
output = buf.getvalue()
|
||||||
|
assert "[3]" in output
|
||||||
|
|
||||||
|
def test_single_task_no_prefix(self):
|
||||||
|
"""Single task (task_count=1) should not show index prefix."""
|
||||||
buf = io.StringIO()
|
buf = io.StringIO()
|
||||||
spinner = KawaiiSpinner("delegating")
|
spinner = KawaiiSpinner("delegating")
|
||||||
spinner._out = buf
|
spinner._out = buf
|
||||||
|
|
@ -199,11 +206,11 @@ class TestBuildChildProgressCallback:
|
||||||
parent._delegate_spinner = spinner
|
parent._delegate_spinner = spinner
|
||||||
parent.tool_progress_callback = None
|
parent.tool_progress_callback = None
|
||||||
|
|
||||||
cb = _build_child_progress_callback(0, parent)
|
cb = _build_child_progress_callback(0, parent, task_count=1)
|
||||||
cb("web_search", "test")
|
cb("web_search", "test")
|
||||||
|
|
||||||
output = buf.getvalue()
|
output = buf.getvalue()
|
||||||
assert "[0]" not in output
|
assert "[" not in output
|
||||||
|
|
||||||
|
|
||||||
# =========================================================================
|
# =========================================================================
|
||||||
|
|
|
||||||
|
|
@ -77,7 +77,7 @@ def _strip_blocked_tools(toolsets: List[str]) -> List[str]:
|
||||||
return [t for t in toolsets if t not in blocked_toolset_names]
|
return [t for t in toolsets if t not in blocked_toolset_names]
|
||||||
|
|
||||||
|
|
||||||
def _build_child_progress_callback(task_index: int, parent_agent) -> Optional[callable]:
|
def _build_child_progress_callback(task_index: int, parent_agent, task_count: int = 1) -> Optional[callable]:
|
||||||
"""Build a callback that relays child agent tool calls to the parent display.
|
"""Build a callback that relays child agent tool calls to the parent display.
|
||||||
|
|
||||||
Two display paths:
|
Two display paths:
|
||||||
|
|
@ -93,7 +93,8 @@ def _build_child_progress_callback(task_index: int, parent_agent) -> Optional[ca
|
||||||
if not spinner and not parent_cb:
|
if not spinner and not parent_cb:
|
||||||
return None # No display → no callback → zero behavior change
|
return None # No display → no callback → zero behavior change
|
||||||
|
|
||||||
prefix = f"[{task_index}] " if task_index > 0 else ""
|
# Show 1-indexed prefix only in batch mode (multiple tasks)
|
||||||
|
prefix = f"[{task_index + 1}] " if task_count > 1 else ""
|
||||||
|
|
||||||
# Gateway: batch tool names, flush periodically
|
# Gateway: batch tool names, flush periodically
|
||||||
_BATCH_SIZE = 5
|
_BATCH_SIZE = 5
|
||||||
|
|
@ -163,6 +164,7 @@ def _run_single_child(
|
||||||
model: Optional[str],
|
model: Optional[str],
|
||||||
max_iterations: int,
|
max_iterations: int,
|
||||||
parent_agent,
|
parent_agent,
|
||||||
|
task_count: int = 1,
|
||||||
) -> Dict[str, Any]:
|
) -> Dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
Spawn and run a single child agent. Called from within a thread.
|
Spawn and run a single child agent. Called from within a thread.
|
||||||
|
|
@ -183,7 +185,7 @@ def _run_single_child(
|
||||||
parent_api_key = parent_agent._client_kwargs.get("api_key")
|
parent_api_key = parent_agent._client_kwargs.get("api_key")
|
||||||
|
|
||||||
# Build progress callback to relay tool calls to parent display
|
# Build progress callback to relay tool calls to parent display
|
||||||
child_progress_cb = _build_child_progress_callback(task_index, parent_agent)
|
child_progress_cb = _build_child_progress_callback(task_index, parent_agent, task_count)
|
||||||
|
|
||||||
child = AIAgent(
|
child = AIAgent(
|
||||||
base_url=parent_agent.base_url,
|
base_url=parent_agent.base_url,
|
||||||
|
|
@ -344,6 +346,7 @@ def delegate_task(
|
||||||
model=model,
|
model=model,
|
||||||
max_iterations=effective_max_iter,
|
max_iterations=effective_max_iter,
|
||||||
parent_agent=parent_agent,
|
parent_agent=parent_agent,
|
||||||
|
task_count=1,
|
||||||
)
|
)
|
||||||
results.append(result)
|
results.append(result)
|
||||||
else:
|
else:
|
||||||
|
|
@ -368,6 +371,7 @@ def delegate_task(
|
||||||
model=model,
|
model=model,
|
||||||
max_iterations=effective_max_iter,
|
max_iterations=effective_max_iter,
|
||||||
parent_agent=parent_agent,
|
parent_agent=parent_agent,
|
||||||
|
task_count=n_tasks,
|
||||||
)
|
)
|
||||||
futures[future] = i
|
futures[future] = i
|
||||||
|
|
||||||
|
|
@ -387,14 +391,21 @@ def delegate_task(
|
||||||
results.append(entry)
|
results.append(entry)
|
||||||
completed_count += 1
|
completed_count += 1
|
||||||
|
|
||||||
# Print per-task completion line (visible in CLI via patch_stdout)
|
# Print per-task completion line above the spinner
|
||||||
idx = entry["task_index"]
|
idx = entry["task_index"]
|
||||||
label = task_labels[idx] if idx < len(task_labels) else f"Task {idx}"
|
label = task_labels[idx] if idx < len(task_labels) else f"Task {idx}"
|
||||||
dur = entry.get("duration_seconds", 0)
|
dur = entry.get("duration_seconds", 0)
|
||||||
status = entry.get("status", "?")
|
status = entry.get("status", "?")
|
||||||
icon = "✓" if status == "completed" else "✗"
|
icon = "✓" if status == "completed" else "✗"
|
||||||
remaining = n_tasks - completed_count
|
remaining = n_tasks - completed_count
|
||||||
print(f" {icon} [{idx+1}/{n_tasks}] {label} ({dur}s)")
|
completion_line = f"{icon} [{idx+1}/{n_tasks}] {label} ({dur}s)"
|
||||||
|
if spinner_ref:
|
||||||
|
try:
|
||||||
|
spinner_ref.print_above(completion_line)
|
||||||
|
except Exception:
|
||||||
|
print(f" {completion_line}")
|
||||||
|
else:
|
||||||
|
print(f" {completion_line}")
|
||||||
|
|
||||||
# Update spinner text to show remaining count
|
# Update spinner text to show remaining count
|
||||||
if spinner_ref and remaining > 0:
|
if spinner_ref and remaining > 0:
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue