Skip to content

feat: status sidebar with live session facts - #87

Open
tcsenpai wants to merge 48 commits into
patoles:mainfrom
tcsenpai:feat/status-sidebar
Open

tcsenpai wants to merge 48 commits into
patoles:mainfrom
tcsenpai:feat/status-sidebar

Conversation

@tcsenpai

Copy link
Copy Markdown

Stacked on #82 (session manager), #84 (collisions) and #85 (dead letters).

Everything in the canvas is small and transient; this adds a right-hand sidebar (300px, 12px type) with the facts you keep wanting about the selected session:

  • Session: folder, active/idle, started and last activity, tokens and estimated cost.
  • Agents: every agent with model (colored by family), state and tokens, indented by nesting depth.
  • Tool calls: running / completed / failed; failed opens the dead-letter panel.
  • Hot files: the five files that consumed the most context, with reads, writes and tokens; opens the Files panel.
  • Same file, same time: cross-session collisions from feat: same-file collision trails across agents and sessions #84, with a chip per session to jump to it.

It hides while Files / Chat / Dead letters occupy the right edge, and in demo mode. Replaces the earlier "health verdict" experiment (#86), which I am closing: the loop/error heuristics were more noise than signal.

https://claude.ai/code/session_01AGs5TZo6yxv7NHARHawyJf

Sessions from different projects all showed only their first user message,
making tabs ambiguous in the standalone app and in multi-workspace setups.

- Plumb the session cwd through SessionInfo and lifecycle events for all
  three sources (Claude JSONL watcher, Codex rollout watcher, dev relay).
- Session tabs prefix the label with the folder name when running standalone
  or when sessions span more than one cwd; single-workspace VS Code is unchanged.
- Right-click a tab to rename it inline (Enter saves, Esc cancels, empty
  resets). Names persist in localStorage keyed by session id.

Claude-Session: https://claude.ai/code/session_01AGs5TZo6yxv7NHARHawyJf
Attaching to a session that already had activity only showed the text of the
current turn: tool calls, subagent spawns and earlier messages were pre-scanned
for dedup and never emitted.

- prepareBackfill() splits existing content: older turns are pre-scanned only,
  the last BACKFILL_TURNS (50) user turns are kept as raw lines.
- replayLines() runs those lines through the normal live path, with the
  session clock pinned to each entry's timestamp (WatchedSession.replayNow),
  so durations and the timeline reflect real timing. Session start is set to
  the first transcript entry for the same reason.
- Applies to both the VS Code watcher and the standalone relay. Codex already
  drained the full rollout on attach and is unchanged.
- Audio stays quiet during a replay burst.

Claude-Session: https://claude.ai/code/session_01AGs5TZo6yxv7NHARHawyJf
Adds a ☰ button in the top bar that opens a modal listing every tracked
session: live indicator, display name, folder and full cwd, status and last
activity. Click opens the session, double-click or ✎ renames it inline, ✕
dismisses it, a filter box narrows by name or path, Esc or backdrop closes.

Custom names move into a shared useSessionNames hook (localStorage) and the
label helpers into lib/session-label.ts, so tabs and modal stay in sync.

Claude-Session: https://claude.ai/code/session_01AGs5TZo6yxv7NHARHawyJf
# Conflicts:
#	extension/src/protocol.ts
…ssion to new SSE clients

- Session clock now starts at the first transcript line that carries a
  timestamp; ai-title/mode/summary lines have none, which left the clock at
  'now', made elapsed() negative and collapsed every replayed event onto one
  instant in the frontend.
- The relay replayed buffered events only for the most recent session on SSE
  connect, so every other tab opened empty. It now sends each session's buffer;
  the frontend already buffers per session and flushes on selection.

Claude-Session: https://claude.ai/code/session_01AGs5TZo6yxv7NHARHawyJf
Real timestamps made a week-old session produce a 10000-minute timeline that
was almost entirely dead time. Gaps between replayed entries longer than
MAX_REPLAY_GAP_MS (30s) are now shortened to that length; the removed time is
tracked in WatchedSession.compressedMs and subtracted by elapsed() for live
events too, so the timeline stays continuous after the replay.

Claude-Session: https://claude.ai/code/session_01AGs5TZo6yxv7NHARHawyJf
… live events

- The context_update emitted before the replay used an uncompressed elapsed()
  ('now' minus a days-old start), which pushed the frontend clock past every
  replayed event and collapsed them again. It is now emitted after the replay.
- advanceClock() replaces the ad-hoc gap logic: it is called per replayed entry,
  once at the end of the replay (gap between last entry and now), and whenever
  live lines arrive, so idle pauses are compressed the same way live.

Claude-Session: https://claude.ai/code/session_01AGs5TZo6yxv7NHARHawyJf
Long sessions replay only the last BACKFILL_TURNS, but the clock starts at the
first transcript timestamp; the gap up to the first replayed entry was never
compressed because the compression clock had no previous event. Seed it at
session start.

Claude-Session: https://claude.ai/code/session_01AGs5TZo6yxv7NHARHawyJf
Claude Code's Workflow tool writes its agents' transcripts one level deeper
than regular subagents (subagents/workflows/<run-id>/agent-*.jsonl) and
spawns them via a Workflow tool_use rather than Agent/Task, so they never
appeared on the canvas. scanSubagentsDir now also scans each workflows/<run>
directory; names come from the .meta.json description as usual.

Claude-Session: https://claude.ai/code/session_01AGs5TZo6yxv7NHARHawyJf
Continuous auto-fit fights the user once the graph has many agents: it
re-engaged on every selection change and after every manual fit. Auto-fit
is now a persistent toggle (top bar + context menu, localStorage), off by
default. Shift+F and 'Zoom to Fit' stay as one-shot fits. Cmd/Ctrl+wheel
zoom and drag pan are unchanged.

Claude-Session: https://claude.ai/code/session_01AGs5TZo6yxv7NHARHawyJf
Orchestrator stays a hexagon; depth 1 is a pentagon, depth 2 a square,
depth 3+ a triangle. Agent.depth is set at spawn from the parent.

Claude-Session: https://claude.ai/code/session_01AGs5TZo6yxv7NHARHawyJf
Fable/Mythos azure, Opus orange, Sonnet white, Haiku green (colors live in
CLAUDE_FAMILIES next to context and cost). Error, paused and
waiting-permission keep their state colors so alerts stay visible.

Claude-Session: https://claude.ai/code/session_01AGs5TZo6yxv7NHARHawyJf
# Conflicts:
#	web/components/agent-visualizer/index.tsx
Audit findings applied:
- lineTimestamp() now parses the top-level timestamp of user/assistant/
  progress/system lines only. file-history-snapshot lines carry a nested,
  later timestamp that pinned the session start hours off and gave the whole
  replay negative times.
- Replay clock never moves backwards (transcript lines are not strictly
  ordered) and advanceClock() ignores timestamps in the future.
- elapsed() is monotonic per session so timer- and subagent-driven events
  cannot land before the last transcript event.
- No per-line context_update during replay; one is emitted at the end.
- The relay ships a replay as a single agent-event-batch frame instead of
  thousands of SSE messages that trickled onto the canvas like playback.
- Buffer trimming keeps agent_spawn/subagent_dispatch/model_detected so a
  late client never receives events for unknown agents.
- SSE frames carry ids; a reconnecting client (Last-Event-ID) gets only what
  it missed, or a reset when the relay was restarted, instead of duplicated
  history and doubled token totals.

Claude-Session: https://claude.ai/code/session_01AGs5TZo6yxv7NHARHawyJf
Workflow tool agents (and any subagent whose run was killed) never get a
tool_result in the parent transcript, so they stayed 'working' forever.

Claude-Session: https://claude.ai/code/session_01AGs5TZo6yxv7NHARHawyJf
- 'session-started' re-sent for the already-selected session (resume after
  inactivity) armed sessionSwitchPendingRef with nothing to clear it, so no
  event reached the canvas until the user switched tabs. Same-id 'started'
  now only updates the session list.
- Live events arriving while part of the eventLog was still un-replayed
  (review playback, restored snapshot) snapped eventIndex to the end and
  skipped those events forever. They are applied first now.
- restoreSnapshot forced isPlaying on a paused/scrubbed snapshot, which
  played the remaining log at 1x under a LIVE badge. It now fast-forwards
  to the end of the log, and the tab switch leaves review mode.
- Cached snapshots are dropped on a bridge reset.
- lastActivityTime is refreshed (throttled) from incoming events.

Claude-Session: https://claude.ai/code/session_01AGs5TZo6yxv7NHARHawyJf
Export button in the top bar replays the whole timeline from t=0 at the
current speed while a MediaRecorder captures canvas.captureStream() plus the
AudioEngine output, then downloads the file. MP4 (H.264/AAC) where the
browser can encode it, WebM otherwise.

- Idle stretches longer than 10s of sim time are replaced by a 2s caption
  card ('… 3m 20s later …') drawn into the canvas, then the clock skips to
  the next event (new skipTo() on the simulation).
- Sounds are recorded even when the speakers are muted: mute now sits on a
  separate output stage and the recorder taps the mix before it.
- Review-mode audio gating is bypassed during an export.
- Click the button again to cancel (nothing is saved).

https://claude.ai/code/session_01AGs5TZo6yxv7NHARHawyJf
# Conflicts:
#	web/components/agent-visualizer/index.tsx
#	web/components/agent-visualizer/top-bar.tsx
# Conflicts:
#	web/components/agent-visualizer/top-bar.tsx
- Session labels were truncated server-side at 14 chars, which made tabs
  unreadable; the limit is now 60 and the tab truncates with an ellipsis.
- Tabs are larger (11px, more padding, max 260px), show the folder as a
  small chip, glow when selected, and dim when the session is idle.
- Tabs are ordered by last activity, most recent first.
- A newly started session no longer steals the selection when the user is
  already looking at one; auto-select only happens when nothing is selected.

Claude-Session: https://claude.ai/code/session_01AGs5TZo6yxv7NHARHawyJf
# Conflicts:
#	web/hooks/use-vscode-bridge.ts
A shared FileCollisionIndex records every file-touching tool call per
normalized path in a 90s sliding window. When two distinct parties (session +
agent) touch the same file and at least one writes, a file_collision event
is emitted to every involved session. Read-vs-read is ignored on purpose.

- Same session: a dashed pheromone-style trail between the involved agent
  nodes with the file name at the centroid, fading over the window.
- Different sessions: a strip under the top bar naming the file and the
  sessions, with ✎/👁 for write/read; click a session to jump to it.
- Replay-aware: uses the replay clock during backfill so historical
  collisions are detected with the right timing.

Stacked on the backfill branch (needs WatchedSession.replayNow).

https://claude.ai/code/session_01AGs5TZo6yxv7NHARHawyJf
# Conflicts:
#	web/components/agent-visualizer/canvas.tsx
#	web/components/agent-visualizer/index.tsx
… relic

- Dead letters: a panel (✕ N in the top bar) listing every failed tool call
  with agent, tool, args, first error line, and what the failure cost: the
  number of later identical calls by the same agent and their token cost.
  Click a row to select the tool call on the canvas.
- Compaction: the parser recognizes the continuation summary Claude Code
  writes after a compaction, resets the session's context accounting to
  system prompt + summary, and emits context_compacted. The canvas shows a
  bubble with before → after tokens, the timeline gets a 'Compacted' block,
  and the transcript keeps the summary as a relic.

https://claude.ai/code/session_01AGs5TZo6yxv7NHARHawyJf
# Conflicts:
#	extension/src/protocol.ts
#	web/components/agent-visualizer/index.tsx
#	web/lib/agent-types.ts
…p bar

assessHealth() looks at the last 20 tool calls of a session: the same
tool+args repeated 4× warns and 6× is bad; 3 consecutive errors of one tool
is bad, 3 of the last 10 failing warns, 5 is bad. The parser emits
session_health only when the verdict changes and logs the raw metrics at
debug level. The tab dot turns orange/red with the reason in the tooltip,
the session manager shows the reason, and the top bar shows it for the
selected session. Context-window pressure is left to the frontend, which
knows the model's window. Also moves the collision strip to the bottom-left
so it never overlaps the tabs.

https://claude.ai/code/session_01AGs5TZo6yxv7NHARHawyJf
The health verdict (loops/errors → warn/bad) produced more noise than
signal and is removed entirely: no session_health event, no recentTools,
no tab/modal coloring.

In its place a right-hand sidebar (300px, 12px type) shows what is actually
useful about the selected session: folder, status, started / last activity,
tokens and estimated cost; every agent with model, state and tokens, indented
by depth; tool calls running / completed / failed (click → dead letters); the
five hottest files with reads, writes and tokens (click → Files panel); and
the same-file collisions across sessions. It hides while a side panel is open
and in mock mode.

https://claude.ai/code/session_01AGs5TZo6yxv7NHARHawyJf
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant