feat(systemd): add and enable continuous tmux-auto-approver user daemon

This commit is contained in:
operator
2026-10-07 00:49:23 +00:00
parent 34b0ef9fe2
commit e25d2cf4cc
12 changed files with 915 additions and 62 deletions
+64 -4
View File
@@ -953,8 +953,8 @@ def cmd_runtime(args):
print(c_dim(" No panes found."))
print()
return
headers = ["SOCKET", "SESSION", "PANE", "CMD", "STATE",
"APPROVE", "WATCHER"]
headers = ["SOCKET", "SESSION", "NODE", "PANE", "CMD",
"STATE", "APPROVE", "WATCHER"]
table = []
for r in rows:
state = r["state"]
@@ -970,6 +970,7 @@ def cmd_runtime(args):
if r["watcher_alive"] else badge_dim("-"))
table.append([os.path.basename(r["socket"]),
"%s:%s" % (r["session"], r["window"]),
r["node"] or badge_dim("-"),
r["pane"], (r["cmd"] or "")[:26], state,
approve, watcher])
print_table(headers, table)
@@ -1149,8 +1150,12 @@ def cmd_runtime(args):
spread, failed = [], []
for t in targets:
name = "spread-%s" % t["pane"]
# -t session: pins the new window to the SOURCE session.
# Without it break-pane follows the ATTACHED session and
# flings panes across sessions (observed live on scratch).
r = mcw._tmux(t["socket"], "break-pane", "-s", t["pane"],
"-n", name, timeout=15)
"-t", "%s:" % t["session"], "-n", name,
timeout=15)
if r.returncode == 0:
spread.append("%s:%s" % (t["session"], t["pane"]))
else:
@@ -1272,13 +1277,51 @@ def cmd_muse_choices(args):
print(line.rstrip("\n"))
print()
elif action == "resolve":
sock = getattr(args, "socket", None)
pane = getattr(args, "pane", None)
decision = getattr(args, "decision", None)
hf = mcw.read_hold(sock, pane)
if not hf:
if as_json:
print(json.dumps({"ok": True, "held": None,
"note": "nothing held"}, indent=2))
return
print(c_dim("\n Nothing held on %s:%s.\n" % (sock, pane)))
return
if decision == "deny" and hf.get("kind") not in mcw.NEGATIVE_KEYS:
msg = ("refusing deny on %s prompt (D2: questions always "
"resolve top-choice); use approve or wait for expiry"
% (hf.get("kind") or "?"))
if as_json:
print(json.dumps({"ok": False, "reason": msg}, indent=2))
return
print(c_red("\n Error: " + msg + "\n"), file=sys.stderr)
sys.exit(1)
hf["directive"] = decision
mcw.write_hold(sock, pane, hf)
mcw.audit("muse-choice-resolved",
name="%s:%s" % (os.path.basename(sock), pane),
caller=caller,
extra={"socket": sock, "pane": pane,
"decision": decision, "sig": hf.get("sig")})
if as_json:
print(json.dumps({"ok": True, "resolved": decision,
"pane": pane}, indent=2))
return
print(c_green("\n✔ Hold on %s:%s will %s within a poll."
% (sock, pane,
"approve" if decision == "approve" else "deny")))
print()
else: # status
desired = mcw.get_desired()
watchers = mcw.status_all()
answers = mcw.recent_answers(5)
held = mcw.list_holds()
if as_json:
print(json.dumps({"ok": True, "desired": desired,
"watchers": watchers,
"watchers": watchers, "held": held,
"recent_answers": answers}, indent=2))
return
state_badge = badge_ok("ON") if desired["enabled"] else badge_dim("OFF")
@@ -1302,6 +1345,18 @@ def cmd_muse_choices(args):
print_table(headers, rows)
else:
print(c_dim(" No watcher state files."))
if held:
print(c_bold("\n Held prompts (resolve via: box muse-choices resolve --socket S --pane P approve|deny):"))
for h in held:
try:
left = max(0, int(float(h.get("held_until", 0)) - time.time()))
except (TypeError, ValueError):
left = -1
print(f" • {badge_warn('HELD')} {c_bold(h.get('pane', '?'))} "
f"{h.get('kind')}/{h.get('key')} rule={h.get('rule')} "
f"expires in {left}s")
print(f" {c_dim((h.get('reason') or '')[:100])}")
print(f" {c_dim((h.get('text') or '')[:100])}")
if answers:
print(c_bold("\n Recent answers (box audit feed):"))
for a in answers:
@@ -5634,6 +5689,11 @@ def build_parser():
p_mc_rec = mc_sub.add_parser("reconcile", parents=[common], help="Enforce desired state now (start missing / stop excess)")
p_mc_res = mc_sub.add_parser("resolve", parents=[common], help="Resolve a held prompt (approve now or deny it)")
p_mc_res.add_argument("--socket", required=True, help="Tmux socket path (e.g. /tmp/tmux-1000/default)")
p_mc_res.add_argument("--pane", required=True, help="Pane id (e.g. %%37)")
p_mc_res.add_argument("decision", choices=["approve", "deny"], help="Release the hold to approve, or deny it (permission kinds only)")
# Domain: RUNTIME
p_rt = subparsers.add_parser("runtime", parents=[common], help="Muse CLI tmux runtimes: list states, send input, launch auto-approved")
rt_sub = p_rt.add_subparsers(dest="rt_action")