feat(box): wire tmux tallies, auto-approvals, and onboard connects into CLI and API

- bin/box-ctl.py: wire tmux-tally, tmux-auto-status, tmux-auto-toggle, tmux-auto-once, and onboard-connects actions with idempotent allowlists
- bin/exec-constrained.py: register tmux.tally, tmux.auto_status, and onboard.connects ops for HTTPS execution
- bin/super-cli.py: wire box tmux dispatch to approver, add cmd_run/cmd_watch, and json unread formatting
- .agents/skills/box/SKILL.md: document tmux worker tally and auto-approval capabilities
This commit is contained in:
operator
2026-10-07 00:25:27 +00:00
parent 9f2a0e836d
commit 04339bad14
4 changed files with 2320 additions and 94 deletions
+2 -2
View File
@@ -32,8 +32,8 @@ Add `--json` to any command for machine-readable output when parsing results in
- `box lookup summary|fleet|threads|unread|approvals` — seamless one-shot lookups. - `box lookup summary|fleet|threads|unread|approvals` — seamless one-shot lookups.
- `box job list` / `box job log` — scheduled jobs and execution events. - `box job list` / `box job log` — scheduled jobs and execution events.
- `box harvest status` / `box followup list` — harvest watermarks / pending nudges. - `box harvest status` / `box followup list` — harvest watermarks / pending nudges.
- `box muse-choices on|off|status|logs|reconcile` — Muse TUI A/B/C auto-answer daemon switch, state, and per-pane logs. - `box muse-choices on|off|status|logs|reconcile` — Muse TUI auto-answer daemon switch, state, and per-pane logs (default on; `off` is the box-command opt-out).
- `box runtime list|send|launch` — Muse CLI tmux runtimes: live state + approval posture, send-keys input, auto-approved launches. - `box runtime list|send|launch|layout|spread` — Muse CLI tmux runtimes: live state + approval posture, send-keys input, auto-approved launches, pane-geometry layout + spread for squeezed panes.
- `box tmux tally` / `box tmux auto [status|on|off|watch|once|logs|match]` — multi-socket Tmux worker tally, regex auto-approver daemon & guardrails. - `box tmux tally` / `box tmux auto [status|on|off|watch|once|logs|match]` — multi-socket Tmux worker tally, regex auto-approver daemon & guardrails.
- `box onboard connects` / `box onboard-tui` — fleet & client onboarding inventory, CDP ports, OTP salvage & 4-surface TUI. - `box onboard connects` / `box onboard-tui` — fleet & client onboarding inventory, CDP ports, OTP salvage & 4-surface TUI.
- `box invite status|code <node>|redeem <node> <CODE>` / `box usage [--node N]` — invite codes and usage limits. - `box invite status|code <node>|redeem <node> <CODE>` / `box usage [--node N]` — invite codes and usage limits.
+1015 -88
View File
File diff suppressed because it is too large Load Diff
+1023 -3
View File
File diff suppressed because it is too large Load Diff
+280 -1
View File
@@ -70,6 +70,7 @@ DEFAULT_AGENT_SIDECHATS = {
"pip": "646-pip-coord", "pip": "646-pip-coord",
"muse": "muse tasks", "muse": "muse tasks",
} }
MUSE_TMUX_LOG_DIR = NETVM_ROOT / "logs" / "tmux"
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# ANSI Color & Formatting Engine # ANSI Color & Formatting Engine
@@ -1082,6 +1083,96 @@ def cmd_runtime(args):
print(" %s watcher %s" % (badge_ok("STARTED"), c_cyan(s))) print(" %s watcher %s" % (badge_ok("STARTED"), c_cyan(s)))
print() print()
elif action == "layout":
sock = getattr(args, "socket", None)
rows = mcw.all_runtime_rows([sock] if sock else None)
minimum = {"width": mcw.MIN_APPROVAL_WIDTH,
"height": mcw.MIN_APPROVAL_HEIGHT}
if as_json:
print(json.dumps({"ok": True, "runtimes": rows,
"minimum": minimum}, indent=2))
return
print(c_bold("\n=== RUNTIME LAYOUT ===\n"))
print(c_dim(" Minimum for reliable approvals: %dx%d\n" % (
minimum["width"], minimum["height"])))
if not rows:
print(c_dim(" No panes found."))
print()
return
headers = ["SOCKET", "SESSION", "PANE", "DIMS", "GEO", "CMD"]
table = []
for r in rows:
dims = ("%dx%d" % (r["width"], r["height"])
if r["width"] is not None else "-")
if not r["is_muse"]:
geo = badge_dim("-")
elif r["squeezed"]:
geo = badge_err("SQUEEZED")
else:
geo = badge_ok("OK")
table.append([os.path.basename(r["socket"]),
"%s:%s" % (r["session"], r["window"]),
r["pane"], dims, geo, (r["cmd"] or "")[:26]])
print_table(headers, table)
targets = mcw.spread_targets(rows)
if targets:
print(c_yellow(" %d squeezed runtime(s): %s" % (
len(targets), ", ".join(
"%s:%s" % (t["session"], t["pane"])
for t in targets))))
print(c_dim(" Run `box runtime spread` to break them into "
"own windows."))
print()
elif action == "spread":
sock = getattr(args, "socket", None)
session = getattr(args, "session", None)
dry_run = getattr(args, "dry_run", False)
rows = mcw.all_runtime_rows([sock] if sock else None)
if session:
rows = [r for r in rows if r["session"] == session]
targets = mcw.spread_targets(rows)
if dry_run:
if as_json:
print(json.dumps({"ok": True, "dry_run": True,
"targets": targets}, indent=2))
return
print(c_bold("\n=== RUNTIME SPREAD (dry-run) ===\n"))
if not targets:
print(c_dim(" No squeezed runtimes; nothing to spread."))
for t in targets:
print(" Would break %s %s:%s (%dx%d) into own "
"window" % (t["socket"], t["session"], t["pane"],
t["width"], t["height"]))
print()
return
spread, failed = [], []
for t in targets:
name = "spread-%s" % t["pane"]
r = mcw._tmux(t["socket"], "break-pane", "-s", t["pane"],
"-n", name, timeout=15)
if r.returncode == 0:
spread.append("%s:%s" % (t["session"], t["pane"]))
else:
failed.append({
"pane": "%s:%s" % (t["session"], t["pane"]),
"error": (r.stderr or r.stdout or "").strip()
or "tmux error"})
if as_json:
print(json.dumps({"ok": not failed, "spread": spread,
"failed": failed}, indent=2))
return
print(c_bold("\n=== RUNTIME SPREAD ===\n"))
if not targets:
print(c_dim(" No squeezed runtimes; nothing to spread."))
for s in spread:
print(" %s spread %s (watchers follow pane ids)" % (
badge_ok("MOVED"), c_cyan(s)))
for f in failed:
print(" %s %s: %s" % (
badge_err("FAILED"), f["pane"], f["error"]))
print()
else: else:
if as_json: if as_json:
print(json.dumps({"ok": False, print(json.dumps({"ok": False,
@@ -5100,10 +5191,35 @@ def cmd_passkey_info(args):
def _lookup_unreads(args): def _lookup_unreads(args):
data = collect_fleet_data()
as_json = getattr(args, "json", False)
if as_json:
nodes_out = []
for item in data:
n = item.get("node")
title = item.get("title", "")
unread_cnt = 0
if "(1)" in title or "(2)" in title or "(3)" in title:
m = re.search(r"\((\d+)\)", title)
unread_cnt = int(m.group(1)) if m else 1
thread_info = "-"
if "thread/" in item.get("url", ""):
thread_info = item["url"].split("thread/")[-1][:12]
elif item.get("url") == "https://muse.ai/":
thread_info = "home"
nodes_out.append({
"node": n,
"unread": unread_cnt,
"title": title,
"thread": thread_info,
"approval_pending": bool(item.get("approval_pending")),
})
print(json.dumps({"ok": True, "nodes": nodes_out}))
return
print("\n" + c_bold("=== FLEET UNREAD / ACTIVITY STATUS ===") + "\n") print("\n" + c_bold("=== FLEET UNREAD / ACTIVITY STATUS ===") + "\n")
headers = ["NODE", "UNREAD COUNT", "ACTIVE PAGE / TITLE", "LAST SEEN / THREAD"] headers = ["NODE", "UNREAD COUNT", "ACTIVE PAGE / TITLE", "LAST SEEN / THREAD"]
rows = [] rows = []
data = collect_fleet_data()
for item in data: for item in data:
n = item["node"] n = item["node"]
title = item.get("title", "") title = item.get("title", "")
@@ -5157,6 +5273,8 @@ def cmd_lookup(args):
cmd_fleet_status(args) cmd_fleet_status(args)
elif sub in ("threads", "sidechats"): elif sub in ("threads", "sidechats"):
cmd_thread_list(args) cmd_thread_list(args)
elif sub in ("unread", "unreads"):
_lookup_unreads(args)
elif sub in ("approvals", "approval"): elif sub in ("approvals", "approval"):
cmd_approvals(args) cmd_approvals(args)
elif sub in ("docs", "doc", "surfaces", "sentence", "regex", "parse"): elif sub in ("docs", "doc", "surfaces", "sentence", "regex", "parse"):
@@ -5169,6 +5287,159 @@ def cmd_lookup(args):
print(f"Unknown lookup target '{sub}'. Choose from: fleet, threads, unread, approvals, key, docs", file=sys.stderr) print(f"Unknown lookup target '{sub}'. Choose from: fleet, threads, unread, approvals, key, docs", file=sys.stderr)
def _sanitize_tmux_name(name: str) -> str:
cleaned = re.sub(r"[^a-zA-Z0-9_-]", "-", name).strip("-")
cleaned = re.sub(r"-+", "-", cleaned)
return cleaned or "run"
def _write_watch_script(session: str) -> tuple[str, str]:
watch_session = f"{session}-watch"
script_path = f"/tmp/{watch_session}.sh"
content = f"""#!/bin/bash
target="{session}"
exit_file="/tmp/{session}.exit"
max=200
count=0
while [ $count -lt $max ]; do
if ! tmux -S /tmp/tmux-muse.sock has-session -t "$target" 2>/dev/null; then
break
fi
pane_content=$(tmux -S /tmp/tmux-muse.sock capture-pane -p -t "$target" 2>/dev/null)
if echo "$pane_content" | grep -q "› 1. Yes, proceed"; then
tmux -S /tmp/tmux-muse.sock send-keys -t "$target" "1" Enter
fi
sleep 1
count=$((count+1))
done
touch "$exit_file"
"""
p = Path(script_path)
p.write_text(content, encoding="utf-8")
os.chmod(script_path, 0o755)
return watch_session, script_path
def cmd_run(args):
"""Run a headless muse-code session in tmux on /tmp/tmux-muse.sock."""
if not shutil.which("tmux"):
print("tmux not found", file=sys.stderr)
sys.exit(2)
if not shutil.which("muse-code"):
print("muse-code not found", file=sys.stderr)
sys.exit(2)
prompt = getattr(args, "prompt", None)
prompt_file = getattr(args, "prompt_file", None)
if not prompt and not prompt_file:
if sys.stdin.isatty():
print("Error: prompt or --prompt-file required", file=sys.stderr)
sys.exit(2)
prompt = sys.stdin.read()
if not prompt.strip():
print("Error: empty prompt", file=sys.stderr)
sys.exit(2)
raw_session = getattr(args, "session", None) or f"run-{int(time.time())}"
session = _sanitize_tmux_name(raw_session)
auto_approve = getattr(args, "auto_approve", False)
prompt_path = f"/tmp/{session}.prompt"
if prompt:
Path(prompt_path).write_text(prompt if prompt.endswith("\n") else prompt + "\n", encoding="utf-8")
elif prompt_file:
prompt_path = prompt_file
wrapper_path = f"/tmp/{session}.sh"
cmd_parts = [
"cd /home/super/Projects/NetVM",
f"muse-code --prompt-file {prompt_path}",
]
if getattr(args, "provider", None):
cmd_parts.append(f"--provider {args.provider}")
if getattr(args, "model", None):
cmd_parts.append(f"--model {args.model}")
if getattr(args, "effort", None):
cmd_parts.append(f"--reasoning-effort {args.effort}")
if getattr(args, "permission_profile", None):
cmd_parts.append(f"--permission-profile {args.permission_profile}")
if getattr(args, "trust_workspace", False):
cmd_parts.append("--trust-workspace")
if getattr(args, "approval_mode", None):
cmd_parts.append(f"--approval-mode {args.approval_mode}")
full_cmd = " && ".join(cmd_parts)
wrapper_content = f"#!/bin/bash\n{full_cmd}\ntouch /tmp/{session}.exit\n"
Path(wrapper_path).write_text(wrapper_content, encoding="utf-8")
os.chmod(wrapper_path, 0o755)
MUSE_TMUX_LOG_DIR.mkdir(parents=True, exist_ok=True)
log_path = MUSE_TMUX_LOG_DIR / f"{session}.log"
tmux_cmd = [
"tmux", "-S", "/tmp/tmux-muse.sock",
"new-session", "-d", "-s", session,
f"{wrapper_path} 2>&1 | tee {log_path}"
]
res = subprocess.run(tmux_cmd)
if res.returncode != 0:
print(f"Error launching tmux session: {res.stderr or 'failed'}", file=sys.stderr)
sys.exit(res.returncode or 1)
print(f"session: {session}")
print(f"attach: tmux -S /tmp/tmux-muse.sock attach -t {session}")
if auto_approve:
watch_session, watch_script = _write_watch_script(session)
watch_log = MUSE_TMUX_LOG_DIR / f"{watch_session}.log"
w_cmd = [
"tmux", "-S", "/tmp/tmux-muse.sock",
"new-session", "-d", "-s", watch_session,
f"{watch_script} 2>&1 | tee {watch_log}"
]
w_res = subprocess.run(w_cmd)
if w_res.returncode == 0:
print(f"watcher: {watch_session}")
print(f"watcher-log: {watch_log}")
def cmd_watch(args):
"""Spawn an auto-approve watcher on an existing tmux session."""
if not shutil.which("tmux"):
print("tmux not found", file=sys.stderr)
sys.exit(2)
raw_session = getattr(args, "session", None)
if not raw_session:
print("Error: session name required", file=sys.stderr)
sys.exit(2)
session = _sanitize_tmux_name(raw_session)
has_res = subprocess.run(["tmux", "-S", "/tmp/tmux-muse.sock", "has-session", "-t", session])
if has_res.returncode != 0:
print(f"Error: session '{session}' does not exist", file=sys.stderr)
sys.exit(2)
watch_session, watch_script = _write_watch_script(session)
MUSE_TMUX_LOG_DIR.mkdir(parents=True, exist_ok=True)
watch_log = MUSE_TMUX_LOG_DIR / f"{watch_session}.log"
w_cmd = [
"tmux", "-S", "/tmp/tmux-muse.sock",
"new-session", "-d", "-s", watch_session,
f"{watch_script} 2>&1 | tee {watch_log}"
]
w_res = subprocess.run(w_cmd)
if w_res.returncode != 0:
print(f"Error starting watcher: {w_res.stderr or 'failed'}", file=sys.stderr)
sys.exit(w_res.returncode or 1)
print(f"watching: {session}")
print(f"watcher: {watch_session}")
print(f"watcher-log: {watch_log}")
def cmd_docs_dispatch(args): def cmd_docs_dispatch(args):
"""Bridge 'box docs' and 'super docs' directly to bin/docs-lookup.py.""" """Bridge 'box docs' and 'super docs' directly to bin/docs-lookup.py."""
d_args = getattr(args, "docs_args", []) or [] d_args = getattr(args, "docs_args", []) or []
@@ -5384,6 +5655,14 @@ def build_parser():
p_rt_launch.add_argument("--dry-run", action="store_true", help="Print the launch plan without creating") p_rt_launch.add_argument("--dry-run", action="store_true", help="Print the launch plan without creating")
p_rt_launch.add_argument("muse_args", nargs=argparse.REMAINDER, default=[], help="Extra muse args after --") p_rt_launch.add_argument("muse_args", nargs=argparse.REMAINDER, default=[], help="Extra muse args after --")
p_rt_layout = rt_sub.add_parser("layout", parents=[common], help="Show pane geometry vs approval minimums")
p_rt_layout.add_argument("--socket", default=None, help="Only this tmux socket")
p_rt_spread = rt_sub.add_parser("spread", parents=[common], help="Break squeezed runtimes into own windows")
p_rt_spread.add_argument("--socket", default=None, help="Only this tmux socket")
p_rt_spread.add_argument("--session", default=None, help="Only this tmux session")
p_rt_spread.add_argument("--dry-run", action="store_true", help="Print the spread plan without moving panes")
# Domain: INVITE # Domain: INVITE
p_invite = subparsers.add_parser("invite", parents=[common], help="Muse.ai invite codes: find per-agent codes and redeem") p_invite = subparsers.add_parser("invite", parents=[common], help="Muse.ai invite codes: find per-agent codes and redeem")
p_invite.add_argument("--node", choices=VALID_NODES, default=None, help="Filter by node (status)") p_invite.add_argument("--node", choices=VALID_NODES, default=None, help="Filter by node (status)")