feat(cli): add 'box swarm' domain (list, status, spawn, prune)
This commit is contained in:
@@ -3109,6 +3109,101 @@ def cmd_ssh_show(args):
|
|||||||
except Exception:
|
except Exception:
|
||||||
sys.stdout.write(res.stdout)
|
sys.stdout.write(res.stdout)
|
||||||
|
|
||||||
|
|
||||||
|
def cmd_swarm_list(args):
|
||||||
|
cmd = ["python3", str(BIN_DIR / "box-ctl.py"), "swarm-list"]
|
||||||
|
res = subprocess.run(cmd, capture_output=True, text=True)
|
||||||
|
if args.json:
|
||||||
|
sys.stdout.write(res.stdout)
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
d = json.loads(res.stdout)
|
||||||
|
if d.get("ok"):
|
||||||
|
swarms = d.get("swarms", [])
|
||||||
|
print("\n" + c_bold(f"=== SWARMS ({len(swarms)}) ===") + "\n")
|
||||||
|
headers = ["ID", "STATUS", "LABEL", "TOTAL", "DONE", "RUNNING", "FAILED", "CREATED"]
|
||||||
|
rows = []
|
||||||
|
for sw in swarms:
|
||||||
|
st = sw.get("status")
|
||||||
|
st_color = c_green(st) if st == "completed" else (c_yellow(st) if st in ("running", "partial") else c_dim(st))
|
||||||
|
rows.append([
|
||||||
|
c_cyan(sw.get("swarm_id", "")[:18]),
|
||||||
|
st_color,
|
||||||
|
(sw.get("label") or "-")[:20],
|
||||||
|
str(sw.get("count", 0)),
|
||||||
|
str(sw.get("done", 0)),
|
||||||
|
str(sw.get("running", 0)),
|
||||||
|
str(sw.get("failed", 0)),
|
||||||
|
(sw.get("created_ts") or "")[:16]
|
||||||
|
])
|
||||||
|
print_table(headers, rows)
|
||||||
|
print("\n" + c_dim(" Spawn swarm: box swarm spawn <count> <task> [--label L]") + "\n")
|
||||||
|
else:
|
||||||
|
print(c_red(f"✗ Failed: {d.get('error')}"))
|
||||||
|
except Exception:
|
||||||
|
sys.stdout.write(res.stdout)
|
||||||
|
|
||||||
|
|
||||||
|
def cmd_swarm_status(args):
|
||||||
|
cmd = ["python3", str(BIN_DIR / "box-ctl.py"), "swarm-status", args.swarm_id]
|
||||||
|
res = subprocess.run(cmd, capture_output=True, text=True)
|
||||||
|
if args.json:
|
||||||
|
sys.stdout.write(res.stdout)
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
d = json.loads(res.stdout)
|
||||||
|
if d.get("ok"):
|
||||||
|
print(c_bold(f"\n=== SWARM STATUS: {args.swarm_id} ==="))
|
||||||
|
print(f" Status: {d.get('status')}")
|
||||||
|
print(f" Counts: {d.get('counts')}")
|
||||||
|
slots = d.get("slots", [])
|
||||||
|
print(f"\n Slots ({len(slots)}):")
|
||||||
|
for s in slots:
|
||||||
|
res_txt = s.get("result")
|
||||||
|
res_prev = (str(res_txt)[:50] + "...") if res_txt and len(str(res_txt)) > 50 else str(res_txt)
|
||||||
|
print(f" Slot {s.get('slot')}: [{s.get('status')}] agent={s.get('agent_id')} subagent={s.get('subagent_session_id')} res={res_prev}")
|
||||||
|
print()
|
||||||
|
else:
|
||||||
|
print(c_red(f"✗ Failed: {d.get('error')}"))
|
||||||
|
except Exception:
|
||||||
|
sys.stdout.write(res.stdout)
|
||||||
|
|
||||||
|
|
||||||
|
def cmd_swarm_spawn(args):
|
||||||
|
cmd = ["python3", str(BIN_DIR / "box-ctl.py"), "swarm-spawn", str(args.count), args.task]
|
||||||
|
if args.label:
|
||||||
|
cmd.extend(["--label", args.label])
|
||||||
|
res = subprocess.run(cmd, capture_output=True, text=True)
|
||||||
|
if args.json:
|
||||||
|
sys.stdout.write(res.stdout)
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
d = json.loads(res.stdout)
|
||||||
|
if d.get("ok"):
|
||||||
|
print(c_green(f"✓ Spawned swarm: {d.get('swarm_id')} ({d.get('count')} slots)"))
|
||||||
|
else:
|
||||||
|
print(c_red(f"✗ Failed: {d.get('error')} ({d.get('code')})"))
|
||||||
|
except Exception:
|
||||||
|
sys.stdout.write(res.stdout)
|
||||||
|
|
||||||
|
|
||||||
|
def cmd_swarm_prune(args):
|
||||||
|
cmd = ["python3", str(BIN_DIR / "box-ctl.py"), "swarm-prune", "--stale-hours", str(args.stale_hours)]
|
||||||
|
if args.confirm:
|
||||||
|
cmd.append("--confirm")
|
||||||
|
res = subprocess.run(cmd, capture_output=True, text=True)
|
||||||
|
if args.json:
|
||||||
|
sys.stdout.write(res.stdout)
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
d = json.loads(res.stdout)
|
||||||
|
if d.get("ok"):
|
||||||
|
print(c_green(f"✓ Pruned {d.get('archived_count')} swarms older than {d.get('stale_hours')}h to 'archived'"))
|
||||||
|
else:
|
||||||
|
print(c_yellow(f"! {d.get('error')} (use --confirm)"))
|
||||||
|
except Exception:
|
||||||
|
sys.stdout.write(res.stdout)
|
||||||
|
|
||||||
def build_parser():
|
def build_parser():
|
||||||
common = argparse.ArgumentParser(add_help=False)
|
common = argparse.ArgumentParser(add_help=False)
|
||||||
common.add_argument("--json", action="store_true", help="Output machine-readable JSON")
|
common.add_argument("--json", action="store_true", help="Output machine-readable JSON")
|
||||||
@@ -3499,6 +3594,24 @@ def build_parser():
|
|||||||
p_dep_sub.add_argument("prompt", help="Task prompt for the subagent")
|
p_dep_sub.add_argument("prompt", help="Task prompt for the subagent")
|
||||||
p_dep_sub.add_argument("--wait", type=int, default=30, help="Seconds to wait for subagent response")
|
p_dep_sub.add_argument("--wait", type=int, default=30, help="Seconds to wait for subagent response")
|
||||||
|
|
||||||
|
|
||||||
|
# Domain: SWARM
|
||||||
|
p_swarm = subparsers.add_parser("swarm", parents=[common], help="Manage multi-agent swarm tasks and worker allocations")
|
||||||
|
swarm_sub = p_swarm.add_subparsers(dest="action")
|
||||||
|
|
||||||
|
p_sw_list = swarm_sub.add_parser("list", parents=[common], help="List all swarms and slot rollups")
|
||||||
|
p_sw_status = swarm_sub.add_parser("status", parents=[common], help="Show status and slot details for a swarm")
|
||||||
|
p_sw_status.add_argument("swarm_id", help="Swarm identifier (sw-...)")
|
||||||
|
|
||||||
|
p_sw_spawn = swarm_sub.add_parser("spawn", parents=[common], help="Spawn a new multi-slot worker swarm")
|
||||||
|
p_sw_spawn.add_argument("count", type=int, help="Number of worker slots (1-50)")
|
||||||
|
p_sw_spawn.add_argument("task", help="Task prompt for the swarm workers")
|
||||||
|
p_sw_spawn.add_argument("--label", default=None, help="Optional tracking label")
|
||||||
|
|
||||||
|
p_sw_prune = swarm_sub.add_parser("prune", parents=[common], help="Archive stale partial/terminal swarms")
|
||||||
|
p_sw_prune.add_argument("--stale-hours", type=float, default=6.0, help="Hours of inactivity before pruning (default: 6)")
|
||||||
|
p_sw_prune.add_argument("--confirm", action="store_true", help="Confirm execution of pruning")
|
||||||
|
|
||||||
# Domain: subagent (Direct alias for spawning & managing subagents)
|
# Domain: subagent (Direct alias for spawning & managing subagents)
|
||||||
p_subagent = subparsers.add_parser("subagent", parents=[common], help="Spawn sub-agent session and dispatch task")
|
p_subagent = subparsers.add_parser("subagent", parents=[common], help="Spawn sub-agent session and dispatch task")
|
||||||
subagent_sub = p_subagent.add_subparsers(dest="action")
|
subagent_sub = p_subagent.add_subparsers(dest="action")
|
||||||
@@ -3691,6 +3804,18 @@ def main():
|
|||||||
cmd_loop_strat(args)
|
cmd_loop_strat(args)
|
||||||
elif args.domain == "vars":
|
elif args.domain == "vars":
|
||||||
cmd_loop_vars(args)
|
cmd_loop_vars(args)
|
||||||
|
elif args.domain == "swarm":
|
||||||
|
act = getattr(args, "action", None)
|
||||||
|
if not act or act == "list":
|
||||||
|
cmd_swarm_list(args)
|
||||||
|
elif act == "status":
|
||||||
|
cmd_swarm_status(args)
|
||||||
|
elif act == "spawn":
|
||||||
|
cmd_swarm_spawn(args)
|
||||||
|
elif act == "prune":
|
||||||
|
cmd_swarm_prune(args)
|
||||||
|
else:
|
||||||
|
p_swarm.print_help()
|
||||||
elif args.domain in ("deploy", "subagent"):
|
elif args.domain in ("deploy", "subagent"):
|
||||||
if args.domain == "subagent":
|
if args.domain == "subagent":
|
||||||
args.action = "subagent"
|
args.action = "subagent"
|
||||||
|
|||||||
Reference in New Issue
Block a user