feat(box): add invite code handler, Settings RPA, and agent onboarding pipeline
- Support invite code discovery in main chat and redemption in settings menu - Add Settings RPA primitives with Radix UI mouse dispatch and retry polling for async DOM - Attribute 'has_redeemed' binary from the Additional tokens ticker / entrypoint visibility - Unblock agent @646 by repairing warp-def/dev tunnels and redeeming REDCJ7 via dev (+1B tokens) - Implement 'box onboard' pipeline to provision infra, authenticate, and auto-redeem queued codes - Add 'box onboard feed-matrix' ranking all agents by work done over time, job count, and role - Register 'InputType.SALVAGE' in loop modulation (CRITICAL priority, 600s timeout, 3 nudges to opm) - Ingest recurring balance audits into canonical HEARTBEAT.md and TOOLS.md
This commit is contained in:
@@ -0,0 +1,151 @@
|
||||
"""Settings dialog navigation: open, tabs, rows, back, text.
|
||||
|
||||
Open is retried (single-shot opens flake ~1/4 live): each attempt
|
||||
re-Escapes and re-drives the dock menu from scratch. Tab clicks are
|
||||
idempotent (re-clicking the active tab is a harmless no-op), so goto
|
||||
always clicks and reports the click result instead of guessing which
|
||||
tab is active.
|
||||
"""
|
||||
import time
|
||||
|
||||
from approvals import cdp_evaluate
|
||||
from hatch_menu.mouse import escape, real_click
|
||||
|
||||
TAB_NAMES = ["General", "Connectors", "Wallet", "Secure store",
|
||||
"Permissions", "Messaging channels", "Devices",
|
||||
"Data controls", "Help & support", "Legal info"]
|
||||
TABS = TAB_NAMES # legacy alias
|
||||
|
||||
DOCK_MORE_TESTID = "hatch-dock-more"
|
||||
|
||||
JS_DOCK_RECT = ("(() => { const b = document.querySelector("
|
||||
"'[data-testid=\"hatch-dock-more\"]'); if (!b) return null;"
|
||||
" const r = b.getBoundingClientRect();"
|
||||
" return {x: r.x + r.width/2, y: r.y + r.height/2}; })()")
|
||||
|
||||
JS_CLICK_SETTINGS_ITEM = ("(() => { const it = Array.from(document."
|
||||
"querySelectorAll('[role=\"menuitem\"]')).find(el => el.getAttribute("
|
||||
"'data-pel-click') === 'settings_nav_click');"
|
||||
" if (!it) return 'NO_ITEM'; it.click(); return 'CLICKED'; })()")
|
||||
|
||||
JS_DIALOG_PRESENT = ("(() => !!document.querySelector('[role=\"dialog\"]'))()")
|
||||
|
||||
JS_DIALOG_TEXT = ("(() => { const d = document.querySelector("
|
||||
"'[role=\"dialog\"]'); return d ? d.innerText : null; })()")
|
||||
|
||||
JS_GOTO_TAB_TMPL = ("(() => { const b = Array.from(document."
|
||||
"querySelectorAll('[role=\"dialog\"] button')).find(x => "
|
||||
"(x.innerText||'').trim() === '%s');"
|
||||
" if (!b) return 'NO_TAB'; b.click(); return 'CLICKED'; })()")
|
||||
|
||||
JS_CLICK_ROW_TMPL = ("((name) => {"
|
||||
" const d = document.querySelector('[role=\"dialog\"]');"
|
||||
" if (!d) return 'NO_DIALOG';"
|
||||
" const inNav = (el) => !!el.closest("
|
||||
"'nav, [role=\"tablist\"], [role=\"navigation\"]');"
|
||||
" const els = Array.from(d.querySelectorAll("
|
||||
"'button, [role=\"button\"], a')).filter(e => !inNav(e));"
|
||||
" const t = els.find(e => (e.innerText || '').trim()"
|
||||
".toLowerCase().startsWith(name.toLowerCase()));"
|
||||
" if (!t) return 'NO_ROW'; t.click(); return 'CLICKED'; })('%s')")
|
||||
|
||||
JS_DESCRIBE_ROWS = ("(() => {"
|
||||
" const d = document.querySelector('[role=\"dialog\"]');"
|
||||
" if (!d) return null;"
|
||||
" const inNav = (el) => !!el.closest("
|
||||
"'nav, [role=\"tablist\"], [role=\"navigation\"]');"
|
||||
" return Array.from(d.querySelectorAll("
|
||||
"'button, [role=\"button\"], a')).filter(e => !inNav(e))"
|
||||
".map(e => { const lines = (e.innerText || '').trim().split('\\n');"
|
||||
" return {name: (lines[0] || '').slice(0, 80),"
|
||||
" detail: lines.slice(1).join(' / ').slice(0, 120)}; }); })()")
|
||||
|
||||
JS_GO_BACK = ("(() => { const d = document.querySelector("
|
||||
"'[role=\"dialog\"]'); if (!d) return 'NO_DIALOG';"
|
||||
" const b = Array.from(d.querySelectorAll('button')).find("
|
||||
"x => (x.getAttribute('aria-label') || '') === 'Go back');"
|
||||
" if (!b) return 'NO_BACK'; b.click(); return 'CLICKED'; })()")
|
||||
|
||||
|
||||
def _eval(ws, js, timeout=8.0):
|
||||
try:
|
||||
return cdp_evaluate(ws, js, timeout=timeout)
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
|
||||
def dialog_present(ws):
|
||||
"""True when a dialog is currently open."""
|
||||
return bool(_eval(ws, JS_DIALOG_PRESENT, timeout=5.0))
|
||||
|
||||
|
||||
def dialog_text(ws, timeout=8.0, limit=4000):
|
||||
"""Inner text of the open dialog, or None."""
|
||||
try:
|
||||
text = cdp_evaluate(ws, JS_DIALOG_TEXT, timeout=timeout)
|
||||
except Exception:
|
||||
return None
|
||||
if not text:
|
||||
return None
|
||||
return text[:limit]
|
||||
|
||||
|
||||
def open_settings(ws, tries=3):
|
||||
"""Open the Settings dialog via the dock menu. True when open."""
|
||||
for _ in range(tries):
|
||||
escape(ws)
|
||||
time.sleep(0.5)
|
||||
rect = _eval(ws, JS_DOCK_RECT, timeout=5.0)
|
||||
if not rect or "x" not in rect:
|
||||
continue
|
||||
try:
|
||||
real_click(ws, rect["x"], rect["y"])
|
||||
except Exception:
|
||||
continue
|
||||
time.sleep(1.0)
|
||||
if _eval(ws, JS_CLICK_SETTINGS_ITEM,
|
||||
timeout=5.0) != "CLICKED":
|
||||
continue
|
||||
time.sleep(1.5)
|
||||
if dialog_present(ws):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def goto_tab(ws, name, timeout=5.0):
|
||||
"""Click a Settings tab by visible name. True when clicked."""
|
||||
if _eval(ws, JS_GOTO_TAB_TMPL % name, timeout=timeout) != "CLICKED":
|
||||
return False
|
||||
time.sleep(0.8)
|
||||
return True
|
||||
|
||||
|
||||
def click_row(ws, name, tab=None, timeout=8.0):
|
||||
"""Click a content row; verify the drill/expand opened. Bool."""
|
||||
if tab is not None and not goto_tab(ws, tab, timeout=timeout):
|
||||
return False
|
||||
if _eval(ws, JS_CLICK_ROW_TMPL % name, timeout=timeout) != "CLICKED":
|
||||
return False
|
||||
time.sleep(1.0)
|
||||
text = dialog_text(ws, timeout=timeout) or ""
|
||||
return name.lower() in text.lower()
|
||||
|
||||
|
||||
def describe_rows(ws, tab=None, timeout=8.0):
|
||||
"""Inventory rows (name/detail) on a tab. [] when unreadable."""
|
||||
if tab is not None and not goto_tab(ws, tab, timeout=timeout):
|
||||
return []
|
||||
return _eval(ws, JS_DESCRIBE_ROWS, timeout=timeout) or []
|
||||
|
||||
|
||||
def go_back(ws):
|
||||
"""Click the sub-page Go back button. True when clicked."""
|
||||
if _eval(ws, JS_GO_BACK, timeout=5.0) != "CLICKED":
|
||||
return False
|
||||
time.sleep(0.8)
|
||||
return True
|
||||
|
||||
|
||||
def close_settings(ws):
|
||||
"""Dismiss settings/popovers. Never raises."""
|
||||
escape(ws)
|
||||
Reference in New Issue
Block a user