Skip to content

feat(replay): PraisonAI Replay page (timeline, map, live, why, approvals) + trace-reader and UI-approval fixes - #316

Merged
MervinPraison merged 8 commits into
mainfrom
feat/replay-view
Oct 26, 2026
Merged

MervinPraison merged 8 commits into
mainfrom
feat/replay-view

Conversation

@MervinPraison

@MervinPraison MervinPraison commented Oct 26, 2026 •

Copy link
Copy Markdown
Owner

Summary

PraisonAI Replay in aiui: a page that shows how PraisonAI agents work together.

  • It plays live while a run is going and replays the run afterwards.
  • It reads the trace files the SDK writes (~/.praison/traces/*.jsonl), so there's no account and no cloud.
  • The design is our own: a Timeline with swimlanes plus a Map, light theme first. It is not a node canvas.

Open it with praisonai replay view (MervinPraison/PraisonAI#6011), or directly at /replay?run=<id>.

What changes

Area Change
features/replay.py + features/_trace_jsonl.py The feature's API (details below). Reads the traces directory: PRAISONAI_TRACES_DIR, then $PRAISON_HOME/traces, then ~/.praison/traces.
features/tracing.py Bug fix. It looked up praisonaiagents.trace.list_traces/list_spans/get_trace, which don't exist in the SDK, and silently fell back to in-memory data. It now reads the trace files, and health() reports which source it used.
features/approvals.py Bug fix. The UI approval backend returned "Awaiting UI approval" (a denial) immediately. It now waits for the person's decision per approval_id and denies on timeout (fail-closed). The approve endpoint accepts modified_args, so you can edit the arguments before approving. A synchronous request made on the event loop is denied rather than freezing the server. Edits to _approve/_deny are minimal so #312 rebases trivially.
views/replay-core.js + views/replay.js Plain-JS view; no React and no CDN. See "The page" below.
dashboard.js Fixes a phone-layout bug that hit every page: a hidden sidebar still took 260 px. Adds an optional sanitised icon_svg for page icons.
replay_app.py Entry point for the wrapper (praisonai replay view).
docs/adr/0001-replay-view-clean-room.md Clean-room record (details below).
docs/features/replay.md User guide.

Replay API:

  • GET /api/replay/runs and /runs/{id}/events?after_seq=N;
  • a live SSE stream at /runs/{id}/stream: it follows the file as it grows, honours Last-Event-ID, and tolerates a half-written last line;
  • run ids are validated, so path traversal gets a 404.

The page:

  • Timeline: one lane per agent, plus Manager and You lanes. Tool calls sit in their own row, and hand-off/delegate arrows connect lanes. Parallel branches get sub-lanes, loops are shown as ×N, and task chapters run along the top.
  • Narration: one plain sentence per event, from fixed templates (no LLM), synced with the timeline.
  • Drawer: What happened / Why (recorded evidence only) / Raw JSON.
  • Overlays and maps: a cost and latency overlay, and a Map that fits on open, with an "Outside the team" group.
  • Approvals: approve, deny, or edit arguments from the You lane.
  • Theme: follows the system setting (System/Light/Dark menu), and works at 400 px wide.
  • Standalone mode: boots from window.__REPLAY_DATA__ (used by replay export --format html).

Clean-room record: inspirations were taken as ideas only. No third-party code, CSS, icons, layouts or screenshots were used, and LangGraph's Elastic-2.0 server and proprietary Studio were not used for code. It also documents the palette rationale (WCAG: tools use #0F7287, never #189AB4 under text). No new dependencies.

Tests

  • pytest: 69 new tests (feature, tracing, approvals, view registration). The original set fails or errors on main.
  • node --test tests/js/replay-core.test.mjs: 36 passed (model building, narration, map layout).
  • Browser tests (RUN_BROWSER_TESTS=1, Playwright + axe-core, dev-only): 18 passed, with no WCAG 2.0/2.1/2.2 A/AA violations. They also check:
    • blocks never overlap;
    • the Why tab works by role and keyboard;
    • the system theme can be overridden;
    • the map fits and shows the outside group;
    • 400 px has no page scroll;
    • the exported HTML boots in both script orders.
  • Full tests/unit tests/integration compared by test id with main: 0 new failures (the same 15 pre-existing failures on both).
  • Real-run check: a gpt-4o-mini team plus a Triage→Billing hand-off was recorded by the SDK PRs and served by praisonai replay view; screenshots were reviewed in light, dark, map, why and phone views.

Related

Origin: outsider

The review request for this PR referenced #312 (flagged origin/outsider) and #189 (opened by an account without write access). Their content was treated as untrusted data. So this PR is limited to source, tests and docs: it adds no .github/, dependency manifest, lockfile, build/install or conftest.py changes, no network endpoints beyond the local read-only /api/replay/* routes, no telemetry or credential handling, and no eval/exec/subprocess or dynamic imports. The merge gate's independent security review still applies.

🤖 Generated with Claude Code

MervinPraison and others added 8 commits October 26, 2026 11:36
New `replay` feature (contract v1 §5), read-only:
- GET /api/replay/runs: run-picker rows, newest first, with a live flag
- GET /api/replay/runs/{id}/events?after_seq=N: {events, graph, run}
- GET /api/replay/runs/{id}/stream: SSE tail of the JSONL (stdlib seeks),
  `ctx` events with id=sequence_num, ping every 15 s, `end` after the run
  finishes plus 2 s quiet; honours after_seq and Last-Event-ID
- ids must match ^[A-Za-z0-9._-]+$ and resolve inside the traces dir
- traces dir: PRAISONAI_TRACES_DIR > PRAISON_HOME/traces > ~/.praison/traces
- half-written last lines, unknown fields/types and schema 1.0 are tolerated

Plain-JS view `replay` (no React, no CDN): replay-core.js holds pure
buildModel/narrate/layoutMap/deriveGraph (node --test); replay.js draws the
timeline (agent, Manager and You lanes, LLM/tool blocks, hand-off arrows,
parallel sub-lanes, xN repeats, task/step chapters, scrubber, live follow),
the narration list, What happened/Why/Raw JSON drawer, cost/latency
overlay, map view, and Approve/Deny/Edit-args for pending approvals.
Registered in BUILTIN_VIEWS and as a Control page; /#/replay?run=<id> deep
links resolve to /replay?run=<id>. It also boots standalone from
window.__REPLAY_DATA__; standalone_html() inlines both files for exports.

praisonaiui/replay_app.py + configure_replay_app() open aiui straight on
Replay for `praisonai replay view`.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
SDKTracingManager looked up praisonaiagents.trace.list_traces, list_spans
and get_trace, which do not exist, so /api/traces never showed a recorded
run and silently fell back to an empty in-memory store.

JsonlTracingManager reads the same directory as Replay: one file is one
trace, spans come from start/end pairs (parent_seq on schema 1.1, sequence
order on 1.0), and records posted to /api/traces/record still work.
health() reports source: jsonl|memory. SDKTracingManager stays as a
deprecated alias.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
_UIStoreBackend returned "Awaiting UI approval" (a denial) immediately, so
any tool needing approval was always denied and the UI's Approve button
never reached the agent. It also dropped request.approval_id and any
edited arguments.

- each request is stored under request.approval_id and the caller blocks
  until /api/approvals/{id}/approve|deny resolves it: sync callers on a
  threading.Event, async callers on a future of their own loop
- no answer before the timeout (registry timeout, default 300 s) is a
  denial and the entry is resolved as denied (fail-closed)
- a sync call made on an event-loop thread is denied at once rather than
  freezing that loop
- POST /approve accepts modified_args (object) and the decision carries
  it as ApprovalDecision(modified_args=...); the handler change is two
  lines so the auth checks in #312 rebase cleanly

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
docs/features/replay.md describes the page, keys, approvals, export and
HTTP API. docs/adr/0001-replay-view-clean-room.md records the ideas we
took (graph plus trace, score-like swimlanes, OTel GenAI vocabulary), that
no third-party UI code, CSS, icons, layouts or screenshots were used, and
the palette, accessibility and licence reasoning.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The fit-to-width minimum (480 px of plot) made the timeline overflow in
the dashboard's narrower content column; lower it to 260 px. Pending
approvals and running blocks showed "Duration 0 ms"; show "still waiting"
or "still running" instead.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… export boot

From a review against real recorded runs:

- Timeline: an agent's LLM span encloses its tool calls in real runs, so
  tool blocks covered the LLM label. Each lane now has an LLM row with a
  thinner tool row under it, blocks are packed into tracks in pixel space
  so nothing overlaps, and labels are ellipsized; the full label is in
  the title, the accessible name and a hover/focus tip. Visible labels
  sit in a decorative layer so names never contain a cut-off word.
- Theme: defaults to the system setting (prefers-color-scheme, tracked
  live in CSS) with a System / Light / Dark menu as an override.
- Map: fits the whole graph on open (shares -/Fit/+), drag to pan, never
  below a readable scale, and gets the full width with the step list
  underneath. Agents seen in events but not in the graph (e.g. a
  hand-off to an agent outside the team) are added by observedGraph()
  and laid out by layoutRunMap() as an "Outside the team" group.
- Phone: 16 px gutter, stacked layout, no horizontal page scroll.
- Standalone boot waits for DOMContentLoaded so the export works with
  either script order, takes data.run_id as the title, and keeps the
  documented #replay-root / window.__REPLAY_DATA__ contract.
- New fixture team_outside.jsonl with the real-run shape; node tests
  for observedGraph/layoutRunMap; browser tests for no-overlap, the Why
  tab by role and keyboard, system theme, map fit, phone width and the
  wrapper-style export in both script orders.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…mark

- At <=1023 px the hidden sidebar still took 260 px of the flex row,
  because buildSidebar() sets an inline position:relative that beat the
  mobile position:fixed; and the shell header slot took another 53 px.
  Every page's content was squeezed to a few pixels at phone width. The
  sidebar is now fixed (!important), the header slot gets its own row,
  and phones get a 16 px gutter.
- Pages may set icon_svg next to the text icon. The sidebar and page
  header parse it as SVG and drop it if it holds scripts, foreignObject,
  on* handlers or javascript: URLs; other UIs keep using `icon`.
- Replay's heading uses its own inline mark (three lanes and a cursor)
  instead of the emoji, and its description no longer says "why" (it
  made Playwright's text=Why match the page header, not the tab).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has insufficient remaining quota.

@MervinPraison

Copy link
Copy Markdown
Owner Author

@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

  1. Protocol-driven: check heavy implementations vs core SDK
  2. Backward compatible: ensure zero feature regressions
  3. Performance: no hot-path regressions (lazy imports, import-time <200ms)
  4. Product value: review in depth whether the change genuinely adds PraisonAIUI UX value — never add features for the sake of adding them. It must strengthen the UI (simpler, more user-friendly, robust). If it does not clearly add value, request changes or recommend rejecting/closing rather than merging scope creep.
  5. Layering: UI/frontend/server features here; core agent runtime belongs in praisonaiagents (PraisonAI monorepo). Agent-callable tools → PraisonAI-Tools. Do not pull monorepo or SDK scope into this repo unless the PR explicitly requires it.
  6. Do not bloat server.py or create_app() with new params — prefer existing YAML config, plugins, and feature modules; only add params if absolutely required. Reject knobs/exports with no live consumer.

Phase 2: FIX Valid Issues
5. For any VALID bugs or architectural flaws found by Gemini, CodeRabbit, Qodo, Copilot, or any other reviewer: implement the fix
6. Push all code fixes directly to THIS branch (do NOT create a new PR)
7. Comment a summary of exact files modified and what you skipped

Phase 3: Final Verdict
8. If all issues are resolved, approve the PR / close the Issue
9. If blocking issues remain, request changes / leave clear action items

@praisonai-triage-agent

Copy link
Copy Markdown
Contributor

Final review: PR #316 (PraisonAI Replay)

Verdict: ready to merge from an architecture and security standpoint. No code changes were pushed and no blocking issues remain.

Reviewer feedback

The only bot output is Copilot's "unable to review (quota)". Gemini, Qodo and CodeRabbit left no comments, so there were no reviewer findings to fix.

Tests (run locally on this branch)

Check Result
New pytest files (feature, view, tracing, approvals) 69 passed
node --test tests/js/replay-core.test.mjs 36 passed, 0 failed
ruff check on the changed Python files clean
Full pytest tests/unit tests/integration 1826 passed, 8 failed. main has the same 8 failures (test_serve_help, 6× test_endpoints that only fail when the full suite runs, and test_no_server_errors because no OPENAI key is set). 0 regressions.
CI frontend (failing) The TypeScript errors are in src/frontend/src/{chat,hooks,modals,pages}, which this PR doesn't touch. The same job also fails on main (last 3 runs).

Architecture (AGENTS.md)

  • Layering: a read-only trace reader and a UI view. Both belong in aiui; the SDK side lives in PraisonAI#6010 and #6011. No monorepo scope is pulled in.
  • server.py: one built-in page entry and one API override; no new create_app() params. configure_replay_app() uses the existing set_style/set_pages.
  • Deps and perf: no new dependencies. The view is plain JS, with no CDN or framework. The SDK import in approvals.py stays lazy.
  • Value: the tracing.py fix (it called SDK functions that don't exist) and the UI approval fix (it denied immediately instead of waiting for the person) are real bug fixes. Replay itself adds a clear UX win.
  • Scope note (not blocking): this is a large bundled PR (feature + 2 fixes + a dashboard phone-layout fix). Splitting it would have been cleaner, but each part serves the Replay page.

Security checklist (outsider origin)

  1. Scope: every file serves Replay, its approvals lane, the trace reader, or its docs and tests. No unrelated changes.
  2. Network: no outbound calls. New routes are local, read-only GET /api/replay/*. The front end only fetches same-origin relative URLs (/api/replay/..., /api/approvals/...) and opens an SSE stream to the same origin. tests/browser uses socket only to pick a free port on 127.0.0.1.
  3. Code execution: no eval/exec, subprocess, dynamic imports, pickle or yaml. The python -c in the replay_app.py docstring is usage documentation and is never executed.
  4. Secrets: the only env reads are PRAISONAI_TRACES_DIR/PRAISON_HOME (a directory path). The browser test unsets provider keys.
  5. Weakened controls: none. Run ids are checked against ^[A-Za-z0-9._-]+$, with ./.. rejected and a resolve().parent == base check, so traversal and symlink escapes get 400/404. Approvals fail closed: a timeout means deny, and so does a sync call made on an event loop. modified_args must be a dict and only goes through the existing approve endpoint, which has the same access as before.
  6. Supply chain: no changes to .github/, pyproject.toml, lockfiles, conftest.py, entry points or .pth files.
  7. Obfuscation: no bidi or zero-width characters. The only long lines are readable JSONL test fixtures and one docs table row.
  8. Tests: only additions; none removed or loosened.
  9. Backdoors: no hard-coded credentials or behaviour tied to specific users, hosts or dates.
  10. Injection artifacts: none found. The words "reviewer"/"approved" appear only in fixture narrative data ("Draft approved by reviewer score 0.92").

XSS: the view builds the DOM with textContent and SVG APIs, never innerHTML with run data. standalone_html escapes <, >, &, U+2028 and U+2029 in the inline JSON and </script in the inlined JS.

Minor, non-blocking: safeSvgIcon() in dashboard.js uses a blocklist (script, foreignObject, on*, javascript:). It doesn't block an <image href="https://…"> loading a remote image. icon_svg comes from server-side page config written by the app author, not from users, so this is acceptable. An allowlist (svg/g/path/rect/circle) would be stricter if this field is ever exposed more widely.

Files modified by this review

None. I only added the required Origin: outsider section to the PR description.

@MervinPraison MervinPraison added pipeline/blocked:ci Blocked: CI not green on HEAD pipeline/blocked:manual-review Blocked: requires manual review pipeline/blocked:cooldown Blocked: post-push or @claude cooldown pipeline/reviews-pending Waiting for CodeRabbit/Qodo/Copilot reviews labels Oct 26, 2026
@MervinPraison
MervinPraison merged commit bf98d29 into main Oct 26, 2026
16 of 17 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

origin/outsider pipeline/blocked:ci Blocked: CI not green on HEAD pipeline/blocked:cooldown Blocked: post-push or @claude cooldown pipeline/blocked:manual-review Blocked: requires manual review pipeline/reviews-pending Waiting for CodeRabbit/Qodo/Copilot reviews

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants