Files
tai/taicpp/tests/orch_mplex_e2e.py
T
retoorandClaude Sonnet 5.5 ec9c471780 orchestration: sub agents render in live panels on the multiplexer screen
Feed panes in the multiplexer (vpane_* control commands, main-vertical layout, header row),
src/orch (board, panel, formatter, transport, CAP adapter), Agent observer and cancel flag,
supervision (idle warn/cancel, user close cancels, soft then hard stop), CAP answers collected
through the spill path. Research archived under background-work-limits.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-25 18:58:22 +01:00

150 lines
6.1 KiB
Python

#!/usr/bin/env python3
"""End to end: the real multiplexer server plus the real OrchBoard/OrchMplexTransport.
python3 -I tests/orch_mplex_e2e.py DETZ_BINARY ORCH_LIVE_BINARY
Checks layout geometry for 1, 2, 3 and 6 sub agents, streamed content, footers, the space limit,
linger and rebalance, the user closing a pane, the main pane exiting, and that a one-shot control
write reports panes that no longer exist. Everything runs in a throwaway TAI_HOME.
"""
import json, os, re, shutil, socket, subprocess, sys, tempfile, time
DETZ, LIVE = os.path.abspath(sys.argv[1]), os.path.abspath(sys.argv[2])
HOME = tempfile.mkdtemp(prefix="oe", dir="/tmp")
ENV = dict(os.environ, TAI_HOME=HOME, HOME=HOME)
for k in ("TAI_PLEXER_ACTIVE", "TAI_PLEXER_SOCKET", "TAI_PLEXER_PANE", "TMUX"):
ENV.pop(k, None)
SOCK = os.path.join(HOME, "plexer", "server.sock")
fails = []
def check(cond, what):
print(("ok " if cond else "FAIL ") + what)
if not cond:
fails.append(what)
def mp(*args):
return subprocess.run([DETZ, "--mplex", *args], env=ENV, capture_output=True, text=True, timeout=30)
def call(**kw):
s = socket.socket(socket.AF_UNIX)
s.connect(SOCK)
s.sendall((json.dumps(kw) + "\n").encode())
buf = b""
while not buf.endswith(b"\n"):
chunk = s.recv(65536)
if not chunk:
break
buf += chunk
s.close()
return json.loads(buf)
def panes():
out = mp("list-panes", "-F", "#{pane_id} #{pane_left} #{pane_top} #{pane_width} #{pane_height}").stdout.split("\n")
rows = []
for line in out:
m = re.match(r"%(\d+) (\d+) (\d+) (\d+) (\d+)$", line.strip())
if m:
rows.append(tuple(int(x) for x in m.groups()))
return rows
def run_live(n, tokens=40, hold=0, fail=False, wait=True):
env = dict(ENV, TAI_PLEXER_ACTIVE="1", TAI_PLEXER_SOCKET=SOCK, TAI_PLEXER_PANE="0")
cmd = [LIVE, str(n), str(tokens), str(hold)] + (["fail"] if fail else [])
if wait:
r = subprocess.run(cmd, env=env, capture_output=True, text=True, timeout=60)
return r.stdout
return subprocess.Popen(cmd, env=env, stdout=subprocess.PIPE, text=True)
def capture(pid):
return mp("capture-pane", "-p", "-t", "%" + str(pid)).stdout
try:
r = mp("new-session", "-d", "-s", "t", "-x", "120", "-y", "40")
check(r.returncode == 0, "detached session starts")
# ---- 1 sub agent: main left full height, one pane right
p = run_live(1, hold=1500, wait=False)
time.sleep(0.6)
rows = panes()
check(len(rows) == 2, "one sub agent: two panes")
main = [r for r in rows if r[0] == 0][0]
sub = [r for r in rows if r[0] != 0]
check(main[1] == 0 and main[2] == 0 and main[4] == 40, "main pane on the left at full height")
check(len(sub) == 1 and sub[0][1] > main[1] + main[3] - 1 and sub[0][4] == 40, "sub pane on the right at full height")
check(abs(main[3] - 120 * 0.55) <= 2, "main pane is about 55 percent wide")
out = p.communicate(timeout=30)[0]
check("shown" in out, "board reports the panel shown")
time.sleep(0.3)
text = capture(sub[0][0])
check("scripted task number 1" in text, "task echoed at the top")
check("shell" in text and "line three" in text and "line four" not in text, "tool call and a three-line result")
check("THE END 0" in text and "word0_39" in text, "streamed text arrived complete")
check("done" in text and "all good" in text, "footer states the result")
# linger then rebalance
time.sleep(21.5)
check(len(panes()) == 1, "panel leaves after the linger and the main pane takes the screen")
# ---- 3 sub agents: stacked evenly on the right
p = run_live(3, tokens=30, hold=1200, wait=False)
time.sleep(0.8)
rows = panes()
subs = sorted([r for r in rows if r[0] != 0], key=lambda r: r[2])
check(len(subs) == 3, "three sub agents: three panes on the right")
check(len({r[1] for r in subs}) == 1 and subs[0][1] > 0, "all in one column")
heights = [r[4] for r in subs]
check(max(heights) - min(heights) <= 2, "even heights " + str(heights))
check(sum(heights) + 2 == 40, "column fills the window")
p.communicate(timeout=30)
time.sleep(0.3)
for r in subs:
check("THE END" in capture(r[0]), "pane %%%d content" % r[0])
time.sleep(21.5)
check(len(panes()) == 1, "all three leave")
# ---- space limit: the pane that does not fit runs without a panel
p = run_live(8, tokens=5, hold=800, wait=False)
time.sleep(1.2)
rows = panes()
check(len(rows) - 1 == 6, "only as many panels as fit (6 rows each): %d" % (len(rows) - 1))
out = p.communicate(timeout=30)[0]
check(out.count("shown") == 6 and "no space" in out, "the others report no space instead of failing")
time.sleep(21.5)
# ---- failure state and a manual close = cancel
p = run_live(2, tokens=400, hold=3000, fail=True, wait=False)
time.sleep(0.8)
subs = sorted([r for r in panes() if r[0] != 0], key=lambda r: r[2])
victim = subs[0][0]
lst = call(cmd="vpane_list")["data"]
check(len(lst) == 2 and all(x["state"] == "running" for x in lst), "list reports running panels")
mp("kill-pane", "-t", "%" + str(victim))
time.sleep(0.5)
reply = call(cmd="vpane_write", ops=[{"pane": victim, "data": "x"}])
check(victim in reply["data"]["closed"], "a write to a closed pane reports it closed")
p.communicate(timeout=60)
time.sleep(0.4)
left = [r for r in panes() if r[0] != 0]
check(len(left) == 1, "the other pane survived")
check("scripted failure" in capture(left[0][0]) and "failed" in capture(left[0][0]), "failed footer")
# ---- main pane exits: feed panes go with the window
call(cmd="vpane_open", pane="0", label="orphan")
check(len(panes()) >= 2, "a feed pane is open")
mp("send-keys", "-t", "%0", "exit", "Enter")
time.sleep(1.5)
check(mp("list-sessions").stdout.strip() == "" or "no server" in (mp("list-sessions").stderr + mp("list-sessions").stdout).lower(),
"session ends with its main pane")
finally:
mp("kill-server")
shutil.rmtree(HOME, ignore_errors=True)
print("\n%d failures" % len(fails))
sys.exit(1 if fails else 0)