fix(work): import hashlib and wire heal subparser into main CLI

This commit is contained in:
operator
2026-10-09 23:13:43 +00:00
parent c3b4b1cd28
commit f75977ca6e
202 changed files with 4185 additions and 4142 deletions
+82 -15
View File
@@ -13,6 +13,7 @@ Supports:
- A/B/C choice prompts -> "A"
- Numbered menus -> "1"
- y/n confirmation prompts -> "y"
- Interview navigate+select menus (cursor on 1 -> Enter)
- Press Enter prompts -> "Enter"
- Safety guardrails (passwords, passkeys, destructive commands are never auto-approved)
4. State persistence & audit logging:
@@ -137,6 +138,21 @@ DEFAULT_RULES: List[MatchRule] = [
description="Confirms y/n at end of terminal line",
press_enter=True,
),
MatchRule(
id="interview_select",
name="Interview Menu (cursor on 1)",
pattern=(r"\?\s*\n"
r"(?:[^\n]*\n){0,8}"
r"[ \t]*(?:›|>)[ \t]*1\.[ \t]+\S[^\n]*\n"
r"(?:[^\n]*\n){0,10}"
r"[ \t]*2\.[ \t]+\S"),
response_key="Enter",
category="enter",
enabled=True,
description=("Selects highlighted option 1 on navigate+select "
"menus (cursor on 1. + 2. + ?-question above)"),
press_enter=False,
),
MatchRule(
id="enter_to_continue",
name="Press Enter to Continue",
@@ -188,9 +204,22 @@ class AutoApproverState:
try:
with open(STATE_FILE) as f:
data = json.load(f)
return cls(**data)
st = cls(**data)
except Exception:
return cls()
# Migrate: append built-in rules missing from stored state (a new
# default must reach the daemon without wiping operator toggles).
try:
have = {r.get("id") for r in st.rules
if isinstance(r, dict)}
missing = [asdict(r) for r in DEFAULT_RULES
if r.id not in have]
if missing:
st.rules.extend(missing)
st.save()
except Exception:
pass
return st
# =====================================================================
@@ -300,6 +329,23 @@ def capture_pane_text(socket_path: str, pane_id: str, lines: int = 30) -> str:
MUSE_COMMAND_HINTS = ("muse-bin", "muse-code")
# (socket, pane) ever observed running a muse runtime. pane_current_command
# flickers to the child tool while the agent works, so a muse pane stays
# muse-owned when its foreground reads "python3" (observed live: the hint
# gate missed tool-running panes and both daemons stacked 'y' answers).
_MUSE_PANES_SEEN = set()
# tmux rule category -> muse watcher kind for verified sends. Text-input
# categories verify render + submit with one retry; single-key widgets
# (and unknown categories) stay blind.
_CATEGORY_KIND_MAP = {
"choice": "letter",
"menu": "numbered",
"confirm": "yn",
"muse_code": "muse-approval",
"enter": None,
}
def should_defer_to_muse_watcher(socket_path: str, pane_id: str,
current_command: str) -> bool:
@@ -309,16 +355,25 @@ def should_defer_to_muse_watcher(socket_path: str, pane_id: str,
panes (stability + re-verify + once-per-prompt + decided-block
guard). When its daemon is alive for this socket:pane, tmux must
skip the pane entirely, or both daemons answer the same prompt
within the same second ('11' + stray keys, observed live). Never
raises: import or liveness failures mean no owner, handle here.
within the same second ('11' + stray keys, observed live; later the
same hole stacked 'y' answers when the foreground flickered to a
child tool mid-poll). Never raises: import or liveness failures
mean no owner, handle here.
"""
try:
cmd = current_command or ""
if not any(h in cmd for h in MUSE_COMMAND_HINTS):
return False
import muse_choice_watcher as mcw
alive = getattr(mcw, "watcher_alive", mcw.is_running)
return alive(socket_path, pane_id) is not None
cmd = current_command or ""
key = (socket_path, pane_id)
if any(h in cmd for h in MUSE_COMMAND_HINTS):
_MUSE_PANES_SEEN.add(key)
alive = getattr(mcw, "watcher_alive", mcw.is_running)
return alive(socket_path, pane_id) is not None
if key in _MUSE_PANES_SEEN:
alive = getattr(mcw, "watcher_alive", mcw.is_running)
return alive(socket_path, pane_id) is not None
# Never observed as muse: cheap pidfile check only (covers a
# watcher racing ahead of our first observation of the pane).
return mcw.is_running(socket_path, pane_id) is not None
except Exception:
return False
@@ -568,15 +623,25 @@ class AutoApproverRunner:
})
continue
# Execute key dispatch
# Execute key dispatch through the verified send path:
# literal text paced apart from Enter (a single-call
# burst arrives as paste and lands a newline in
# composers instead of submitting, then re-fires past
# dedup and stacks). Text-input categories also verify
# render + submit with one retry; single-key widgets
# stay blind.
success = False
detail = {"verified": None, "retried": False}
if not self.dry_run:
args = ["send-keys", "-t", p.pane_id, verdict.key]
if verdict.press_enter or verdict.key == "Enter":
if verdict.key != "Enter":
args.append("Enter")
rc, _, _ = run_tmux_cmd(p.socket, *args)
success = (rc == 0)
import muse_choice_watcher as mcw
want_enter = (verdict.key != "Enter"
and bool(verdict.press_enter))
ok, detail = mcw.send_answer(
p.socket, p.pane_id, verdict.key,
enter=want_enter,
kind=_CATEGORY_KIND_MAP.get(verdict.category),
sig=sig)
success = bool(ok)
else:
success = True # dry-run simulated
@@ -597,6 +662,8 @@ class AutoApproverRunner:
"excerpt": verdict.excerpt,
"dry_run": self.dry_run,
"success": success,
"verified": detail["verified"],
"retried": detail["retried"],
}
self.record_audit(event)
actions_taken.append(event)