Skip to content

Session replay: capture mode (auto/plan/YOLO) + approvals (count/decision/edit-diff) + wire transcript viewer #4814

Description

@vivekchand

Context

Today the session UI shows nothing about how a session ran:

  • Auto vs interactive vs plan vs --dangerously-skip-permissions — Claude Code emits {"type":"permission-mode","permissionMode":"default"} on every session (verified in ../clawmetry-pro/tests/fixtures/runtimes/claude_code/…jsonl:1), but _parse_v3_event at clawmetry/sync.py:3186 never consumes it. No sessions column stores it. grep permissionMode|acceptEdits|bypassPermissions|autoAccept across clawmetry/, routes/, app.js returns zero hits.
  • Approvals asked count — the approvals DuckDB table (clawmetry/local_store.py:610) IS populated by the sync daemon (sync.py:7346). /api/session-governance/<sid> (routes/sessions.py:2887) returns {approvals[], decision_count, denied_count}. /api/session-insight/<sid> folds it into governance = {decision_count, denied_count}. The transcript viewer never fetches either endpoint.
  • User replies to approvalsapprovals.decision + decision_reason + resolver are stored, rendered only in the NemoClaw tab (app.js:8177).
  • "Edit" approvals — where the user modifies the tool args before approving (a real Claude Code flow) — has no column and no ingest path.

Deliverables

1. Ingest mode + policy signals

  • clawmetry/sync.py _parse_v3_event — consume permission-mode, mode, permissionMode (Claude Code); turn_context.approval_policy + sandbox_policy + permission_profile (Codex); session.auto_mode_resolved (Copilot); exec_approvals_config (OpenClaw); steps.permissions per step (Antigravity).
  • New sessions columns: mode_permission, mode_sandbox, mode_collaboration, approval_policy_default, resolved_at (latest resolved timestamp — modes can change per turn in Codex + Claude Code Plan Mode).
  • Per-turn mode changes stored as kind=mode.changed events in the new replay_events table (see OSS-01).

2. Extend approvals capture

  • Add edit_diff JSON column to approvals table — {before: tool_args, after: tool_args} captured whenever the user modifies the tool_use payload before approving. Claude Code's toolDenialKind maps to decision=denied; a subsequent tool_use with different args after the same sourceToolUseID maps to decision=edited with edit_diff.
  • Capture apiRefusalCategory / apiRefusalExplanation for model-side refusals as resolver=model.
  • Capture hookInfos / hookErrors / hookAdditionalContext for hook-driven auto-decisions as resolver=hook.

3. Wire the transcript viewer

  • /api/replay-tree/<id> (from OSS-01) includes approvals[] per turn.
  • New UI in clawmetry/static/js/app.js:
    • Session-level mode chip in the transcript header — auto / plan / interactive / YOLO — with a tooltip listing sandbox + collaboration.
    • Per-turn mode marker — small badge on turn chapters where mode changed (Codex per-turn, Claude Code Plan Mode toggles).
    • Approvals rail running down the right side of the transcript, one row per approval, click to jump to the tool_use it gated. Colors: green approved, yellow edited, red denied, gray timeout.
    • Per-turn approvals count on the TURNS TOC (next to the existing 🔧 N / ✕ N).
    • Inline approval bubble rendered above the gated tool_use chip, showing prompt (if captured — see hook-lifecycle issue), decision, decision_reason, resolver, edit_diff.

Acceptance

  • Every ingested session has mode_permission populated (or explicitly unknown for runtimes without a signal — see per-runtime issues)
  • /api/replay-tree/<id> returns approvals[] matching what /api/session-governance/<id> returns today
  • Opening a session with ≥1 approval renders the approvals rail; clicking scrolls to the gated tool
  • Fixture session with an edited approval shows the diff (before/after tool args)
  • Sessions with bypassPermissions/--dangerously-skip-permissions show a red YOLO chip

Depends on

  • OSS-01 (schema)

Blocks

  • Every per-runtime mapper issue (they need the columns + endpoint contract to write into)

Gotchas

  • Approval outcome ≠ approval prompt. Almost every runtime persists only the decision, not the prompt text the user saw. Rebuilding the prompt for replay needs a runtime hook (see hook-lifecycle issue).
  • Auto mode is per-turn (Codex, Claude Code), per-session (Copilot, OpenClaw), or per-project (opencode permission table). A single session-level "auto" chip is misleading — badge at the turn level where the runtime supports it.
  • Do not conflate mode.changed events across runtimes with different vocabularies — keep the raw runtime value in payload, only normalize the top-level enum.

References

  • clawmetry/local_store.py:610 (approvals schema)
  • clawmetry/sync.py:7346 (approvals ingest today)
  • clawmetry/sync.py:3186 (_parse_v3_event — the seam to extend)
  • routes/sessions.py:2887 (/api/session-governance/<sid> — already returns most of what we need)
  • routes/sessions.py:2905 (/api/session-insight/<sid> — folds governance into waste_flags)
  • clawmetry/static/js/app.js:8177 (NemoClaw approvals renderer — reference implementation for the bubble)

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions