feat: enhance task delegation with spinner updates and progress display
- Added a spinner to visually indicate task delegation progress in quiet mode, improving user experience during batch processing. - Implemented a method to update spinner text dynamically based on remaining tasks, providing real-time feedback. - Enhanced the `delegate_task` function to include per-task completion messages, ensuring clarity on task status during execution. - Updated the KawaiiSpinner class to allow message updates while running, facilitating better interaction during long-running tasks.
This commit is contained in:
parent
90e5211128
commit
ba07d9d5e3
2 changed files with 64 additions and 16 deletions
45
run_agent.py
45
run_agent.py
|
|
@ -1023,6 +1023,10 @@ class KawaiiSpinner:
|
||||||
self.thread = threading.Thread(target=self._animate, daemon=True)
|
self.thread = threading.Thread(target=self._animate, daemon=True)
|
||||||
self.thread.start()
|
self.thread.start()
|
||||||
|
|
||||||
|
def update_text(self, new_message: str):
|
||||||
|
"""Update the spinner message text while it's running."""
|
||||||
|
self.message = new_message
|
||||||
|
|
||||||
def stop(self, final_message: str = None):
|
def stop(self, final_message: str = None):
|
||||||
"""Stop the spinner and optionally print a final message."""
|
"""Stop the spinner and optionally print a final message."""
|
||||||
self.running = False
|
self.running = False
|
||||||
|
|
@ -2978,18 +2982,37 @@ class AIAgent:
|
||||||
# Delegate task -- spawn child agent(s) with isolated context
|
# Delegate task -- spawn child agent(s) with isolated context
|
||||||
elif function_name == "delegate_task":
|
elif function_name == "delegate_task":
|
||||||
from tools.delegate_tool import delegate_task as _delegate_task
|
from tools.delegate_tool import delegate_task as _delegate_task
|
||||||
function_result = _delegate_task(
|
tasks_arg = function_args.get("tasks")
|
||||||
goal=function_args.get("goal"),
|
if tasks_arg and isinstance(tasks_arg, list):
|
||||||
context=function_args.get("context"),
|
spinner_label = f"🔀 delegating {len(tasks_arg)} tasks"
|
||||||
toolsets=function_args.get("toolsets"),
|
else:
|
||||||
tasks=function_args.get("tasks"),
|
goal_preview = (function_args.get("goal") or "")[:30]
|
||||||
model=function_args.get("model"),
|
spinner_label = f"🔀 {goal_preview}" if goal_preview else "🔀 delegating"
|
||||||
max_iterations=function_args.get("max_iterations"),
|
spinner = None
|
||||||
parent_agent=self,
|
|
||||||
)
|
|
||||||
tool_duration = time.time() - tool_start_time
|
|
||||||
if self.quiet_mode:
|
if self.quiet_mode:
|
||||||
print(f" {self._get_cute_tool_message('delegate_task', function_args, tool_duration)}")
|
face = random.choice(KawaiiSpinner.KAWAII_WAITING)
|
||||||
|
spinner = KawaiiSpinner(f"{face} {spinner_label}", spinner_type='dots')
|
||||||
|
spinner.start()
|
||||||
|
# Store spinner on self so delegate_tool can update its text
|
||||||
|
self._delegate_spinner = spinner
|
||||||
|
try:
|
||||||
|
function_result = _delegate_task(
|
||||||
|
goal=function_args.get("goal"),
|
||||||
|
context=function_args.get("context"),
|
||||||
|
toolsets=function_args.get("toolsets"),
|
||||||
|
tasks=tasks_arg,
|
||||||
|
model=function_args.get("model"),
|
||||||
|
max_iterations=function_args.get("max_iterations"),
|
||||||
|
parent_agent=self,
|
||||||
|
)
|
||||||
|
finally:
|
||||||
|
self._delegate_spinner = None
|
||||||
|
tool_duration = time.time() - tool_start_time
|
||||||
|
cute_msg = self._get_cute_tool_message('delegate_task', function_args, tool_duration)
|
||||||
|
if spinner:
|
||||||
|
spinner.stop(cute_msg)
|
||||||
|
elif self.quiet_mode:
|
||||||
|
print(f" {cute_msg}")
|
||||||
# Execute other tools - with animated kawaii spinner in quiet mode
|
# Execute other tools - with animated kawaii spinner in quiet mode
|
||||||
# The face is "alive" while the tool works, then vanishes
|
# The face is "alive" while the tool works, then vanishes
|
||||||
# and is replaced by the clean result line.
|
# and is replaced by the clean result line.
|
||||||
|
|
|
||||||
|
|
@ -231,7 +231,11 @@ def delegate_task(
|
||||||
overall_start = time.monotonic()
|
overall_start = time.monotonic()
|
||||||
results = []
|
results = []
|
||||||
|
|
||||||
if len(task_list) == 1:
|
n_tasks = len(task_list)
|
||||||
|
# Track goal labels for progress display (truncated for readability)
|
||||||
|
task_labels = [t["goal"][:40] for t in task_list]
|
||||||
|
|
||||||
|
if n_tasks == 1:
|
||||||
# Single task -- run directly (no thread pool overhead)
|
# Single task -- run directly (no thread pool overhead)
|
||||||
t = task_list[0]
|
t = task_list[0]
|
||||||
result = _run_single_child(
|
result = _run_single_child(
|
||||||
|
|
@ -245,7 +249,10 @@ def delegate_task(
|
||||||
)
|
)
|
||||||
results.append(result)
|
results.append(result)
|
||||||
else:
|
else:
|
||||||
# Batch -- run in parallel
|
# Batch -- run in parallel with per-task progress lines
|
||||||
|
completed_count = 0
|
||||||
|
spinner_ref = getattr(parent_agent, '_delegate_spinner', None)
|
||||||
|
|
||||||
with ThreadPoolExecutor(max_workers=MAX_CONCURRENT_CHILDREN) as executor:
|
with ThreadPoolExecutor(max_workers=MAX_CONCURRENT_CHILDREN) as executor:
|
||||||
futures = {}
|
futures = {}
|
||||||
for i, t in enumerate(task_list):
|
for i, t in enumerate(task_list):
|
||||||
|
|
@ -263,17 +270,35 @@ def delegate_task(
|
||||||
|
|
||||||
for future in as_completed(futures):
|
for future in as_completed(futures):
|
||||||
try:
|
try:
|
||||||
results.append(future.result())
|
entry = future.result()
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
idx = futures[future]
|
idx = futures[future]
|
||||||
results.append({
|
entry = {
|
||||||
"task_index": idx,
|
"task_index": idx,
|
||||||
"status": "error",
|
"status": "error",
|
||||||
"summary": None,
|
"summary": None,
|
||||||
"error": str(exc),
|
"error": str(exc),
|
||||||
"api_calls": 0,
|
"api_calls": 0,
|
||||||
"duration_seconds": 0,
|
"duration_seconds": 0,
|
||||||
})
|
}
|
||||||
|
results.append(entry)
|
||||||
|
completed_count += 1
|
||||||
|
|
||||||
|
# Print per-task completion line (visible in CLI via patch_stdout)
|
||||||
|
idx = entry["task_index"]
|
||||||
|
label = task_labels[idx] if idx < len(task_labels) else f"Task {idx}"
|
||||||
|
dur = entry.get("duration_seconds", 0)
|
||||||
|
status = entry.get("status", "?")
|
||||||
|
icon = "✓" if status == "completed" else "✗"
|
||||||
|
remaining = n_tasks - completed_count
|
||||||
|
print(f" {icon} [{idx+1}/{n_tasks}] {label} ({dur}s)")
|
||||||
|
|
||||||
|
# Update spinner text to show remaining count
|
||||||
|
if spinner_ref and remaining > 0:
|
||||||
|
try:
|
||||||
|
spinner_ref.update_text(f"🔀 {remaining} task{'s' if remaining != 1 else ''} remaining")
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
# Sort by task_index so results match input order
|
# Sort by task_index so results match input order
|
||||||
results.sort(key=lambda r: r["task_index"])
|
results.sort(key=lambda r: r["task_index"])
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue