From 08f05263e6af9c1248a5a41ba2a0648715478eb4 Mon Sep 17 00:00:00 2001 From: operator Date: Sat, 3 Oct 2026 21:36:42 +0000 Subject: [PATCH] dm.py: Add UUID, logging, and read-back verification. No trust required. --- bin/dm.py | 122 +++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 89 insertions(+), 33 deletions(-) diff --git a/bin/dm.py b/bin/dm.py index 171d27a..32e4f7f 100755 --- a/bin/dm.py +++ b/bin/dm.py @@ -1,38 +1,52 @@ #!/usr/bin/env python3 """ -DM: Headless Direct Message API (muse.ai) +DM: Headless Direct Message API (muse.ai) — with logging and verification. -Distinct from Chat (front-door, externally hosted). -DM operates via headless browser automation on muse.ai. -Chat operates via signed HTTPS API to chat.muse-dev.online. - -This separation increases headless reliability by isolating -browser automation from network API calls. +Every send generates a UUID, logs to dm-log.jsonl, and verifies via read-back. +No trust required: success means the UUID was found in the chat. Usage: dm.py send --agent 646 --target "message" dm.py read --agent 646 --target [n] - dm.py thread --from 646 --to muse --target "message" + dm.py verify --agent 646 --target + dm.py log [--n 20] """ import argparse import subprocess import time import sys +import uuid +import json +import os +from datetime import datetime, timezone API = "/home/super/Projects/NetVM/bin/muse-chat-api.py" NETVM_EXEC = "/home/super/Projects/NetVM/bin/netvm-exec.sh" VALID_AGENTS = ["muse", "pip", "646", "opm"] +LOG_FILE = "/home/super/Projects/NetVM/dm-log.jsonl" + +def log_event(event): + """Append to JSONL log.""" + event["ts"] = datetime.now(timezone.utc).isoformat() + with open(LOG_FILE, "a") as f: + f.write(json.dumps(event) + "\n") def run(cmd, timeout=60): result = subprocess.run(cmd, shell=True, capture_output=True, text=True, timeout=timeout) return result.stdout.strip() -def dm_send(agent, target, message): - """Send a DM via headless to agent's target (main or side chat).""" +def dm_send(agent, target, message, verify=True): + """Send a DM with UUID and verification.""" if agent not in VALID_AGENTS: print(f"ERROR: Unknown agent {agent}", file=sys.stderr) sys.exit(1) + msg_id = str(uuid.uuid4())[:8] + # Embed ID in message for verification + tagged = f"[{msg_id}] {message}" + + log_event({"type": "send_start", "id": msg_id, "agent": agent, "target": target, "msg": message[:100]}) + # Navigate to target if target == "main": run(f"{NETVM_EXEC} {agent} -- python3 {API} --account {agent} sidechat main") @@ -42,17 +56,32 @@ def dm_send(agent, target, message): time.sleep(2) # Send - safe = message.replace('"', '\\"').replace('$', '\\$').replace('`', '\\`')[:1000] - result = run(f'{NETVM_EXEC} {agent} -- python3 {API} --account {agent} send "{safe}"') + safe = tagged.replace('"', '\\"').replace('$', '\\$').replace('`', '\\`')[:1000] + run(f'{NETVM_EXEC} {agent} -- python3 {API} --account {agent} send "{safe}"') # Back to main run(f"{NETVM_EXEC} {agent} -- python3 {API} --account {agent} sidechat main") - print(f"DM sent to {agent}/{target}") - return result + log_event({"type": "send_done", "id": msg_id, "agent": agent, "target": target}) + + if verify: + time.sleep(3) + # Read back and check for our ID + msgs = dm_read(agent, target, n=5, quiet=True) + if msg_id in msgs: + log_event({"type": "verified", "id": msg_id, "agent": agent, "target": target}) + print(f"DM {msg_id} to {agent}/{target}: VERIFIED") + return msg_id + else: + log_event({"type": "verify_failed", "id": msg_id, "agent": agent, "target": target}) + print(f"DM {msg_id} to {agent}/{target}: FAILED (not found in read-back)", file=sys.stderr) + sys.exit(1) + else: + print(f"DM {msg_id} to {agent}/{target}: SENT (unverified)") + return msg_id -def dm_read(agent, target, n=5): - """Read DMs via headless from agent's target.""" +def dm_read(agent, target, n=5, quiet=False): + """Read DMs via headless.""" if agent not in VALID_AGENTS: print(f"ERROR: Unknown agent {agent}", file=sys.stderr) sys.exit(1) @@ -66,40 +95,67 @@ def dm_read(agent, target, n=5): msgs = run(f"{NETVM_EXEC} {agent} -- python3 {API} --account {agent} messages {n}") run(f"{NETVM_EXEC} {agent} -- python3 {API} --account {agent} sidechat main") - print(msgs) + if not quiet: + print(msgs) return msgs +def dm_verify(agent, target, msg_id): + """Check if a message ID exists in the chat.""" + msgs = dm_read(agent, target, n=10, quiet=True) + if msg_id in msgs: + print(f"VERIFIED: {msg_id} found in {agent}/{target}") + return True + else: + print(f"NOT FOUND: {msg_id} in {agent}/{target}", file=sys.stderr) + sys.exit(1) + +def dm_log(n=20): + """Show recent log entries.""" + if not os.path.exists(LOG_FILE): + print("No log file yet") + return + with open(LOG_FILE) as f: + lines = f.readlines() + for line in lines[-n:]: + e = json.loads(line) + print(f"{e['ts'][:19]} {e['type']:12} {e.get('id','-'):8} {e.get('agent','-')}/{e.get('target','-')}") + def dm_thread(from_agent, to_agent, target, message): - """Thread a message from one agent into another's chat (via operator).""" - # Read from source (optional, for context) - # Send to target with attribution attributed = f"[{from_agent}] {message}" return dm_send(to_agent, target, attributed) def main(): - p = argparse.ArgumentParser(description="DM: Headless Direct Messages (muse.ai)") + p = argparse.ArgumentParser(description="DM: Headless Direct Messages with verification") sub = p.add_subparsers(dest='cmd', required=True) - # dm send - ps = sub.add_parser('send', help='Send a DM via headless') + ps = sub.add_parser('send', help='Send a DM with verification') ps.add_argument('--agent', required=True, choices=VALID_AGENTS) - ps.add_argument('--target', required=True, help='main or chat_id') - ps.add_argument('message', help='Message to send') - ps.set_defaults(func=lambda a: dm_send(a.agent, a.target, a.message)) + ps.add_argument('--target', required=True) + ps.add_argument('--no-verify', action='store_true', help='Skip verification') + ps.add_argument('message') + ps.set_defaults(func=lambda a: dm_send(a.agent, a.target, a.message, verify=not a.no_verify)) - # dm read - pr = sub.add_parser('read', help='Read DMs via headless') + pr = sub.add_parser('read', help='Read DMs') pr.add_argument('--agent', required=True, choices=VALID_AGENTS) - pr.add_argument('--target', required=True, help='main or chat_id') - pr.add_argument('--n', type=int, default=5, help='Number of messages') + pr.add_argument('--target', required=True) + pr.add_argument('--n', type=int, default=5) pr.set_defaults(func=lambda a: dm_read(a.agent, a.target, a.n)) - # dm thread + pv = sub.add_parser('verify', help='Verify a message ID exists') + pv.add_argument('--agent', required=True, choices=VALID_AGENTS) + pv.add_argument('--target', required=True) + pv.add_argument('msg_id') + pv.set_defaults(func=lambda a: dm_verify(a.agent, a.target, a.msg_id)) + + pl = sub.add_parser('log', help='Show DM log') + pl.add_argument('--n', type=int, default=20) + pl.set_defaults(func=lambda a: dm_log(a.n)) + pt = sub.add_parser('thread', help='Thread from one agent to another') pt.add_argument('--from', dest='from_agent', required=True, choices=VALID_AGENTS) pt.add_argument('--to', dest='to_agent', required=True, choices=VALID_AGENTS) - pt.add_argument('--target', required=True, help='main or chat_id on target') - pt.add_argument('message', help='Message to thread') + pt.add_argument('--target', required=True) + pt.add_argument('message') pt.set_defaults(func=lambda a: dm_thread(a.from_agent, a.to_agent, a.target, a.message)) args = p.parse_args()