docs(netvm): document watchdog, swarm-worker, and alert relay in README; track approvals CLI

This commit is contained in:
operator
2026-10-05 17:42:01 +00:00
parent 4f4b8768b6
commit d72b63bfd1
6 changed files with 945 additions and 4 deletions
+204 -4
View File
@@ -261,6 +261,18 @@ def collect_fleet_data() -> list:
tab = fetch_active_tab(info["peer_ip"], info["cdp_port"]) if cdp["ok"] else {"title": "-", "url": "-"}
q_depth = get_queue_depth(node)
approval_pending = False
approval_detail = None
if cdp["ok"] and ("(1)" in tab.get("title", "") or "approval" in tab.get("title", "").lower()):
try:
import approvals
app_info = approvals.inspect_node_approvals(node)
if app_info.get("has_pending"):
approval_pending = True
approval_detail = app_info
except Exception:
pass
results.append({
"node": node,
"netns": info["netns"],
@@ -272,6 +284,8 @@ def collect_fleet_data() -> list:
"title": tab["title"],
"url": tab["url"],
"queue_depth": q_depth,
"approval_pending": approval_pending,
"approval_detail": approval_detail,
})
return results
@@ -285,9 +299,13 @@ def cmd_fleet_status(args):
headers = ["NODE", "STATUS", "PEER IP:PORT", "LATENCY", "QUEUE", "ACTIVE PAGE / THREAD"]
rows = []
has_any_approval = False
for item in data:
# Status calculation
if item["proc_alive"] and item["cdp_ok"]:
if item.get("approval_pending"):
status = badge_warn("APPROVAL_REQ")
has_any_approval = True
elif item["proc_alive"] and item["cdp_ok"]:
status = badge_ok("ACTIVE")
elif item["proc_alive"] and not item["cdp_ok"]:
status = badge_warn("CDP_DOWN")
@@ -299,7 +317,10 @@ def cmd_fleet_status(args):
# Format title/URL nicely
title = item["title"]
if "thread/" in item["url"]:
if item.get("approval_pending"):
target = item.get("approval_detail", {}).get("ip") or "request"
title = f"{c_yellow('[APPROVAL: ' + target + ']')} {title}"
elif "thread/" in item["url"]:
m = re.search(r"thread/([0-9a-fA-F-]+)", item["url"])
if m:
uuid_short = m.group(1)[:8]
@@ -313,11 +334,13 @@ def cmd_fleet_status(args):
f"{item['peer_ip']}:{item['cdp_port']}",
lat,
q,
title[:45]
title[:50]
])
print_table(headers, rows)
print("\n" + c_dim(" Commands: super fleet watch | super fleet restart <node> | super fleet cdp <node>") + "\n")
if has_any_approval:
print("\n" + c_yellow(" ⚠ Agent(s) held up on browser approval. Run 'box approvals' to inspect/allow."))
print("\n" + c_dim(" Commands: super fleet watch | super fleet restart <node> | box approvals [check|allow|auto]") + "\n")
def cmd_fleet_watch(args):
interval = getattr(args, "interval", 2)
@@ -372,6 +395,146 @@ def cmd_fleet_cdp(args):
print(f" SSH Forward : {c_yellow(f'ssh -L {port}:{peer}:{port} super@100.123.153.75')}")
print(f" Local Connect : {c_dim(f'http://127.0.0.1:{port}')} (after forwarding)\n")
# ---------------------------------------------------------------------------
# Domain: APPROVALS
# ---------------------------------------------------------------------------
def cmd_approvals(args):
import approvals
action = getattr(args, "app_action", None) or "check"
node = getattr(args, "node", None)
if action in ("check", "list"):
nodes = [node] if node else VALID_NODES
fleet = approvals.check_fleet_approvals(nodes)
if getattr(args, "json", False):
print(json.dumps({"ok": True, "approvals": fleet}, indent=2))
return
print("\n" + c_bold("=== FLEET APPROVALS STATUS ===") + c_dim(f" ({datetime.now().strftime('%H:%M:%S')} local)\n"))
headers = ["NODE", "STATUS", "TARGET / IP", "PURPOSE / DETAILS", "TRUST", "ACTIONS"]
rows = []
pending_count = 0
untrusted_count = 0
for it in fleet:
n = it["node"]
st = it["status"]
if st == "PENDING":
pending_count += 1
badge = badge_err("PENDING") if not it.get("is_trusted") else badge_warn("PENDING")
target = it.get("ip") or "-"
purp = (it.get("purpose") or it.get("title") or "-")[:50]
trust = c_green("TRUSTED") if it.get("is_trusted") else c_red("UNTRUSTED")
btns = " ".join([f"[{b}]" for b in it.get("buttons", [])])
if not it.get("is_trusted"):
untrusted_count += 1
elif st == "UNREACHABLE":
badge = badge_dim("OFFLINE")
target = "-"
purp = c_dim("CDP unreachable")
trust = "-"
btns = "-"
elif st == "ERROR":
badge = badge_err("ERROR")
target = "-"
purp = it.get("error", "-")[:40]
trust = "-"
btns = "-"
else:
badge = badge_ok("CLEAR")
target = "-"
purp = c_dim("No pending approvals")
trust = "-"
btns = "-"
rows.append([c_bold(n), badge, target, purp, trust, btns])
print_table(headers, rows)
if pending_count > 0:
print("\n" + c_yellow(f" ⚠ {pending_count} pending approval(s) detected across fleet."))
if untrusted_count > 0:
print(c_red(f" ⚠ {untrusted_count} UNTRUSTED approval(s) require manual review: 'box approvals allow <node> --force' or 'box approvals deny <node>'."))
else:
print(c_cyan(" All pending approvals are for trusted infrastructure. Run 'box approvals auto' to resolve."))
print()
else:
print("\n" + c_green(" ✔ All agent approval queues clear. No agents blocked.") + "\n")
elif action in ("allow", "approve"):
if not node:
print(c_red("Error: Must specify node for allow. e.g. 'box approvals allow 646'"), file=sys.stderr)
sys.exit(1)
always = getattr(args, "always", False)
force = getattr(args, "force", False)
res = approvals.allow_node_approval(node, always=always, force=force)
if getattr(args, "json", False):
print(json.dumps(res, indent=2))
return
if res.get("ok"):
decision_str = "Always allow this site" if always else "Allow once"
print(c_green(f"✔ Approved request on node '{node}' ({decision_str}). Dialog dismissed: {res.get('dismissed')}."))
else:
print(c_red(f"✖ Failed to approve on node '{node}': {res.get('error')}"))
sys.exit(2 if "Untrusted" in res.get("error", "") else 1)
elif action == "deny":
if not node:
print(c_red("Error: Must specify node for deny. e.g. 'box approvals deny 646'"), file=sys.stderr)
sys.exit(1)
res = approvals.deny_node_approval(node)
if getattr(args, "json", False):
print(json.dumps(res, indent=2))
return
if res.get("ok"):
print(c_green(f"✔ Denied request on node '{node}'. Dialog dismissed: {res.get('dismissed')}."))
else:
print(c_red(f"✖ Failed to deny on node '{node}': {res.get('error')}"))
sys.exit(1)
elif action == "auto":
nodes = [node] if node else VALID_NODES
res = approvals.auto_approve_fleet(nodes)
if getattr(args, "json", False):
print(json.dumps(res, indent=2))
return
approved = res.get("auto_approved", [])
untrusted = res.get("untrusted_pending", [])
if approved:
print("\n" + c_green(f"✔ Auto-approved {len(approved)} trusted request(s):"))
for a in approved:
print(f" • {c_bold(a['node'])}: {a.get('target_ip')} — {a.get('title')}")
if untrusted:
print("\n" + c_yellow(f"⚠ {len(untrusted)} untrusted request(s) require manual decision:"))
for u in untrusted:
print(f" • {c_bold(u['node'])}: {u.get('ip')} — {u.get('title')} (run: box approvals allow {u['node']} --force)")
if not approved and not untrusted:
print(c_green("✔ All nodes clear. No approvals pending."))
print()
if untrusted:
sys.exit(2)
elif action == "watch":
interval = getattr(args, "interval", 2)
auto_mode = getattr(args, "auto", False)
try:
while True:
sys.stdout.write("\033[2J\033[H")
sys.stdout.flush()
if auto_mode:
auto_res = approvals.auto_approve_fleet(nodes=[node] if node else VALID_NODES)
if auto_res.get("auto_approved"):
for a in auto_res["auto_approved"]:
print(c_green(f"[AUTO-APPROVED] {a['node']}: {a.get('target_ip')}"))
# Print status
setattr(args, "app_action", "check")
cmd_approvals(args)
auto_label = c_cyan(" [AUTO-APPROVE ENABLED]") if auto_mode else ""
print(c_dim(f" [Watching every {interval}s{auto_label}. Press Ctrl+C to exit]"))
time.sleep(interval)
except KeyboardInterrupt:
print("\n" + c_dim("Exited watch mode."))
def resolve_sender(args) -> str:
explicit = getattr(args, "from_agent", None)
if explicit and explicit != DEFAULT_SENDER:
@@ -3602,6 +3765,41 @@ def build_parser():
p_fleet.add_argument("node", nargs="?", default=None, help="Target node (for restart / cdp)")
p_fleet.add_argument("--interval", type=int, default=2, help="Watch refresh interval in seconds")
# Domain: APPROVALS
p_approvals = subparsers.add_parser("approvals", parents=[common], help="Inspect and handle agent browser & gateway approvals")
p_approval = subparsers.add_parser("approval", parents=[common], help="Alias for 'approvals'")
for p_app in (p_approvals, p_approval):
p_app.add_argument("--node", choices=VALID_NODES, default=None, help="Filter by node")
app_sub = p_app.add_subparsers(dest="app_action")
p_app_check = app_sub.add_parser("check", parents=[common], help="Check fleet approval states")
p_app_check.add_argument("--node", choices=VALID_NODES, default=None, help="Filter by node")
p_app_list = app_sub.add_parser("list", parents=[common], help="Alias for 'check'")
p_app_list.add_argument("--node", choices=VALID_NODES, default=None, help="Filter by node")
p_app_allow = app_sub.add_parser("allow", parents=[common], help="Approve pending browser request")
p_app_allow.add_argument("node", choices=VALID_NODES, help="Target node to approve")
p_app_allow.add_argument("--always", action="store_true", help="Click 'Always allow this site' instead of 'Allow once'")
p_app_allow.add_argument("--force", action="store_true", help="Force approval even if target is untrusted")
p_app_approve = app_sub.add_parser("approve", parents=[common], help="Alias for 'allow'")
p_app_approve.add_argument("node", choices=VALID_NODES, help="Target node to approve")
p_app_approve.add_argument("--always", action="store_true", help="Click 'Always allow this site' instead of 'Allow once'")
p_app_approve.add_argument("--force", action="store_true", help="Force approval even if target is untrusted")
p_app_deny = app_sub.add_parser("deny", parents=[common], help="Deny pending browser request")
p_app_deny.add_argument("node", choices=VALID_NODES, help="Target node to deny")
p_app_auto = app_sub.add_parser("auto", parents=[common], help="Auto-approve all trusted requests across fleet")
p_app_auto.add_argument("--node", choices=VALID_NODES, default=None, help="Target node (or all nodes)")
p_app_watch = app_sub.add_parser("watch", parents=[common], help="Live watch pending approvals")
p_app_watch.add_argument("--interval", type=int, default=2, help="Watch refresh interval in seconds")
p_app_watch.add_argument("--auto", action="store_true", help="Automatically approve trusted requests as they appear")
p_app_watch.add_argument("--node", choices=VALID_NODES, default=None, help="Filter by node")
# Domain: DM
p_dm = subparsers.add_parser("dm", parents=[common], help="Inter-agent DMs, work orders ([WO]), acks, live log tail")
dm_sub = p_dm.add_subparsers(dest="action")
@@ -4285,6 +4483,8 @@ def main():
cmd_swarm_prune(args)
else:
p_swarm.print_help()
elif args.domain in ("approvals", "approval"):
cmd_approvals(args)
elif args.domain in ("deploy", "subagent"):
if args.domain == "subagent":
args.action = "subagent"