#!/usr/bin/env python3 # GOLDEN PATH: container -> VM (34.139.37.135) -> bl (100.123.153.75) -> netns -> browser -> agent # This API is the bridge. Every call traverses 4 hops. Respect the path. """ Multi-account muse.ai chat API with approval handling. Approvals: The browser may show permission dialogs (e.g., "Allow pip to share information with 34.139.37.135?"). The API detects these and handles them: - Known-safe (our infrastructure IPs): auto-approve - Unknown: raise APPROVAL_NEEDED, operator decides via chat Usage: muse-chat-api.py --account send "message" muse-chat-api.py --account messages [n] muse-chat-api.py --account wait [timeout] muse-chat-api.py --account approvals # check pending approvals """ import json, urllib.request, websocket, time, sys, argparse ACCOUNTS = { "muse": ("muse", "http://127.0.0.1:9410/json/list"), "pip": ("pip", "http://127.0.0.1:9420/json/list"), "646": ("646", "http://127.0.0.1:9430/json/list"), "646": ("646", "http://10.201.202.2:9353/json/list"), } # IPs we trust for auto-approval (our infrastructure) TRUSTED_IPS = { "34.139.37.135", # VM (gateway) "100.123.153.75", # bl (main compute) "100.81.31.9", # VM tailnet } def get_page(node, cdp_url): with urllib.request.urlopen(cdp_url, timeout=5) as r: ts = json.load(r) pages = [t for t in ts if t.get('type') == 'page'] if not pages: print("ERROR: No page found", file=sys.stderr) sys.exit(1) return pages[0] def ev(ws, expr, await_p=False): ws.send(json.dumps({ "id": 1, "method": "Runtime.evaluate", "params": {"expression": expr, "returnByValue": True, "awaitPromise": await_p} })) resp = json.loads(ws.recv()) return resp.get('result', {}).get('result', {}).get('value') def check_approvals(ws): """ Check for browser permission dialogs. Returns list of (dialog_text, is_trusted, action_taken). """ result = ev(ws, """(() => { const dialogs = []; // Look for permission prompts (common patterns) const body = document.body.innerText; // Check for "Allow ... to share" pattern if (body.includes('Allow') && body.includes('to share')) { // Find the dialog const els = [...document.querySelectorAll('*')].filter(el => { const t = el.innerText || ''; return t.includes('Allow') && t.includes('to share') && t.length < 500; }); for (const el of els.slice(0,3)) { dialogs.push(el.innerText.slice(0,200)); } } // Check for other permission patterns const perm_btns = [...document.querySelectorAll('button')].filter(b => { const t = (b.innerText||'').toLowerCase(); return t.includes('allow') || t.includes('deny') || t.includes('block'); }); if (perm_btns.length >= 2 && dialogs.length === 0) { // Might be a permission dialog const parent = perm_btns[0].closest('div'); if (parent) dialogs.push(parent.innerText.slice(0,200)); } return JSON.stringify(dialogs); })()""") try: dialogs = json.loads(result) if result else [] except: dialogs = [] actions = [] for d in dialogs: # Extract IP if present import re ips = re.findall(r'\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b', d) is_trusted = any(ip in TRUSTED_IPS for ip in ips) if is_trusted: # Auto-approve: click "Allow once" or "Allow" clicked = ev(ws, """(async()=>{ const b = [...document.querySelectorAll('button')].find(x=>{ const t = (x.innerText||'').toLowerCase(); return t.includes('allow once') || t === 'allow'; }); if (b) { b.click(); return 'clicked:'+b.innerText.slice(0,20); } return 'NOTFOUND'; })()""", True) actions.append((d[:80], True, clicked)) else: actions.append((d[:80], False, "APPROVAL_NEEDED")) return actions def cmd_approvals(ws): """Check and handle pending approvals.""" actions = check_approvals(ws) if not actions: print("No pending approvals") return for dialog, trusted, action in actions: print(f"Dialog: {dialog}") print(f" Trusted: {trusted}, Action: {action}") if not trusted: print(" APPROVAL_NEEDED: Manual review required") sys.exit(2) def cmd_send(ws, message): # Check approvals first actions = check_approvals(ws) for dialog, trusted, action in actions: if not trusted: print(f"APPROVAL_NEEDED: {dialog[:80]}", file=sys.stderr) sys.exit(2) msg_esc = message.replace('\\', '\\\\').replace('`', '\\`').replace('$', '\\$') result = ev(ws, f"""(async()=>{{ const input = document.querySelector('[contenteditable="true"]') || document.querySelector('textarea[placeholder*="Message"]') || [...document.querySelectorAll('div[role="textbox"]')][0]; if (!input) return 'NOINPUT'; input.focus(); document.execCommand('insertText', false, `{msg_esc}`); await new Promise(r=>setTimeout(r,500)); const send = [...document.querySelectorAll('button')].find(b=> b.getAttribute('aria-label')&&b.getAttribute('aria-label').toLowerCase().includes('send') ); if (send) {{ send.click(); return 'sent'; }} const ke = new KeyboardEvent('keydown', {{key:'Enter', code:'Enter', bubbles:true}}); input.dispatchEvent(ke); return 'enter-sent'; }})()""", True) print(result) def cmd_messages(ws, n=5): # Check approvals first (non-blocking) check_approvals(ws) result = ev(ws, f"""(() => {{ const ps = [...document.querySelectorAll('p')].slice(-{n*2}).map(p=>p.innerText.slice(0,200)); return ps.join('\\n---\\n'); }})()""") print(result) def cmd_wait(ws, timeout=30): print(f"Waiting {timeout}s for response...") # Check approvals periodically during wait for i in range(timeout // 5): actions = check_approvals(ws) for dialog, trusted, action in actions: if not trusted: print(f"APPROVAL_NEEDED: {dialog[:80]}", file=sys.stderr) sys.exit(2) time.sleep(5) cmd_messages(ws, 2) def main(): p = argparse.ArgumentParser() p.add_argument('--account', required=True, choices=list(ACCOUNTS.keys()), help='Agent name (matches node, profile, ACCOUNTS.md)') p.add_argument('command', choices=['send', 'messages', 'wait', 'approvals']) p.add_argument('arg', nargs='?', default=None) args = p.parse_args() node, cdp_url = ACCOUNTS[args.account] page = get_page(node, cdp_url) ws = websocket.create_connection(page['webSocketDebuggerUrl'], timeout=15) try: if args.command == 'send': if not args.arg: print("ERROR: send requires a message", file=sys.stderr) sys.exit(1) cmd_send(ws, args.arg) elif args.command == 'messages': n = int(args.arg) if args.arg else 5 cmd_messages(ws, n) elif args.command == 'wait': t = int(args.arg) if args.arg else 30 cmd_wait(ws, t) elif args.command == 'approvals': cmd_approvals(ws) finally: ws.close() if __name__ == '__main__': main()