feat(systemd): add and enable continuous tmux-auto-approver user daemon
This commit is contained in:
@@ -138,29 +138,6 @@ JS_CLICK_SWITCH = """((label) => {
|
||||
return 'CLICKED';
|
||||
})('%s')"""
|
||||
|
||||
JS_SWITCH_RECT = """((label) => {
|
||||
const d = document.querySelector('[role="dialog"]');
|
||||
if (!d) return null;
|
||||
const rowOf = (s) => {
|
||||
let el = s.parentElement, depth = 0;
|
||||
while (el && el !== d && depth < 6) {
|
||||
const t = (el.innerText || '').trim();
|
||||
if (t && t.length < 250) return t.toLowerCase();
|
||||
el = el.parentElement;
|
||||
depth += 1;
|
||||
}
|
||||
return '';
|
||||
};
|
||||
const hits = Array.from(d.querySelectorAll('[role="switch"]'))
|
||||
.filter(s => rowOf(s).includes(label.toLowerCase())
|
||||
|| (s.getAttribute('aria-label') || '').toLowerCase()
|
||||
.includes(label.toLowerCase()));
|
||||
if (hits.length !== 1) return null;
|
||||
const r = hits[0].getBoundingClientRect();
|
||||
return {x: r.x + r.width / 2, y: r.y + r.height / 2};
|
||||
})('%s')"""
|
||||
|
||||
|
||||
def _eval(ws, js, timeout=8.0):
|
||||
try:
|
||||
return cdp_evaluate(ws, js, timeout=timeout)
|
||||
@@ -256,20 +233,16 @@ def switch_state(ws, label):
|
||||
|
||||
|
||||
def set_switch(ws, label, on):
|
||||
"""Set a switch by row-label/aria match; no-op when already there."""
|
||||
"""Set a switch by row-label/aria match; no-op when already there.
|
||||
|
||||
Single click only: a fallback re-click would UNDO a slow commit
|
||||
(switches toggle). The fresh-session readback decides.
|
||||
"""
|
||||
state = switch_state(ws, label)
|
||||
if state is None:
|
||||
return False
|
||||
if state == bool(on):
|
||||
return True
|
||||
if _eval(ws, JS_CLICK_SWITCH % label) == "CLICKED" \
|
||||
and _poll(lambda: switch_state(ws, label) is bool(on)):
|
||||
return True
|
||||
rect = _eval(ws, JS_SWITCH_RECT % label)
|
||||
if not rect or "x" not in rect:
|
||||
return False
|
||||
try:
|
||||
real_click(ws, rect["x"], rect["y"])
|
||||
except Exception:
|
||||
if _eval(ws, JS_CLICK_SWITCH % label) != "CLICKED":
|
||||
return False
|
||||
return _poll(lambda: switch_state(ws, label) is bool(on))
|
||||
|
||||
@@ -1,15 +1,49 @@
|
||||
"""Data controls tab: model-improvement switch (read-only otherwise).
|
||||
|
||||
The switch label is pinned from live recon; resolution prefers it
|
||||
and falls back to single-switch, then keyword match. Import/Delete
|
||||
rows are inventoried, never touched.
|
||||
and falls back to single-switch, then keyword match. Sets use a
|
||||
trusted click: synthetic clicks are proven no-ops here (2026-10-06).
|
||||
Import/Delete rows are inventoried, never touched.
|
||||
"""
|
||||
import time
|
||||
|
||||
from approvals import cdp_evaluate
|
||||
from hatch_menu import controls, dialog
|
||||
from hatch_menu.mouse import real_click
|
||||
|
||||
TAB = "Data controls"
|
||||
SWITCH_LABEL = "Help improve our AI models"
|
||||
_KEYWORDS = ("improv", "train", "model", "data", "usage")
|
||||
|
||||
JS_AI_RECT_TMPL = """((label) => {
|
||||
const d = document.querySelector('[role="dialog"]');
|
||||
if (!d) return null;
|
||||
const rowOf = (s) => {
|
||||
let el = s.parentElement, depth = 0;
|
||||
while (el && el !== d && depth < 6) {
|
||||
const t = (el.innerText || '').trim();
|
||||
if (t && t.length < 250) return t.toLowerCase();
|
||||
el = el.parentElement;
|
||||
depth += 1;
|
||||
}
|
||||
return '';
|
||||
};
|
||||
const hits = Array.from(d.querySelectorAll('[role="switch"]'))
|
||||
.filter(s => rowOf(s).includes(label.toLowerCase())
|
||||
|| (s.getAttribute('aria-label') || '').toLowerCase()
|
||||
.includes(label.toLowerCase()));
|
||||
if (hits.length !== 1) return null;
|
||||
const r = hits[0].getBoundingClientRect();
|
||||
return {x: r.x + r.width / 2, y: r.y + r.height / 2};
|
||||
})('%s')"""
|
||||
|
||||
|
||||
def _eval(ws, js, timeout=8.0):
|
||||
try:
|
||||
return cdp_evaluate(ws, js, timeout=timeout)
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
|
||||
def _resolve(ws):
|
||||
"""The improvement switch dict, or None when not resolvable."""
|
||||
@@ -39,15 +73,24 @@ def ai_improvement(ws):
|
||||
|
||||
|
||||
def set_ai_improvement(ws, on):
|
||||
"""Set the improvement switch; verify-then-fallback. Bool."""
|
||||
"""Set via one trusted click; synthetic clicks are no-ops. Bool."""
|
||||
sw = _resolve(ws)
|
||||
if sw is None:
|
||||
return False
|
||||
label = (sw.get("aria") or "").strip() \
|
||||
or (sw.get("label") or "")[:40].strip()
|
||||
if not label:
|
||||
if bool(sw.get("checked")) == bool(on):
|
||||
return True
|
||||
rect = _eval(ws, JS_AI_RECT_TMPL % SWITCH_LABEL)
|
||||
if not rect or "x" not in rect:
|
||||
return False
|
||||
return controls.set_switch(ws, label, on)
|
||||
try:
|
||||
real_click(ws, rect["x"], rect["y"])
|
||||
except Exception:
|
||||
return False
|
||||
for _ in range(8):
|
||||
time.sleep(2.0)
|
||||
if ai_improvement(ws) is bool(on):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def describe(ws):
|
||||
|
||||
@@ -44,7 +44,10 @@ TOGGLES = {
|
||||
"values": ("on", "off")},
|
||||
"data_controls.ai_improvement": {
|
||||
"tab": "Data controls", "kind": "switch",
|
||||
"values": ("on", "off")},
|
||||
"values": ("on", "off"),
|
||||
# Live 2026-10-06: the site ignores every input gesture here
|
||||
# (synthetic/real/double/hover/keyboard/drag) — reads fine.
|
||||
"readonly": True},
|
||||
"general.theme": {
|
||||
"tab": "General", "kind": "radio-aria",
|
||||
"values": _GEN.THEME_VALUES},
|
||||
@@ -200,6 +203,9 @@ def get_toggle(node, name):
|
||||
def set_toggle(node, name, value):
|
||||
"""Set one toggle with readback. Never partially reports success."""
|
||||
spec = resolve_toggle(name)
|
||||
if spec.get("readonly"):
|
||||
raise MenuError("%s is read-only: the site ignores all input "
|
||||
"gestures there (locked?)" % name)
|
||||
want = _normalize_value(spec, value)
|
||||
_check_node(node)
|
||||
try:
|
||||
|
||||
Reference in New Issue
Block a user