c415899b03
Self-made multi-agent chatroom. Append-only JSONL room on lmcp; reactive thin worker with a mechanically-enforced tool allowlist; human window via room_tail. Engine = a small local tool-model. Verified end-to-end (ask->ack->fetch_url reply incl. artifact spill; non-tool ask -> graceful decline). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
69 lines
2.3 KiB
Python
Executable File
69 lines
2.3 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""bullpen human window: watch the room, or post to it.
|
|
room_tail # print history, then follow live
|
|
room_tail read [since] # one-shot dump (optionally since id)
|
|
room_tail say <text...> # post as 'markus' (broadcast)
|
|
room_tail say @nick <text...> # post addressed to @nick
|
|
"""
|
|
import sys, json, time, os, subprocess
|
|
LOG = "/var/lib/bullpen/room.jsonl"
|
|
|
|
def fmt(d):
|
|
ts = time.strftime("%H:%M", time.localtime(d.get("ts", 0)))
|
|
rid = d.get("id", "?")
|
|
frm = d.get("from", "?")
|
|
to = d.get("to", "") or ""
|
|
typ = d.get("type", "chat")
|
|
body= d.get("body", "")
|
|
arrow = f" → {to}" if to else ""
|
|
if typ == "ack": return f"{rid:>4} {ts} {frm}{arrow} ·ack·"
|
|
if typ == "system": return f"{rid:>4} {ts} * {body}"
|
|
tag = {"ask": "ask: ", "reply": "reply: "}.get(typ, "")
|
|
return f"{rid:>4} {ts} {frm}{arrow} {tag}{body}"
|
|
|
|
def dump(since=0):
|
|
if not os.path.exists(LOG): return 0
|
|
last = since
|
|
with open(LOG) as f:
|
|
for line in f:
|
|
line = line.strip()
|
|
if not line: continue
|
|
try: d = json.loads(line)
|
|
except Exception: continue
|
|
if d.get("id", 0) > since:
|
|
print(fmt(d)); last = d.get("id", last)
|
|
return last
|
|
|
|
def say(args):
|
|
text = " ".join(args).strip()
|
|
frm, to = "markus", ""
|
|
if text.startswith("@"):
|
|
p2 = text.split(None, 1); to = p2[0]; text = p2[1] if len(p2) > 1 else ""
|
|
body = text
|
|
if not body:
|
|
print("usage: room_tail say [@nick] <text>", file=sys.stderr); sys.exit(2)
|
|
cmd = ["lmcp-tool", "room_say", f"from={frm}", f"body={body}"]
|
|
if to: cmd.append(f"to={to}")
|
|
r = subprocess.run(cmd, capture_output=True, text=True)
|
|
sys.stdout.write(r.stdout); sys.stderr.write(r.stderr)
|
|
sys.exit(r.returncode)
|
|
|
|
def follow():
|
|
last = dump(0)
|
|
print("--- following (Ctrl-C to quit) ---")
|
|
while True:
|
|
time.sleep(1.0)
|
|
last = dump(last)
|
|
|
|
def main():
|
|
a = sys.argv[1:]
|
|
if not a: follow()
|
|
elif a[0] == "say": say(a[1:])
|
|
elif a[0] == "read": dump(int(a[1]) if len(a) > 1 else 0)
|
|
elif a[0] in ("follow","-f"): follow()
|
|
else: print(__doc__)
|
|
|
|
if __name__ == "__main__":
|
|
try: main()
|
|
except KeyboardInterrupt: pass
|