Repository navigation
feat(replay): embeddable Replay app, Run box, Play, and a map that follows the cursor - #317
Conversation
…llows the cursor
- create_replay_app(run=None): the Replay view as a small ASGI app (no dashboard)
to mount in any FastAPI/Starlette app; /?run=<id> pins one run (iframe-friendly).
With run(input, run_id) it shows a Run box: POST /api/replay/run (JSON only,
one run at a time) starts a run and the page follows it live.
- render(el, {base, run, runner, approve}) so the view works under a mount prefix.
- Map nodes show their state at the cursor: not reached / running / done / failed /
skipped, and "×N" counts real runs instead of events (a step that ran once
no longer reads "×2"). Scrubbing or Play rewinds the map with the cursor.
- ▶ Play steps through a run on its own.
- events?wait=1 answers a not-yet-started run without a 404.
- Node names stay whole (squeezed or clipped) so accessible names contain the
visible text (WCAG 2.5.3; axe flagged truncated tool names).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
|
@claude You are the FINAL architecture reviewer. If the branch is under MervinPraison/PraisonAIUI (not a fork), you are able to make modifications to this branch and push directly. SCOPE: Focus ONLY on PraisonAIUI (src/praisonaiui, src/frontend, tests, docs). Do NOT expand into praisonaiagents or the monorepo unless the PR explicitly requires it. Read ALL comments above from Gemini, Qodo, CodeRabbit, and Copilot carefully before responding. Phase 1: Review per AGENTS.md
Phase 2: FIX Valid Issues Phase 3: Final Verdict |
Final review: PR #317 (
|
| # | Check | Result |
|---|---|---|
| 1 | Scope | Pass. Every file is Replay: features/replay.py, replay.js, replay-core.js, docs, and tests. |
| 2 | Network / exfiltration | Pass. There is one new local route, POST /api/replay/run, and it is registered only when the host passes a run callable. Every frontend fetch goes to the same origin under ${S.base}; there are no external hosts. The favicon is an inline data: URI. |
| 3 | Code execution | Pass. There is no eval/exec, subprocess or dynamic import. run(input, run_id) is a callable supplied by the host developer; it runs in a worker thread and is awaited with asyncio.run if it returns an awaitable. |
| 4 | Secrets | Pass. No new env-var or credential reads; it uses the existing traces-dir lookup. |
| 5 | Weakened controls | Pass, with notes. The Run route requires application/json, which blocks CSRF from cross-site forms. It caps input at 20k characters, allows one run at a time through a non-blocking lock (released in finally), and generates server-side run_ids with uuid. ?run= is checked with tj.is_valid_run_id, which a test covers with ../../etc/passwd. ?wait=1 only changes a 404 into an empty 200 and reveals nothing new. The page config and root_path go through _json_for_script, which escapes <, > and &, and the title goes through html.escape. The docstring says the app has no auth of its own and must stay on localhost or behind the host app's auth. That matches the existing Replay routes. |
| 6 | Supply chain | Pass. No manifests, lockfiles, CI or conftest changes. Starlette is already a dependency. |
| 7 | Obfuscation | Pass. No bidi or zero-width characters, encoded blobs or minified code. The only lines over 250 characters are docs table rows. |
| 8 | Tests | Pass. No tests were removed or loosened. The 10 new tests in tests/unit/test_replay_embed.py check real behaviour: the 415/400/409 responses, the lock being released after a runner fails, and async runners. |
| 9 | Backdoors | Pass. No magic values, no behaviour keyed on a user or host, no hidden routes. |
| 10 | Injection artifacts | Pass. No instructions to AI tools in code, comments, docs or test data. |
Deterministic risk flags: none were listed in the request. I scanned the added lines myself for innerHTML, eval, Function(, external URLs, postMessage and dynamic import( and found none.
Architecture (AGENTS.md)
- Backward compatible: the dashboard path changes only in the map node states and the Play button.
server.pyandcreate_app()are untouched. - Lightweight: no new dependencies. The new imports are stdlib only.
create_replay_apphas a live consumer in the companion PraisonAI PR (replay_app()/replay watch), and all four of its parameters are used by the page. - It belongs in this repo: it is UI and server code for an existing feature.
Tests run
node --test tests/js/replay-core.test.mjs: 40 passed.pytest tests/unit/test_replay*.py: 56 passed.- Full
tests/unit: 1537 passed and 23 failed. The failures, plus the 28 errors and 3 collection errors, come from packages missing in this runner:praisonaiagentsandmcp, plus one eval test that expectssdk_availablefrom the SDK. This PR does not touch that code. - Browser tests were not run.
Verdict
No blocking issues. It is safe to merge once a maintainer accepts the outsider-origin flag.
…n live, and embedding
- praisonai.replay.watch(target): serves a page with a Run button; each click
records the Agent/AgentTeam/AgentFlow/YAML/callable under a new run id and the
page draws it live (map: not reached / running / done). A team's tasks start
afresh each run (AgentTeam otherwise skips completed tasks).
- praisonai.replay.replay_app(target=None): the same page as an ASGI app to mount
in your own FastAPI/Starlette app; /replay/?run=<id> shows one run (iframe).
- CLI: praisonai replay watch [agents.yaml] ("show" was taken).
- examples/replay: watch_flow.py (no key), watch_team.py, embed_fastapi.py.
Needs aiui with create_replay_app (MervinPraison/PraisonAIUI#317); older aiui
gets a clear "pip install -U praisonai[ui]" error and the tests skip.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Lets people show agent progress inside their own app, and gives learners a click Run, watch each step page. Follow-up to #316.
What changes
Embed (
create_replay_app)app.mount("/replay", create_replay_app())./replay/?run=<id>shows only that run (no run list) and waits for it if it hasn't started yet, so it works in an<iframe>.run(input, run_id), the page shows a Run box.POST /api/replay/runstarts a run:render(el, {base, run, runner, approve})works under any mount prefix.Learning
Small fixes found while testing
events?wait=1: a run that hasn't started yet returns an empty answer instead of a 404 in the console.Weight
Tests
tests/js/replay-core.test.mjs: 40 pass, 4 of them new fornodeStates.tests/unit/test_replay_embed.py(new, 10 tests): the mount prefix, a pinned run, run-id validation, the Run route (415 / 400 / 409, a failing runner frees the lock, async runners), andwait=1.tests/unit: 1646 passed.RUN_BROWSER_TESTS=1, with axe): 19 passed, including a new end-to-end test. It clicks Run, sees a step drawn as running while later steps are not reached, waits for the run to complete, then presses Play and checks the map rewinds. axe finds no violations.<iframe>;AgentFlowwith four steps.The PraisonAI side (
praisonai.replay.watch()/replay_app()andpraisonai replay watch agents.yaml) is in a companion PR in MervinPraison/PraisonAI.🤖 Generated with Claude Code
Origin: outsider. Issue #316 was flagged as outsider input, so its text and comments are treated as untrusted data. The scope of fixes is limited to source and tests: no
.github/, dependency manifests, lockfiles, build or install files, orconftest.py, and no new network endpoints, telemetry, credential handling, eval/exec/subprocess or dynamic imports. The security review is in the PR comments.