No-AI desktop path: PyQt bot wired to the page
- templates/bot_runner.py (new, no Qt): runs the bot as a child process (unbuffered, UTF-8), tails its output, keeps raw lines in bot_raw.log, and turns lines into step/log/status events. Step markers and friendly rules use specific substrings, first match wins; unmatched error lines are always shown, other unmatched lines hidden. Dry run: python dashboard/bot_runner.py prints the events the page would get. - bridge.py: delegates to bot_runner, one run at a time, restores the saved dark mode on every page load (fixes sample-data mode in PyQt), adds load_latest_report / download_report slots the page calls. - SKILL.md: skip the stack question for a PyQt project; desktop wiring steps from the bot's real output; verify steps for the host. - tests: assert-based self-check for the line logic and run loop; Seam 1 run replay (status dot, Run button, steps, log) and wired-page mode; fixture b-pyqt-bot and its Seam 2 eval (all 14 checks pass). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
3eb3fdf00e
commit
162edd41df
17 files changed
+753
-47
No files matched your search
+34
-1
@@ -36,9 +36,39 @@ FIXTURES = {
|
||||
"never_asks": [r"\bkeep\b|restyle"],
|
||||
"brand": "#7a3cff",
|
||||
},
|
||||
"b-pyqt-bot": {
|
||||
"prompt": "Build me an RPA dashboard for this project. I can't answer questions while you "
|
||||
"work, so here are my answers to anything the skill asks. Colours: Tebra Sky. "
|
||||
"Optional sections: none.",
|
||||
"frontend": r"^\s*(none|no\b)",
|
||||
"asks": [r"colou?r", r"section"],
|
||||
"never_asks": [r"\bkeep\b|restyle", r"desktop|web"],
|
||||
"bot": True,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def bot_checks(project, html, checks, replay):
|
||||
"""Bot fixtures: the page is wired, and a dry run of the generated wiring gives real steps and logs.
|
||||
Returns the env that points the page check at that run."""
|
||||
checks["page wired to the bot (sample data off)"] = bool(re.search(r"SAMPLE_DATA\s*=\s*false", html))
|
||||
runners = [p for p in project.rglob("bot_runner.py") if ".claude" not in p.parts]
|
||||
checks["bot_runner.py built"] = len(runners) == 1
|
||||
if not runners:
|
||||
return {}
|
||||
host = [runners[0], runners[0].with_name("bridge.py")]
|
||||
checks["no placeholders left in the host"] = all(p.exists() and "{{" not in p.read_text(encoding="utf-8") for p in host)
|
||||
dry = subprocess.run([sys.executable, str(runners[0])], cwd=project, capture_output=True, text=True,
|
||||
encoding="utf-8", timeout=300)
|
||||
replay.write_text(dry.stdout, encoding="utf-8")
|
||||
events = [json.loads(x) for x in dry.stdout.splitlines() if x.startswith("{")]
|
||||
steps = [e["data"] for e in events if e["event"] == "step"]
|
||||
checks["bot dry run ends done"] = bool(events) and events[-1]["data"].get("state") == "done"
|
||||
checks["real steps reach the page"] = len({s["label"] for s in steps}) >= 3 and steps[-1]["pct"] == 100
|
||||
checks["bot error line reaches the log"] = any(e["event"] == "log" and e["data"]["type"] == "error" for e in events)
|
||||
return {"PAGE_CHECK_WIRED": "1", "PAGE_CHECK_REPLAY": str(replay)}
|
||||
|
||||
|
||||
def main(fixture):
|
||||
cfg = FIXTURES[fixture]
|
||||
work = Path(tempfile.mkdtemp(prefix=f"rpa-eval-{fixture}-"))
|
||||
@@ -80,8 +110,11 @@ def main(fixture):
|
||||
spec.loader.exec_module(palette)
|
||||
t = palette.derive(cfg["brand"])
|
||||
checks["custom palette applied"] = all(f"--accent:{t[m]['--accent']}" in html for m in t)
|
||||
env = {**os.environ, "PAGE_CHECK_HTML": str(pages[0])}
|
||||
if cfg.get("bot"):
|
||||
env.update(bot_checks(project, html, checks, results / f"{fixture}.run.jsonl"))
|
||||
pc = subprocess.run([sys.executable, "-m", "pytest", str(ROOT / "tests" / "page_check"), "-q"],
|
||||
env={**os.environ, "PAGE_CHECK_HTML": str(pages[0])}, capture_output=True, text=True)
|
||||
env=env, capture_output=True, text=True)
|
||||
checks["page check passes"] = pc.returncode == 0
|
||||
page_check = pc.stdout.strip().splitlines()[-1:] if pc.stdout else [pc.stderr[-500:]]
|
||||
else:
|
||||
|
||||
Reference in new issue
Block a user