Repository navigation
feat(replay): PraisonAI Replay page (timeline, map, live, why, approvals) + trace-reader and UI-approval fixes - #316
Conversation
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>
|
@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 #316 (PraisonAI Replay)Verdict: ready to merge from an architecture and security standpoint. No code changes were pushed and no blocking issues remain. Reviewer feedbackThe 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)
Architecture (AGENTS.md)
Security checklist (outsider origin)
XSS: the view builds the DOM with Minor, non-blocking: Files modified by this reviewNone. I only added the required Origin: outsider section to the PR description. |
Summary
PraisonAI Replay in aiui: a page that shows how PraisonAI agents work together.
~/.praison/traces/*.jsonl), so there's no account and no cloud.Open it with
praisonai replay view(MervinPraison/PraisonAI#6011), or directly at/replay?run=<id>.What changes
features/replay.py+features/_trace_jsonl.pyPRAISONAI_TRACES_DIR, then$PRAISON_HOME/traces, then~/.praison/traces.features/tracing.pypraisonaiagents.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, andhealth()reports which source it used.features/approvals.pyapproval_idand denies on timeout (fail-closed). The approve endpoint acceptsmodified_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/_denyare minimal so #312 rebases trivially.views/replay-core.js+views/replay.jsdashboard.jsicon_svgfor page icons.replay_app.pypraisonai replay view).docs/adr/0001-replay-view-clean-room.mddocs/features/replay.mdReplay API:
GET /api/replay/runsand/runs/{id}/events?after_seq=N;/runs/{id}/stream: it follows the file as it grows, honoursLast-Event-ID, and tolerates a half-written last line;The page:
window.__REPLAY_DATA__(used byreplay 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#189AB4under text). No new dependencies.Tests
node --test tests/js/replay-core.test.mjs: 36 passed (model building, narration, map layout).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:tests/unit tests/integrationcompared by test id with main: 0 new failures (the same 15 pre-existing failures on both).praisonai replay view; screenshots were reviewed in light, dark, map, why and phone views.Related
record()and export: feat(replay): praisonai replay view/export/rerun, record(), OTel and self-contained HTML export PraisonAI#6011.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 orconftest.pychanges, 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