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:
operator
2026-10-06 19:33:17 +00:00
parent 4998ffddb6
commit b824f6d405
30 changed files with 4516 additions and 0 deletions
+443
View File
@@ -0,0 +1,443 @@
#!/usr/bin/env python3
"""settings_rpa.py: RPA module for Muse.ai Settings menu, dock rail, and dialogs.
Provides headless browser automation primitives for:
- Dock rail settings menu toggle via CDP mouse events
- Settings dialog navigation (General, Connectors, Wallet, etc.)
- Token and weekly quota usage inspection
- Settings-based invite code redemption entry point
Compatible with NetVM's unified box CLI and approvals system.
"""
from __future__ import annotations
import argparse
import itertools
import json
import re
import sys
import time
from dataclasses import asdict, dataclass
from typing import Any, Dict, List, Optional, Tuple
try:
from approvals import cdp_evaluate, get_cdp_ws, get_node_pages
except ImportError:
# Allow running when approvals is in current dir or NetVM bin
import os
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from approvals import cdp_evaluate, get_cdp_ws, get_node_pages
_REQ_COUNTER = itertools.count(10000)
@dataclass
class UsageBar:
label: str
percent: int
raw_text: str
@dataclass
class NodeUsage:
node: str
plan: str
weekly_reset_text: str
weekly_percent_used: int
extra_tokens_status: str
extra_percent_used: int
extra_tokens_remaining: str
is_blocked: bool
bars: List[Dict[str, Any]]
dialog_text: str
has_redeemed: bool = False
def to_dict(self) -> Dict[str, Any]:
return asdict(self)
def cdp_send_command(ws: Any, method: str, params: Dict[str, Any], timeout: float = 3.0) -> Optional[Dict[str, Any]]:
"""Send a raw CDP command and await response with a unique request ID."""
req_id = next(_REQ_COUNTER)
msg = {"id": req_id, "method": method, "params": params}
ws.send(json.dumps(msg))
deadline = time.time() + timeout
while time.time() < deadline:
try:
raw = ws.recv()
except Exception:
break
resp = json.loads(raw)
if resp.get("id") == req_id:
return resp.get("result", {})
return None
def cdp_dispatch_mouse_click(ws: Any, x: float, y: float, timeout: float = 3.0) -> bool:
"""Simulate real mousePressed and mouseReleased events at (x, y)."""
p1 = cdp_send_command(
ws,
"Input.dispatchMouseEvent",
{"type": "mousePressed", "x": x, "y": y, "button": "left", "clickCount": 1},
timeout=timeout,
)
p2 = cdp_send_command(
ws,
"Input.dispatchMouseEvent",
{"type": "mouseReleased", "x": x, "y": y, "button": "left", "clickCount": 1},
timeout=timeout,
)
return p1 is not None and p2 is not None
def cdp_click_element_by_selector(ws: Any, selector: str, timeout: float = 3.0) -> bool:
"""Find element by selector, calculate bounding box center, and dispatch real mouse click."""
js_rect = f"""(() => {{
const el = document.querySelector({json.dumps(selector)});
if (!el) return null;
const r = el.getBoundingClientRect();
if (r.width === 0 || r.height === 0) return null;
return {{x: r.x + r.width / 2, y: r.y + r.height / 2}};
}})()"""
rect = cdp_evaluate(ws, js_rect, timeout=timeout)
if not rect or "x" not in rect:
return False
return cdp_dispatch_mouse_click(ws, float(rect["x"]), float(rect["y"]), timeout=timeout)
def cdp_send_escape(ws: Any) -> None:
"""Send an Escape keydown event to dismiss popovers and modals."""
js_escape = """(() => {
document.dispatchEvent(new KeyboardEvent('keydown', {
key: 'Escape',
code: 'Escape',
keyCode: 27,
which: 27,
bubbles: true,
cancelable: true
}));
})()"""
cdp_evaluate(ws, js_escape)
class SettingsRPA:
"""Automates Settings menu, dialogs, and usage inspection for a NetVM node."""
def __init__(self, node: str, timeout: float = 4.0):
self.node = node
self.timeout = timeout
self.ws = None
self.page = None
def connect(self) -> SettingsRPA:
if self.ws is None:
self.ws, self.page = get_cdp_ws(self.node, timeout=self.timeout)
return self
def close(self) -> None:
if self.ws is not None:
try:
self.ws.close()
except Exception:
pass
self.ws = None
def __enter__(self) -> SettingsRPA:
return self.connect()
def __exit__(self, exc_type, exc_val, exc_tb) -> None:
self.close()
def ensure_main_chat(self) -> bool:
"""Ensure agent is in main chat home view so dock and main toolbar are active."""
self.connect()
js_is_main = """(() => {
return Boolean(document.querySelector('[data-testid="hatch-invite-friends-button"]'));
})()"""
if cdp_evaluate(self.ws, js_is_main):
return True
# Click the chat dock item to return to main
cdp_evaluate(
self.ws,
"""(() => {
const chatLink = document.querySelector('[data-testid="hatch-nav-chat"]');
if (chatLink) { chatLink.click(); return true; }
return false;
})()""",
)
time.sleep(1.0)
return bool(cdp_evaluate(self.ws, js_is_main))
def is_menu_open(self) -> bool:
"""Check if dock radix menu is currently open."""
self.connect()
js_check = """(() => {
return document.querySelectorAll('[role="menuitem"]').length > 0;
})()"""
return bool(cdp_evaluate(self.ws, js_check))
def is_settings_dialog_open(self) -> bool:
"""Check if Settings dialog is currently open."""
self.connect()
js_check = """(() => {
const d = document.querySelector('[role="dialog"]');
if (!d) return false;
const h = d.querySelector('h2');
return h && h.innerText.trim().toLowerCase() === 'settings';
})()"""
return bool(cdp_evaluate(self.ws, js_check))
def open_dock_menu(self) -> bool:
"""Open the dock settings menu via real mouse clicks on hatch-dock-more."""
self.connect()
if self.is_menu_open():
return True
# Dispatch real mouse click on dock button
clicked = cdp_click_element_by_selector(self.ws, '[data-testid="hatch-dock-more"]', timeout=self.timeout)
if not clicked:
return False
deadline = time.time() + 2.0
while time.time() < deadline:
if self.is_menu_open():
return True
time.sleep(0.1)
return False
def close_dock_menu(self) -> None:
"""Dismiss dock menu."""
if self.is_menu_open():
cdp_send_escape(self.ws)
time.sleep(0.2)
def open_settings_dialog(self) -> bool:
"""Open the Settings dialog through the dock settings menu."""
self.connect()
if self.is_settings_dialog_open():
return True
# Ensure menu is open
if not self.open_dock_menu():
return False
time.sleep(0.3)
# Click "Settings" menu item
js_click_item = """(() => {
const items = Array.from(document.querySelectorAll('[role="menuitem"]'));
const target = items.find(el => el.getAttribute('data-pel-click') === 'settings_nav_click') ||
items.find(el => (el.innerText || '').trim() === 'Settings');
if (target) {
target.click();
return true;
}
return false;
})()"""
clicked = cdp_evaluate(self.ws, js_click_item)
if not clicked:
return False
deadline = time.time() + 3.0
while time.time() < deadline:
if self.is_settings_dialog_open():
return True
time.sleep(0.15)
return False
def close_settings_dialog(self) -> None:
"""Dismiss Settings dialog with Escape or Close button."""
if self.is_settings_dialog_open():
cdp_send_escape(self.ws)
time.sleep(0.3)
# If still open, try close button
if self.is_settings_dialog_open():
cdp_evaluate(
self.ws,
"""(() => {
const btn = Array.from(document.querySelectorAll('[role="dialog"] button')).find(b => (b.innerText||'').trim() === 'Close');
if (btn) btn.click();
})()""",
)
time.sleep(0.2)
def select_tab(self, tab_name: str) -> bool:
"""Select a section tab inside the Settings dialog."""
self.connect()
if not self.open_settings_dialog():
return False
js_tab = f"""(() => {{
const btns = Array.from(document.querySelectorAll('[role="dialog"] button'));
const tab = btns.find(b => (b.innerText||'').trim().toLowerCase() === {json.dumps(tab_name.lower())});
if (tab) {{
tab.click();
return true;
}}
return false;
}})()"""
res = cdp_evaluate(self.ws, js_tab)
if res:
time.sleep(0.4)
return True
return False
def read_usage(self, keep_dialog_open: bool = False) -> NodeUsage:
"""Open settings dialog, read token and weekly limit usage, and optionally close dialog."""
self.connect()
opened = self.open_settings_dialog()
if not opened:
raise RuntimeError(f"Could not open Settings dialog for node {self.node}")
# Ensure General tab is active
self.select_tab("General")
time.sleep(0.4)
js_usage = """(() => {
const dialog = document.querySelector('[role="dialog"]');
if (!dialog) return null;
const fullText = dialog.innerText || '';
const bars = Array.from(dialog.querySelectorAll('[role="progressbar"]')).map(b => ({
label: b.getAttribute('aria-label') || '',
percent: parseInt(b.getAttribute('aria-valuenow') || '0', 10),
raw_text: b.parentElement ? b.parentElement.innerText.trim() : ''
}));
// Extract plan
let plan = 'Free plan';
if (fullText.includes('Free plan')) plan = 'Free plan';
else if (fullText.includes('Pro plan')) plan = 'Pro plan';
// Extract reset text
let resetText = '';
const mReset = fullText.match(/Weekly limit resets on [^\\n]+/);
if (mReset) resetText = mReset[0];
// Extract tokens left
let tokensLeft = '';
const mLeft = fullText.match(/\\(([^\\)]+tokens left)\\)/);
if (mLeft) tokensLeft = mLeft[1];
else if (fullText.includes('0 tokens left')) tokensLeft = '0 tokens left';
return {
dialogText: fullText,
bars: bars,
plan: plan,
resetText: resetText,
tokensLeft: tokensLeft
};
})()"""
# Poll up to 4.0s for usage elements to render in React
deadline = time.time() + 4.0
raw = None
while time.time() < deadline:
res = cdp_evaluate(self.ws, js_usage)
if res and (res.get("bars") or "Weekly limit" in res.get("dialogText", "") or "Additional tokens" in res.get("dialogText", "")):
raw = res
break
time.sleep(0.3)
if not raw:
raw = cdp_evaluate(self.ws, js_usage)
if not keep_dialog_open:
self.close_settings_dialog()
if not raw:
raise RuntimeError(f"Failed to read usage data from Settings dialog on {self.node}")
bars = raw.get("bars", [])
weekly_pct = 0
extra_pct = 0
extra_status = "Never expires"
for b in bars:
label = b.get("label", "").lower()
detail = b.get("raw_text", "").lower()
if "free plan" in label or "weekly" in detail:
weekly_pct = b.get("percent", 0)
elif "additional tokens" in label or "additional" in detail:
extra_pct = b.get("percent", 0)
# Blocked condition: weekly limit 100% and additional tokens 100% or 0 tokens left
tokens_left = raw.get("tokensLeft", "")
is_blocked = (weekly_pct >= 100 and extra_pct >= 100) or ("0 tokens left" in tokens_left)
# has_redeemed binary: If the "Additional tokens" ticker is present in Settings (or "Redeem invite code" entry is absent),
# the agent has already redeemed an invite code.
has_extra_ticker = any("additional tokens" in b.get("label", "").lower() or "additional" in b.get("raw_text", "").lower() for b in bars)
has_redeemed = has_extra_ticker or ("Additional tokens" in raw.get("dialogText", ""))
return NodeUsage(
node=self.node,
plan=raw.get("plan", "Free plan"),
weekly_reset_text=raw.get("resetText", ""),
weekly_percent_used=weekly_pct,
extra_tokens_status=extra_status,
extra_percent_used=extra_pct,
extra_tokens_remaining=tokens_left or ("0 tokens left" if extra_pct >= 100 else "Unknown"),
is_blocked=is_blocked,
bars=bars,
dialog_text=raw.get("dialogText", ""),
has_redeemed=has_redeemed,
)
def check_redeem_entrypoint(self) -> Dict[str, Any]:
"""Check if 'Redeem invite code' option is available in Settings -> General."""
self.connect()
if not self.open_settings_dialog():
return {"available": False, "reason": "could_not_open_settings"}
self.select_tab("General")
time.sleep(0.3)
js_check = """(() => {
const dialog = document.querySelector('[role="dialog"]');
if (!dialog) return {available: false, reason: 'no_dialog'};
const items = Array.from(dialog.querySelectorAll('button, div, span'));
const redeemItem = items.find(el => (el.innerText || '').trim() === 'Redeem invite code');
return {
available: Boolean(redeemItem),
has_text: dialog.innerText.includes('Redeem invite code')
};
})()"""
res = cdp_evaluate(self.ws, js_check)
self.close_settings_dialog()
return res or {"available": False, "reason": "evaluation_failed"}
def main() -> None:
parser = argparse.ArgumentParser(description="Settings menu RPA for NetVM agents")
parser.add_argument("node", help="Node name (e.g. 646, pip, muse, opm)")
parser.add_argument("--usage", action="store_true", help="Read token and weekly quota usage")
parser.add_argument("--json", action="store_true", help="Emit JSON output")
parser.add_argument("--check-redeem", action="store_true", help="Check if Redeem Invite Code is available")
args = parser.parse_args()
rpa = SettingsRPA(args.node)
try:
rpa.connect()
if args.usage:
usage = rpa.read_usage()
if args.json:
print(json.dumps(usage.to_dict(), indent=2))
else:
status_str = "BLOCKED (LIMIT REACHED)" if usage.is_blocked else "ACTIVE"
print(f"=== Node {usage.node} Usage ===")
print(f" Plan: {usage.plan}")
print(f" Weekly Reset: {usage.weekly_reset_text} ({usage.weekly_percent_used}% used)")
print(f" Extra Tokens: {usage.extra_tokens_remaining} ({usage.extra_percent_used}% used)")
print(f" Status: {status_str}")
elif args.check_redeem:
info = rpa.check_redeem_entrypoint()
print(json.dumps(info, indent=2))
else:
usage = rpa.read_usage()
print(json.dumps(usage.to_dict(), indent=2))
finally:
rpa.close()
if __name__ == "__main__":
main()