2026-10-05 17:29:16 +00:00
|
|
|
#!/usr/bin/env python3
|
|
|
|
|
"""Swarm worker -> main-loop visibility bridge (Bridge Builder 5/5).
|
|
|
|
|
|
|
|
|
|
After the worker records a slot result via `box swarm-report` (see
|
|
|
|
|
reporter.py), this posts a short human-readable completion note to the
|
|
|
|
|
slot's own sidechat. The note deliberately carries NO `[JOB ` tag, so
|
|
|
|
|
self_main_loop.py's check_sidechats() does not filter it: the next
|
|
|
|
|
main-loop tick picks it up, includes it in the agent's digest, and the
|
|
|
|
|
completion becomes visible in the same surface as everything else.
|
|
|
|
|
That is the "answering via the main loop" path.
|
|
|
|
|
|
|
|
|
|
Design doc: bin/swarm_worker/mainloop-bridge.md
|
|
|
|
|
|
|
|
|
|
Runs on bl. Never auto-wired into any loop; call explicitly from the
|
|
|
|
|
worker daemon after post_result() succeeds. Dry-run mode posts nothing.
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
import subprocess
|
|
|
|
|
import sys
|
|
|
|
|
|
|
|
|
|
BIN = "/home/super/Projects/NetVM/bin"
|
|
|
|
|
DM_PY = BIN + "/dm.py"
|
|
|
|
|
NOTE_CHARS = 200
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def notify_via_mainloop(swarm_id, slot_index, message, worker_id="swarm-worker",
|
|
|
|
|
dry_run=False):
|
|
|
|
|
"""Post a swarm-slot completion note visible to the main loop.
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
swarm_id: e.g. "sw-20261005-151307-a222"
|
|
|
|
|
slot_index: int slot number
|
|
|
|
|
message: human-readable summary (first NOTE_CHARS chars used)
|
|
|
|
|
worker_id: sender identity for dm.py --agent
|
|
|
|
|
dry_run: if True, print what WOULD be sent without sending.
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
True on success (or dry-run), False on failure (logged, not raised).
|
|
|
|
|
"""
|
2026-10-07 00:25:51 +00:00
|
|
|
target_name = f"{swarm_id}-s{slot_index}" if str(swarm_id).startswith("sw-") else f"sw-{swarm_id}-s{slot_index}"
|
2026-10-05 17:29:16 +00:00
|
|
|
summary = (message or "").strip().replace("\n", " ")[:NOTE_CHARS]
|
2026-10-07 00:25:51 +00:00
|
|
|
tag = "%s/%s" % (swarm_id, slot_index)
|
|
|
|
|
note = "[SWARM-DONE %s] %s" % (tag, summary)
|
|
|
|
|
|
|
|
|
|
sender = worker_id if worker_id in ("muse", "pip", "646", "opm", "dev", "def", "super") else "super"
|
|
|
|
|
to_agent = "opm"
|
2026-10-05 17:29:16 +00:00
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
sys.path.insert(0, BIN)
|
|
|
|
|
import dm
|
|
|
|
|
uuid = dm.resolve_sidechat_target(target_name)
|
2026-10-07 00:25:51 +00:00
|
|
|
if not uuid:
|
|
|
|
|
sc = dm.load_sidechat_map() if hasattr(dm, "load_sidechat_map") else {}
|
|
|
|
|
entry = sc.get(target_name, {})
|
|
|
|
|
uuid = entry.get("thread_uuid")
|
|
|
|
|
if entry.get("agent"):
|
|
|
|
|
to_agent = entry.get("agent")
|
2026-10-05 17:29:16 +00:00
|
|
|
except Exception as e:
|
|
|
|
|
print("notify_via_mainloop: target resolve failed for %s: %s"
|
|
|
|
|
% (target_name, e), file=sys.stderr)
|
|
|
|
|
return False
|
|
|
|
|
if not uuid:
|
|
|
|
|
print("notify_via_mainloop: no UUID for %s" % target_name,
|
|
|
|
|
file=sys.stderr)
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
cmd = [sys.executable, DM_PY, "send",
|
2026-10-07 00:25:51 +00:00
|
|
|
"--agent", sender,
|
|
|
|
|
"--to", to_agent,
|
2026-10-05 17:29:16 +00:00
|
|
|
"--target", uuid,
|
|
|
|
|
note]
|
|
|
|
|
if dry_run:
|
|
|
|
|
print("DRY-RUN would run: %s" % " ".join(cmd))
|
|
|
|
|
return True
|
|
|
|
|
try:
|
|
|
|
|
r = subprocess.run(cmd, capture_output=True, text=True, timeout=120)
|
|
|
|
|
except Exception as e:
|
|
|
|
|
print("notify_via_mainloop: dm.py exec failed: %s" % e,
|
|
|
|
|
file=sys.stderr)
|
|
|
|
|
return False
|
|
|
|
|
if r.returncode != 0:
|
|
|
|
|
tail = ((r.stderr or "") + (r.stdout or "")).strip()[-300:]
|
|
|
|
|
print("notify_via_mainloop: dm.py rc=%d: %s" % (r.returncode, tail),
|
|
|
|
|
file=sys.stderr)
|
|
|
|
|
return False
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
|
live = "--live" in sys.argv
|
|
|
|
|
args = [a for a in sys.argv[1:] if a != "--live"]
|
|
|
|
|
if len(args) < 3:
|
|
|
|
|
print("usage: mainloop_notify.py <swarm-id> <slot> \"<message>\" [--live]")
|
|
|
|
|
print("default is DRY-RUN (posts nothing).")
|
|
|
|
|
sys.exit(2)
|
|
|
|
|
ok = notify_via_mainloop(args[0], args[1], args[2], dry_run=not live)
|
|
|
|
|
print("notified" if ok else "FAILED")
|