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>
150 lines
6.1 KiB
Python
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)
|