#!/usr/bin/env python3 """End-to-end differential test: the same scripted conversation runs through tai.py (real Store, real Agent, patched urlopen) and through the C++ Agent (tests/agent_e2e.cpp: scripted transport, in-memory store). Messages, HTTP requests, store events, audit rows and tool results must be identical. python3 tests/agent_e2e.py [--bin build/agent_e2e] [--filter NAME] [-v] """ import argparse import http.client import io import json import os import re import shutil import subprocess import sys import tempfile import types import urllib.error import urllib.request HERE = os.path.dirname(os.path.abspath(__file__)) ROOT = os.path.abspath(os.path.join(HERE, "..", "..")) sys.path.insert(0, ROOT) sys.dont_write_bytecode = True SAFE_PATH = "/usr/bin:/bin" TZ = "Europe/Amsterdam" def clean_env(home): for key in list(os.environ): if key.startswith("TAI_") or key in ("MUSE_BIN", "OPENCODE_BIN", "GROK_BIN", "CLAUDE_BIN", "CODEX_BIN", "GEMINI_BIN", "OPENROUTER_API_KEY", "DEVPLACE_API_KEY", "TMUX", "TMUX_PANE", "HISTFILE"): del os.environ[key] os.environ["PATH"] = SAFE_PATH os.environ["TZ"] = TZ os.environ["TAI_HOME"] = home os.environ["TAI_BACKENDS"] = "lab|http://fake.invalid/v1|k|m1" os.environ["TAI_OPENROUTER"] = "0" os.environ["TAI_POLLINATIONS"] = "0" os.environ["TAI_OPENCODE"] = "0" os.environ["HOME"] = home import tai # noqa: E402 # ---------------------------------------------------------------- script builders def say(text): return {"kind": "stream", "events": [{"choices": [{"delta": {"content": text}}]}]} def calls(*items, text=None): events = [] if text: events.append({"choices": [{"delta": {"content": text}}]}) for idx, (name, args, cid) in enumerate(items): argtext = args if isinstance(args, str) else json.dumps(args) half = len(argtext) // 2 events.append({"choices": [{"delta": {"tool_calls": [{"index": idx, "id": cid, "function": {"name": name, "arguments": argtext[:half]}}]}}]}) events.append({"choices": [{"delta": {"tool_calls": [{"index": idx, "function": {"arguments": argtext[half:]}}]}}]}) return {"kind": "stream", "events": events} def single(text): return {"kind": "json", "body": {"choices": [{"message": {"content": text}}]}} def raw(body, content_type="text/plain"): return {"kind": "raw", "body": body, "content_type": content_type} def status(code, body=""): return {"kind": "status", "status": code, "body": body} def tc(name, args, cid): return (name, args, cid) # ---------------------------------------------------------------- python side class ScriptExhausted(BaseException): """Not an Exception: the retry loops of ChatClient must not swallow it.""" class FakeResponse(object): def __init__(self, body=b"", lines=None, status=200, content_type=""): self._body = body self._lines = lines self.status = status self.headers = {"Content-Type": content_type} if content_type else {} def __enter__(self): return self def __exit__(self, *exc): return False def read(self, n=-1): data = self._body if n is None or n < 0 else self._body[:n] return data def __iter__(self): return iter(self._lines or []) def close(self): pass def python_run(sc, workdir): home = os.path.join(workdir, ".home") clean_env(home) if sc.get("retry_seconds"): os.environ["TAI_RETRY_SECONDS"] = str(sc["retry_seconds"]) os.makedirs(home, exist_ok=True) os.chdir(workdir) tai.AGENTS.clear() tai.AGENTS_NEXT[0] = 1 requests = [] queues = {"stream": list(sc.get("script", [])), "single": list(sc.get("single", []))} def fake_urlopen(request, timeout=None): url = request.full_url if url.endswith("/models"): raise urllib.error.URLError("fake") body = None if request.data is not None: try: body = json.loads(request.data) except ValueError: body = request.data.decode("utf-8", "replace") requests.append({"method": request.get_method(), "url": url, "body": body}) streaming = isinstance(body, dict) and bool(body.get("stream")) queue = queues["stream" if streaming else "single"] if not queue: raise ScriptExhausted() item = queue.pop(0) kind = item["kind"] if kind == "transport_error": raise urllib.error.URLError("scripted") if kind == "status": reason = http.client.responses.get(item["status"], "") raise urllib.error.HTTPError(url, item["status"], reason, {}, io.BytesIO(item.get("body", "").encode("utf-8"))) content_type = item.get("content_type", "") if kind == "json": return FakeResponse(body=json.dumps(item["body"]).encode("utf-8"), content_type=content_type) if kind == "raw": return FakeResponse(body=item["body"].encode("utf-8"), content_type=content_type) lines = [] for event in item["events"]: lines.append(("data: %s\n" % json.dumps(event)).encode("utf-8")) lines.append(b"\n") lines.append(b"data: [DONE]\n") return FakeResponse(lines=lines, content_type=content_type) original = urllib.request.urlopen original_sleep = tai.time.sleep urllib.request.urlopen = fake_urlopen tai.time.sleep = lambda seconds: None try: args = types.SimpleNamespace(yes=bool(sc.get("auto_approve")), profile=sc.get("profile", "default"), no_opencode=True) config = tai.Config(args) seal = tai.Seal(config.home, tai.resolve_passphrase()[0]) store = tai.Store(config, seal) presets = sc.get("presets") or {} for name, text in (presets.get("systems") or {}).items(): store.save_system(name, text) for name, item in (presets.get("secrets") or {}).items(): store.save_secret(name, item["value"], {}, [], "default") for name, item in (presets.get("bots") or {}).items(): store.save_bot_system("default", name, item["system"]) bots = store.load_bots("default") bots[name] = {"nicknames": item["nicknames"], "created": "t", "updated": "t"} store.save_bots("default", bots) events = [] original_log = store.log_event def wrapped(profile, role, kind, text, tags=()): events.append([role, kind, text]) return original_log(profile, role, kind, text, tags) store.log_event = wrapped agent = tai.Agent(config, store, persist=sc.get("persist", True), quiet=True, yolo=bool(sc.get("yolo"))) if sc.get("deadline_expired"): agent.deadline = 1.0 answers = sc.get("answers") if isinstance(answers, list): queue = list(answers) agent.prompter = lambda kind, text, options: queue.pop(0) if queue else None runner_script = sc.get("runner_script") if isinstance(runner_script, list): rq = list(runner_script) agent.runner_override = lambda task, profile, timeout: rq.pop(0) if rq else "runner exhausted" out = {} try: prompts = sc.get("prompts") or [sc.get("prompt")] results = [] for prompt in prompts: results.append(agent.run_turn(prompt, capture=True, max_steps=sc.get("max_steps", tai.MAX_STEPS))) out["results"] = results if sc.get("verify"): text, ok = agent.verify_loop(sc["prompt"], results[-1]) out["verify"] = {"text": text, "ok": ok} except ScriptExhausted: out["exception"] = "script exhausted" except Exception as exc: # noqa: BLE001 out["exception"] = str(exc) out["messages"] = agent.messages out["requests"] = requests out["events"] = events audits = [] for row in reversed(store.audit_history(None, None, 100)): full = store.audit_get(row["id"]) audits.append({"actor": full["actor"], "action": full["action"], "path": full["path"], "message": full["message"], "tags": full["tags"], "old_size": full["old_size"], "new_size": full["new_size"], "old": full["old"], "new": full["new"]}) out["audits"] = audits out["tool_calls"] = agent.tools.calls out["yolo"] = agent.yolo out["timed_out"] = agent.timed_out out["system"] = agent.system_message store.close() return out finally: urllib.request.urlopen = original tai.time.sleep = original_sleep def cpp_run(sc, workdir, binary): path = os.path.join(workdir, "..", "scenario.json") with open(path, "w") as handle: json.dump(sc, handle) env = dict(os.environ) env.update({"PATH": SAFE_PATH, "TZ": TZ, "HOME": workdir}) done = subprocess.run([binary, path, workdir], stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=env, cwd=workdir) if done.returncode != 0: raise RuntimeError("binary failed: %s %s" % (done.returncode, done.stderr.decode("utf-8", "replace")[-1500:])) return json.loads(done.stdout.decode("utf-8")) def normalize(value): text = json.dumps(value, sort_keys=True) text = re.sub(r"mem:[0-9a-f]{16}", "mem:ID", text) return json.loads(text) # ---------------------------------------------------------------- scenarios def build_scenarios(): big = "line of text number %d\n" * 1 scenarios = [] def add(name, prompt, script, **kw): sc = {"name": name, "prompt": prompt, "script": script, "skill_catalog": tai.skill_catalog({})} sc.update(kw) scenarios.append(sc) add("plain", "hi there", [say("Hello there")], auto_approve=True) add("read_file", "read a.txt", [calls(tc("read_file", {"path": "a.txt"}, "c1")), say("done reading")], files={"a.txt": "alpha\nbeta\n"}, auto_approve=True) add("read_missing_and_big", "read", [calls(tc("read_file", {"path": "missing.txt"}, "c1"), tc("read_file", {"path": "big.txt"}, "c2")), say("ok")], files={"big.txt": "x" * 200001}, auto_approve=True) add("write_flow", "write", [ calls(tc("write_file", {"path": "b.txt", "content": "new"}, "c1")), calls(tc("read_file", {"path": "b.txt"}, "c2")), calls(tc("write_file", {"path": "b.txt", "content": "second\nversion"}, "c3"), tc("write_file", {"path": "sub/dir/c.txt", "content": "nested"}, "c4"), tc("write_file", {"path": "", "content": "x"}, "c5")), calls(tc("edit_file", {"path": "b.txt", "find": "second", "replace": "third"}, "c6"), tc("edit_file", {"path": "b.txt", "find": "zzz", "replace": "q"}, "c7"), tc("edit_file", {"path": "b.txt", "find": "", "replace": "q"}, "c8"), tc("edit_file", {"path": "nope.txt", "find": "a", "replace": "b"}, "c9")), say("finished")], files={"b.txt": "old"}, auto_approve=True) add("edit_not_unique", "edit", [ calls(tc("edit_file", {"path": "c.txt", "find": "x", "replace": "y"}, "c1")), calls(tc("read_file", {"path": "c.txt"}, "c2")), calls(tc("edit_file", {"path": "c.txt", "find": "x", "replace": "y"}, "c3"), tc("edit_file", {"path": "c.txt", "find": "x x x", "replace": "é"}, "c4")), say("end")], files={"c.txt": "x x x"}, auto_approve=True) add("delete_restore", "delete and restore", [ calls(tc("delete_file", {"path": "d.txt"}, "c1")), calls(tc("read_file", {"path": "d.txt"}, "c2")), calls(tc("delete_file", {"path": "d.txt"}, "c3")), calls(tc("audit", {}, "c4"), tc("restore", {"id": 1}, "c5"), tc("restore", {"id": 99}, "c6"), tc("restore", {"id": "x"}, "c7")), calls(tc("read_file", {"path": "d.txt"}, "c8")), say("restored")], files={"d.txt": "keep me"}, auto_approve=True) add("shell_basic", "run things", [ calls(tc("shell", {"command": "echo hello"}, "c1"), tc("shell", {"command": "ls nonexistent_dir_xyz"}, "c2"), tc("shell", {"command": "echo curl http://example.com"}, "c3"), tc("shell", {"command": " "}, "c4"), tc("shell", {"command": "true"}, "c5"), tc("shell", {"command": "printf 'a\\nb\\n'; exit 3"}, "c6")), calls(tc("shell", {"command": "echo x", "secrets": "oops"}, "c7"), tc("shell", {"command": "echo x", "secrets": ["nope"]}, "c8"), tc("shell", {"command": "pwd", "workdir": "sub"}, "c9")), say("done")], files={"sub/keep.txt": "k"}, auto_approve=True) add("shell_long_output", "spill", [ calls(tc("shell", {"command": "yes line | head -n 3000"}, "c1")), say("done")], auto_approve=True) add("unknown_and_bad_args", "odd calls", [ calls(tc("nonexistent_tool", {}, "c1"), tc("read_file", "this is not json", "c2"), tc("read_file", "[1, 2]", "c3"), tc("list_secrets", "[1]", "c4"), tc("bad name!", {}, "c5")), say("ok")], auto_approve=True) add("loop_stop", "loop", [calls(tc("read_file", {"path": "a.txt"}, "c%d" % i)) for i in range(1, 8)] + [say("never")], files={"a.txt": "same"}, auto_approve=True) add("secrets", "secrets", [ calls(tc("store_secret", {"name": "api_key", "value": "supersecret123", "username": "me", "host": "h", "port": 22, "notes": "n", "tags": ["Prod"]}, "c1")), calls(tc("list_secrets", {}, "c2"), tc("store_secret", {"name": "bad name", "value": "v"}, "c3"), tc("store_secret", {"name": "x", "value": ""}, "c4"), tc("store_secret", {"name": "y", "value": "valid1", "port": "abc"}, "c5"), tc("store_secret", {"name": "y", "value": "valid1", "port": 70000}, "c6"), tc("store_secret", {"name": "y", "value": "valid1", "expires": "bogus"}, "c7"), tc("store_secret", {"name": "y", "value": "valid1", "tags": "x"}, "c8")), calls(tc("shell", {"command": "echo $TAI_SECRET_API_KEY", "secrets": ["api_key"]}, "c9")), calls(tc("delete_secret", {"name": "api_key"}, "c10"), tc("delete_secret", {"name": "missing"}, "c11"), tc("list_secrets", {}, "c12")), say("fin")], auto_approve=True) add("schedules", "plan", [ calls(tc("schedule", {"prompt": "do it", "every": 120, "name": "nightly", "tags": ["Ops"]}, "c1"), tc("schedule", {"prompt": "x", "at": "2026-12-24T10:00"}, "c2"), tc("schedule", {"prompt": "x"}, "c3"), tc("schedule", {"prompt": "x", "every": 5}, "c4"), tc("schedule", {"prompt": "x", "at": "garbage"}, "c5"), tc("schedule", {"prompt": "", "every": 100}, "c6"), tc("schedule", {"prompt": "x", "every": "abc"}, "c7"), tc("schedule", {"prompt": "x", "every": 100, "profile": "other"}, "c8")), calls(tc("schedules", {}, "c9"), tc("unschedule", {"id": 1}, "c10"), tc("unschedule", {"id": 77}, "c11"), tc("schedules", {}, "c12")), say("planned")], auto_approve=True) add("bots_and_mentions", "make a bot", [ calls(tc("create_bot", {"name": "helper-bot", "description": "I help.", "rules": "be brief", "nicknames": ["hb"]}, "c1"), tc("create_bot", {"name": "main", "description": "x"}, "c2"), tc("create_bot", {"name": "Bad Name", "description": "x"}, "c3"), tc("create_bot", {"name": "empty"}, "c4"), tc("create_bot", {"name": "helper-bot", "description": "dup"}, "c5")), say("bot created"), say("hello from the bot"), say("plain third answer")], prompts=["make a bot", "@hb say hello", "@nobody hi", "@ "], auto_approve=True) add("compaction", "read all", [calls(tc("read_file", {"path": "f%d.txt" % i}, "c%d" % i)) for i in range(1, 14)] + [say("all read")], files={"f%d.txt" % i: ("%d " % i) * 95000 for i in range(1, 14)}, single=[single("SUMMARY OF HISTORY")], auto_approve=True) add("backend_fail", "will fail", [status(500, "upstream oops")], retry_seconds=1, auto_approve=True) add("tools_400_fallback", "fallback", [status(400, "bad tools"), say("worked without tools")], auto_approve=True) add("all_permanent_fail", "perm", [status(401, "no key")], auto_approve=True) add("ask_user", "ask", [ calls(tc("ask_user", {"question": "Pick one", "options": ["A", "B", {"label": "C"}, "", 5]}, "c1"), tc("ask_user", {"question": "Pick again", "options": ["A", "B"]}, "c2"), tc("ask_user", {"question": "Free?"}, "c3"), tc("ask_user", {"question": ""}, "c4"), tc("ask_user", {"question": "q", "options": "no"}, "c5"), tc("ask_user", {"question": "closed"}, "c6")), say("asked")], answers=["2", "b", " hello world ", None], auto_approve=True) add("approval_deny", "dangerous", [ calls(tc("shell", {"command": "rm -rf somewhere"}, "c1"), tc("shell", {"command": "ls"}, "c2")), say("stopped")], answers=["Deny", "Deny"], files={"somewhere/x": "1"}) add("yolo_always", "yolo", [calls(tc("read_file", {"path": "a.txt"}, "c1")), say("ok")], yolo=True, files={"a.txt": "a"}) add("verify_ok", "do the thing", [say("I did it")], runner_script=['{"success": true, "failures": [], "commentary": "good"}'], verify=True, auto_approve=True) add("verify_retry", "do the thing", [say("first try"), say("second try")], runner_script=['{"success": false, "failures": [{"item": "x", "reason": "r", "proposal": "p"}]}', '{"success": true, "failures": []}'], verify=True, auto_approve=True) add("remember", "remember my name", [ calls(tc("remember", {"instruction": "The user is called Retoor and likes concise answers."}, "c1"), tc("remember", {"instruction": ""}, "c2")), say("remembered")], single=[single("You are tai. The user is called Retoor and likes concise answers. Be professional.")], auto_approve=True) add("remember_fail", "remember", [calls(tc("remember", {"instruction": "x"}, "c1")), say("sorry")], single=[single("short")], auto_approve=True) add("record_flow", "records", [ calls(tc("record_save", {"content": "alpha beta gamma", "title": "T1", "kind": "note", "tags": ["Zeta"]}, "c1"), tc("record_save", {"content": "", "title": "e"}, "c2"), tc("record_save", {"content": "x", "kind": "weird"}, "c3"), tc("record_save", {"content": "x", "tags": "no"}, "c4")), say("saved")], auto_approve=True) add("web_fetch", "fetch", [ calls(tc("web_fetch", {"url": "ftp://x"}, "c1"), tc("web_fetch", {"url": "http://example.com/a", "method": "TRACE"}, "c2"), tc("web_fetch", {"url": "http://example.com/a"}, "c3"), tc("web_fetch", {"url": "http://example.com/b", "raw": True}, "c4"), tc("web_fetch", {"url": "http://example.com/c", "headers": {"X-A": "b"}, "method": "post", "body": "{}"}, "c5"), tc("web_fetch", {"url": "http://example.com/d", "headers": {"bad name": "b"}}, "c6"), tc("web_fetch", {"url": "http://example.com/e", "timeout": "abc"}, "c7"), tc("web_fetch", {"url": "http://example.com/f"}, "c8"), tc("web_fetch", {"url": "http://example.com/g"}, "c9")), say("fetched")], single=[raw("
Para text
", "text/html; charset=utf-8"), raw("raw text", "text/plain"), raw("posted", "application/json"), status(404, "Not here"), status(500, ""), raw("", "text/html")], auto_approve=True) add("web_search", "search", [ calls(tc("web_search", {"query": "rust news", "images": True}, "c1"), tc("web_search", {"query": ""}, "c2"), tc("web_search", {"query": "x"}, "c3")), say("searched")], single=[{"kind": "json", "body": {"results": [{"title": "T", "url": "http://u", "description": "d" * 500, "content": "c" * 3000}, {"title": None, "url": "u2"}]}}, {"kind": "json", "body": {"results": []}}], auto_approve=True) add("poll_fork", "fork", [ calls(tc("fork", {"task": "inspect the repo"}, "c1"), tc("fork", {"task": ""}, "c2"), tc("fork", {"task": "x", "profile": "zzz"}, "c3")), calls(tc("poll", {"id": 1, "wait": 5}, "c4"), tc("poll", {"id": 99}, "c5"), tc("poll", {"id": "abc"}, "c6")), say("collected")], runner_script=["worker result text"], auto_approve=True) add("terminals", "terminals", [ calls(tc("list_terminals", {}, "c1"), tc("plexer_send", {"text": "x"}, "c2"), tc("plexer_status", {}, "c3"), tc("plexer_read", {}, "c4")), say("looked")], auto_approve=True) add("persist_off", "worker style", [calls(tc("ask_user", {"question": "q"}, "c1"), tc("read_file", {"path": "a.txt"}, "c2")), say("end")], persist=False, files={"a.txt": "z"}, auto_approve=True) add("max_steps_stall", "stall", [calls(tc("shell", {"command": "echo %d" % i}, "c%d" % i)) for i in range(1, 5)] + [say("end")], max_steps=2, auto_approve=True) add("presets_profile", "with presets", [say("ok")], presets={"systems": {"default": "You are a custom bot. " + tai.VAULT_MEMORY_NOTE + " end"}}, auto_approve=True) def ev(delta): return {"choices": [{"delta": delta}]} add("stream_quirks", "weird stream", [ {"kind": "stream", "events": [ev({"reasoning_content": "thinking about it"}), ev({"content": "part one "}), ev({"content": "part two"}), ev({"tool_calls": [{"index": 0, "id": "q1", "function": {"name": "read_file", "arguments": "{\"path\": "}}]}), ev({"tool_calls": [{"index": 5, "function": {"arguments": "\"a.txt\"}"}}]}), {"choices": []}, {"nochoices": True}, ev({"tool_calls": [{"index": 1, "id": "q2", "function": {"name": "shell<|channel|>commentary", "arguments": "{\"command\": \"echo\"}"}}]}), ev({"tool_calls": [{"index": 2, "id": "q3", "function": {"name": "bad name!", "arguments": "{}"}}]}), ev({"tool_calls": [{"index": 3, "id": "q4", "function": {"name": "list_secrets", "arguments": "oops not json"}}]})]}, {"kind": "stream", "events": [ev({"reasoning": "only reasoning, no content"})]}], files={"a.txt": "A"}, auto_approve=True) add("reasoning_only", "think", [{"kind": "stream", "events": [ev({"reasoning": "deep thought"})]}], auto_approve=True) add("empty_reply", "nothing", [{"kind": "stream", "events": []}], auto_approve=True) add("store_errors", "probe store tools", [ calls(tc("record_read", {"id": "mem:0000000000000000"}, "c1"), tc("record_delete", {"id": ""}, "c2"), tc("record_delete", {"id": "mem:none"}, "c3"), tc("graph_query", {"node": "bogus"}, "c4"), tc("graph_query", {"node": "mem:0123456789abcdef"}, "c5"), tc("graph_link", {"src": "", "dst": "x"}, "c6"), tc("tags", {}, "c7"), tc("tags", {"limit": "x"}, "c8"), tc("search", {"query": ""}, "c9"), tc("search", {"query": "x", "kinds": ["bogus"]}, "c10"), tc("search", {"query": "x", "limit": "q"}, "c11"), tc("record_search", {"kind": "weird"}, "c12"), tc("audit", {"limit": "z"}, "c13"), tc("release", {"part": "huge"}, "c14"), tc("release", {"part": "patch"}, "c15"), tc("install", {"action": "bogus"}, "c16"), tc("sysinfo", {"checks": []}, "c17"), tc("mcp_call", {}, "c18"), tc("mcp_enable", {"server": "nope"}, "c19"), tc("mcp_list", {}, "c20"), tc("mcp_tools", {"server": "nope"}, "c21"), tc("recall", {"query": ""}, "c22"), tc("recall", {"query": "x", "tags": "no"}, "c23"), tc("load_skill", {"name": "nope"}, "c24")), say("probed")], auto_approve=True) add("deadline", "late", [say("never reached")], deadline_expired=True, auto_approve=True) add("mentions", "pins", [say("one"), say("two"), say("three"), say("four"), say("five")], prompts=["@lab hello", "@m1 hello", "@a.txt what is this", "@auto hi", "@lab"], files={"a.txt": "A"}, auto_approve=True) add("persist_resume", "session restore", [say("first"), say("second")], prompts=["one", "two"], auto_approve=True) add("tool_error_text", "bad things", [ calls(tc("describe_image", {"source": ""}, "c1"), tc("speak", {"text": ""}, "c2"), tc("create_skill", {"name": "Bad Name", "brief": "x"}, "c3"), tc("create_skill", {"name": "ok-name", "brief": ""}, "c4"), tc("install_skill", {"source": ""}, "c5"), tc("plexer_type", {"text": ""}, "c6"), tc("plexer_show", {"spec": "no"}, "c7"), tc("tui_screen", {}, "c8"), tc("export_terminal_log", {}, "c9"), tc("get_current_terminal_content", {"lines": "x", "target": "nonexistent-pane"}, "c10"), tc("tail_terminals", {}, "c11"), tc("web_fetch", {"url": "http://x", "headers": {"A": 1}}, "c12"), tc("web_fetch", {"url": "http://x", "body": 5}, "c13"), tc("write_file", {"path": "/proc/forbidden/x", "content": "y"}, "c14")), say("done")], auto_approve=True) add("legacy_note", "legacy", [say("ok")], presets={"systems": {"default": "Old " + tai.LEGACY_PASSWORD_NOTE + " tail"}}, auto_approve=True) return scenarios def run_all(args): scenarios = build_scenarios() failures = 0 for sc in scenarios: if args.filter and args.filter not in sc["name"]: continue results = [] for who in ("python", "cpp"): base = tempfile.mkdtemp(prefix="agent-e2e-") workdir = os.path.join(base, "work") os.makedirs(workdir) for rel, content in (sc.get("files") or {}).items(): path = os.path.join(workdir, rel) os.makedirs(os.path.dirname(path), exist_ok=True) with open(path, "w", encoding="utf-8") as handle: handle.write(content) try: if who == "python": results.append(normalize(python_run(sc, workdir))) else: results.append(normalize(cpp_run(sc, workdir, args.bin))) except Exception as exc: # noqa: BLE001 results.append({"harness_error": "%s: %s" % (type(exc).__name__, exc)}) finally: os.chdir(ROOT) shutil.rmtree(base, ignore_errors=True) py, cpp = results # workdir path differs per run: neutralise it py_text = re.sub(r"/tmp/agent-e2e-[A-Za-z0-9_]+/work", "