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)

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions