From 34082045cca935a39d16506bdb6a5222e56a9469 Mon Sep 17 00:00:00 2001 From: operator-main Date: Sun, 4 Oct 2026 03:23:34 +0000 Subject: [PATCH] Nail sidechat create: direct send, no ID lookup\n\n- cmd_sidechat_create: simplified, just create and return\n- job-dispatch: after create, send via direct API to current chat\n- No thread ID parsing needed; thread lookup via URL for later ops --- bin/job-dispatch.py | 101 ++++++++++++++++++++++++++++--------------- bin/muse-chat-api.py | 22 ++-------- 2 files changed, 69 insertions(+), 54 deletions(-) diff --git a/bin/job-dispatch.py b/bin/job-dispatch.py index a8caef9..788b9cf 100755 --- a/bin/job-dispatch.py +++ b/bin/job-dispatch.py @@ -96,25 +96,46 @@ def send_dm(agent, target, message, dry_run=False): def create_sidechat(sender_agent, dry_run=False): """Create a sidechat via muse-chat-api.py in sender's context. - Returns the thread URL or None on failure.""" + Returns True on success (browser now on new sidechat), False on failure.""" if dry_run: print(f"[DRY RUN] Would create sidechat for {sender_agent}") - return "https://muse.ai/thread/dry-run-uuid" + return True cmd = [NETVM_EXEC, sender_agent, "--", "python3", str(CHAT_API), "--account", sender_agent, "sidechat", "create"] try: - result = subprocess.run(cmd, capture_output=True, text=True, timeout=60) - output = result.stdout.strip() + result.stderr.strip() - # Look for "Created: " - for line in output.split("\n"): - if "Created:" in line: - url = line.split("Created:")[1].strip() - return url - print(f"Sidechat create output: {output[:200]}", file=sys.stderr) - return None + result = subprocess.run(cmd, capture_output=True, text=True, timeout=90) + output = result.stdout.strip() + # Success if we see "Created:" (URL may be /thread/new placeholder) + if "Created:" in output: + print(f"Sidechat created", file=sys.stderr) + return True + print(f"Sidechat create failed: {output[:200]}", file=sys.stderr) + return False except Exception as e: print(f"Sidechat creation failed: {e}", file=sys.stderr) + return False + +def send_to_current_chat(sender_agent, message, dry_run=False): + """Send message to current chat via muse-chat-api.py (no navigation). + Used after sidechat create - browser is already on the new chat.""" + if dry_run: + print(f"[DRY RUN] Would send to current chat: {message[:100]}...") + return "dry-run-id" + + if HAS_RATE_LIMITER: + rate_limit_wait(sender_agent) + + cmd = [NETVM_EXEC, sender_agent, "--", "python3", str(CHAT_API), + "--account", sender_agent, "send", message] + try: + result = subprocess.run(cmd, capture_output=True, text=True, timeout=60) + if result.returncode == 0: + return "sent-to-sidechat" + print(f"Send failed: {result.stderr[:200]}", file=sys.stderr) + return None + except Exception as e: + print(f"Send failed: {e}", file=sys.stderr) return None def main(): @@ -155,30 +176,24 @@ def main(): sidechat_cfg = job.get("sidechat", {}) sidechat_url = None - if sidechat_cfg.get("create", False): - # Create sidechat in sender's (opm's) context - # Then send the job DM DIRECTLY to the sidechat, not main. - # This keeps main chat clean; the sidechat IS the job workspace. + use_sidechat = sidechat_cfg.get("create", False) + sidechat_created = False + + if use_sidechat: + # Create sidechat in sender's (opm's) context. + # Browser will be on the new sidechat; we send directly to it. + # No thread ID lookup needed - send_to_current_chat uses current page. print(f"Creating sidechat for job {job_id}...", file=sys.stderr) - sidechat_url = create_sidechat("opm", dry_run=dry_run) - if sidechat_url: - # Extract thread ID from URL (https://muse.ai/thread/) - m = re.search(r"/thread/([a-f0-9-]+)", sidechat_url) - if m: - thread_id = m.group(1) - target = thread_id # DM goes directly to sidechat - name_tmpl = sidechat_cfg.get("name_template", "job-{job_name}-{date}") - sc_name = render_prompt(name_tmpl, variables) - log_event("job_sidechat_created", { - "job_id": job_id, - "sidechat_url": sidechat_url, - "sidechat_name": sc_name, - "thread_id": thread_id, - }) - print(f"Sidechat created: {sc_name} ({thread_id})", file=sys.stderr) - else: - print(f"Warning: Could not parse thread ID from {sidechat_url}", file=sys.stderr) - target = "main" + sidechat_created = create_sidechat("opm", dry_run=dry_run) + if sidechat_created: + name_tmpl = sidechat_cfg.get("name_template", "job-{job_name}-{date}") + sc_name = render_prompt(name_tmpl, variables) + log_event("job_sidechat_created", { + "job_id": job_id, + "sidechat_name": sc_name, + }) + print(f"Sidechat created: {sc_name}", file=sys.stderr) + target = "sidechat" # marker, not used for dm.py else: print(f"Warning: Failed to create sidechat, falling back to main", file=sys.stderr) target = "main" @@ -194,8 +209,22 @@ def main(): "dry_run": dry_run, }) - # Send DM - msg_id = send_dm(agent, target, dm_message, dry_run=dry_run) + # Send DM: to sidechat via direct API, or to main via dm.py + if use_sidechat and sidechat_created: + msg_id = send_to_current_chat("opm", dm_message, dry_run=dry_run) + # Log as dispatched (no dm.py ID, but sent) + if msg_id and not dry_run: + print(f"Dispatched job {job_id} to sidechat (direct send)") + log_event("job_dispatched", { + "job_id": job_id, + "target": "sidechat", + "method": "direct", + }) + # Skip the dm.py dispatch block below + import sys as _sys2 + _sys2.exit(0) + else: + msg_id = send_dm(agent, target, dm_message, dry_run=dry_run) if msg_id and not dry_run: print(f"Dispatched job {job_id} to {agent} (DM: {msg_id})") diff --git a/bin/muse-chat-api.py b/bin/muse-chat-api.py index 36dffc8..65b9e86 100755 --- a/bin/muse-chat-api.py +++ b/bin/muse-chat-api.py @@ -326,24 +326,10 @@ def cmd_sidechat_create(ws): if not url or "/thread/" not in url: print(f"ERROR: Sidechat creation did not navigate to thread URL (got: {url})", file=sys.stderr) sys.exit(1) - # If we got /thread/new (placeholder), the real ID is assigned after - # the first message. Send a system message to trigger ID assignment, - # then re-read the URL. - if url.endswith("/thread/new"): - # Use proven cmd_send to trigger real thread ID - try: - cmd_send(ws, "[System: Side chat created]") - except Exception as e: - print(f"Warning: initial send failed: {e}", file=sys.stderr) - # Poll for URL to change from /thread/new to /thread/ - for i in range(15): - _time.sleep(1) - url = ev(ws, "window.location.href") - if url and "/thread/" in url and not url.endswith("/thread/new"): - break - if not url or "/thread/" not in url or url.endswith("/thread/new"): - print(f"ERROR: Could not get real thread ID (got: {url})", file=sys.stderr) - sys.exit(1) + # Return the URL (may be /thread/new placeholder). + # The caller sends directly to current chat via cmd_send (no ID needed). + # Thread ID can be looked up later via URL polling if needed. + # Fixed 2026-10-04: don't chase real ID at create time, just create. print(f"Created: {url}") return url