Skip to content

feat(webui): restore sessions from display replay - #567

Merged
delete-cloud merged 7 commits into
mainfrom
feat/webui-session-recovery
Jun 12, 2026
Merged

delete-cloud merged 7 commits into
mainfrom
feat/webui-session-recovery

Conversation

@delete-cloud

@delete-cloud delete-cloud commented Jun 12, 2026 •

Copy link
Copy Markdown
Owner

Summary

  • add webui session list, session summary loading, full history replay through durable run display-events, and restored-session continuation
  • use /prompt for completed restored sessions, reserve /resume for latest interrupted runs, and guard session-load/create/reconnect races so stale async work cannot overwrite the active session
  • persist approval decisions as wire.ApprovalResponse runtime events so restored history replays approval_result and does not resurrect approved prompts as pending
  • guard both live /sessions/{id}/display-events and legacy /sessions/{id}/events streams with the same owner-scoped visibility check used by replay endpoints
  • preserve direct-call compatibility for event route tests by normalizing un-injected FastAPI dependency defaults at the route boundary
  • render assistant markdown with react-markdown and replace ad hoc diff text with a lightweight structured diff/patch panel
  • document the session runs replay contract used by GUI history restore

Verification

  • pnpm test -- src/App.test.tsx
  • pnpm typecheck
  • pnpm test
  • pnpm build
  • pnpm peers check
  • git diff --check
  • uv run pytest tests/ui/test_http_server_failover.py -q
  • uv run pytest tests/ui/test_session_manager_runtime.py -k "approval" -q
  • uv run pytest tests/ui/test_http_server.py -k "session_events_rejects_non_owner or session_display_events or approve_success or approve_retry or wait_for_approval_request_can_be_approved_via_http_endpoint" -q
  • uv run pytest tests/approval/test_coordinator.py tests/approval/test_store.py -q
  • Browser/DevTools real ask-mode E2E with DeepSeek: new session -> prompt requiring bash_run -> approval card -> approve -> tool result/final answer -> reload tab -> restore from session list via display-events -> approval card replayed as -> approved without buttons -> continued conversation uses /prompt?event_format=display
  • Local subagent review fallback: final focused review at head b684fa5 confirmed the previous direct-call P2 is fixed and found no new P1/P2 findings; earlier local and CodeRabbit owner-scope findings were fixed and re-reviewed clean

Notes

@coderabbitai

coderabbitai Bot commented Jun 12, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

This PR extends the coding agent WebUI with deterministic session history reconnection, multi-session management with resume mode, and interactive approval handling. Backend changes add session-level authorization checks and record approval decisions to the runtime wire. Frontend introduces session list UI, timeline history replay, and a reworked main App component supporting streaming, reconnection, and session mode selection.

Changes

Backend Session Authorization & Approval Persistence

Layer / File(s) Summary
API contract: runs endpoint and reconnect procedure documentation
coding-agent/docs/web-client-api-contract.md
New GET /sessions/{id}/runs endpoint documented; "History restore and reconnect" section describes deterministic full-history replay via fetch session, fetch runs, and replay per-run display-events, clarifying that GET /sessions/{id}/display-events is only for live reconnect during active turns.
Display-events endpoint authorization and visibility checking
coding-agent/src/coding_agent/server/http_server.py, coding-agent/tests/ui/test_http_server.py
GET /sessions/{session_id}/display-events now resolves visibility via _get_visible_session with auth_context from request headers instead of direct session fetch; test_session_display_events_rejects_non_owner validates 404 rejection and empty event_queues when owner_label does not match.
Approval response recording to runtime wire
coding-agent/src/coding_agent/server/session_manager.py, coding-agent/tests/ui/test_session_manager_runtime.py
SessionManager.submit_approval_response appends RuntimeWireEventRecorder event when ApprovalResponse is returned; event id derived from run id plus request_id; test validates wire.ApprovalResponse emission and approval_result display event transformation.

Frontend Type System & API Implementation

Layer / File(s) Summary
Type definitions for sessions, runs, and patches
coding-agent/webui/app/src/lib/types.ts
New SessionSummary (session-level state snapshot) and RuntimeRun (execution metadata) interfaces; WorkspaceDiff.workspace_id made optional; new WorkspacePatch (unified-diff format container) interface.
API client session, runs, and display event APIs
coding-agent/webui/app/src/lib/api.ts
AgentClient gains listSessions, getSession, displayEvents, and patch methods; prompt generator refactored to use shared streamDisplayPost/readDisplayStream SSE helpers; sessionRuns and runDisplayEvents implement cursor-based pagination with displayResponseToStreamEvent conversion.

Frontend Timeline Event & History Handling

Layer / File(s) Summary
Approval result event handling and validation
coding-agent/webui/app/src/lib/timeline.ts
Approval_result stream events parsed with ApprovalResultPayload validation: ignores missing request_id or non-boolean approved, otherwise resolves approval timeline entries via resolveApproval.
Timeline replay and history reconstruction
coding-agent/webui/app/src/lib/timeline.ts
New exported replayEvents function immutably applies DisplayStreamEvent array to initial TimelineItem via reduce, enabling deterministic full-history reconstruction from run-level display events.
Timeline event replay test coverage
coding-agent/webui/app/src/lib/timeline.test.ts
Vitest suite validates replayEvents reducer behavior matches incremental event application; covers well-formed and malformed approval_result payloads; includes local helpers for deterministic event construction and id normalization.

Frontend UI Components

Layer / File(s) Summary
SessionList sidebar component for session selection
coding-agent/webui/app/src/components/SessionList.tsx
SessionList renders sidebar with refresh button, error banner, empty state, and per-session rows; SessionRow displays status dot (from pending/status), truncated id, optional running badge, model name fallback, and formatted last-activity time; includes StatusDot, shortId, and formatTime helpers.
DiffPanel component for workspace diff visualization
coding-agent/webui/app/src/components/DiffPanel.tsx
DiffPanel renders diff summary (total additions/deletions), per-file status list with paths and counts, and unified patch viewer with colored line rendering or fallback message; statusColor and lineColor helpers map statuses and diff prefixes to CSS classes.
Markdown rendering and prose typography
coding-agent/webui/app/src/components/Timeline.tsx, coding-agent/webui/app/src/index.css
Timeline assistant messages render via MarkdownText component using ReactMarkdown; new .prose-webui CSS block defines typography, spacing, list indentation, link styling (accent + underline), code/pre formatting (background, padding, overflow), and blockquote styling (left border, muted text).

Frontend App Session Management & Streaming

Layer / File(s) Summary
App state initialization and session list management
coding-agent/webui/app/src/App.tsx
App state expanded with session list data/loading/error, session mode, streaming/loading flags, diff state (diff + optional patch), and abort-controller/sequence-counter refs; refreshSessions function with error handling; mounted session refresh via useEffect.
Session loading, streaming, and reconnect reconciliation flows
coding-agent/webui/app/src/App.tsx
New followSession for streaming display-event updates; ref-guarded loadSession and reconcileSession for history restoration; send supports prompt and resume modes with loading/streaming gate; post-stream refresh and error-driven reconciliation; newSession uses sequence counter and abort controller for safe cancellation.
App helper utilities and layout updates
coding-agent/webui/app/src/App.tsx
New formatProgressStatus, shouldResumeSession, isCompletedRootTurnEnd, and upsertSession helpers; layout includes SessionList sidebar and conditional DiffPanel; composer disabled state expanded to include sessionLoading; diff patch fetched via client.patch and stored with diff.

Testing Infrastructure & Integration Tests

Layer / File(s) Summary
Vitest and development dependency configuration
coding-agent/webui/app/package.json
Adds test script (vitest run), react-markdown to dependencies, and vitest/jsdom to devDependencies.
Comprehensive App integration test suite
coding-agent/webui/app/src/App.test.tsx
Vitest+React Testing Library suite with deferred promise, session fixture builder, and jsonResponse utilities; validates session switching aborts previous streams, completed restored sessions use prompt endpoint, send disabled during creation, resume used only with interrupted metadata, send gated during metadata loading, resume persists on interrupted stream, stale history ignored on newer selection, status driven by display-events reconnect, and approval submission with correct request body.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

🐰 A rabbit hops through session trees,
History replayed with proper keys,
Approvals flow and diffs take flight,
Reconnect smooth—resuming right!
The UI springs to multi-seat delight. ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 12.90% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The PR title 'feat(webui): restore sessions from display replay' accurately captures the main feature: enabling session restoration through display event replay. It is specific, concise, and directly reflects the primary objective of the changeset.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/webui-session-recovery

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Add session list, durable display-event replay, resume streaming, markdown assistant rendering, and lightweight diff panel.

Verification: pnpm typecheck; pnpm test; pnpm build; pnpm peers check; browser flow create session, reload, restore via runs replay, continue via /resume.
@delete-cloud
delete-cloud force-pushed the feat/webui-session-recovery branch from 14f7705 to 35bbcc2 Compare June 12, 2026 01:52
@delete-cloud
delete-cloud deleted the feat/webui-session-recovery branch June 12, 2026 03:31
@delete-cloud delete-cloud reopened this Jun 12, 2026
@delete-cloud
delete-cloud marked this pull request as ready for review June 12, 2026 04:45

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
coding-agent/src/coding_agent/server/http_server.py (1)

3136-3145: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Owner scoping is still bypassable through the legacy /sessions/{session_id}/events stream.

This change protects /display-events, but /events just above still exposes the live wire stream after only API-key/session checks. A caller who correctly gets a 404 from _get_visible_session() can still subscribe to /sessions/{session_id}/events and read another owner's session data through the legacy SSE path.

Suggested fix
 `@app.get`("/sessions/{session_id}/events")
 `@limiter.limit`(RateLimits.EVENTS)
 async def get_events(
     request: Request,
     session_id: str,
     api_key: str | None = Depends(verify_api_key),
+    auth_context: AuthContext | None = Depends(auth_context_from_headers),
 ) -> EventSourceResponse:
     """Persistent SSE event stream (fan-out supported)."""
-    try:
-        await session_manager.get_session_async(session_id)
-    except KeyError as exc:
-        raise HTTPException(status_code=404, detail=_key_error_detail(exc)) from exc
+    del request, api_key
+    _ = await _get_visible_session(session_id, auth_context)
 
     try:
         await session_manager.authorize_event_stream(session_id)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@coding-agent/src/coding_agent/server/http_server.py` around lines 3136 -
3145, The legacy SSE handler for /sessions/{session_id}/events currently
bypasses owner scoping; update that handler (the legacy events endpoint
function, e.g., get_session_events) to call and enforce the same visibility
check used by get_session_display_events by invoking
_get_visible_session(session_id, auth_context) (or reuse its guard logic) before
opening the wire stream, and remove any path that allows subscribing after only
API-key/session checks so both endpoints share identical owner-scoping behavior.
🧹 Nitpick comments (1)
coding-agent/webui/app/package.json (1)

10-10: Re-check dependency security + version recency (react-markdown/vitest/jsdom)

  • react-markdown@^10.1.0: no direct security advisories were found for the 10.1.0 release; still run npm audit/Snyk on the full dependency tree since issues often land in transitive deps.
  • vitest@^2.1.9: latest stable as of June 2026 is vitest@4.1.8; consider upgrading if compatibility allows.
  • jsdom@^29.1.1: already on the latest stable (29.1.1 as of June 2026).
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@coding-agent/webui/app/package.json` at line 10, Update package.json
dependency versions and audit the dependency tree: run npm audit and Snyk
against the repo, bump vitest from ^2.1.9 to a compatible newer version
(consider ^4.1.8), confirm react-markdown@^10.1.0 has no transitive advisories
or upgrade if needed, and verify jsdom remains at ^29.1.1; after changing
versions update the lockfile (npm install / npm ci), run the "test" script
(vitest run) and the full test suite, and fix any compatibility issues
introduced by upgrades in files calling vitest/react-markdown/jsdom.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@coding-agent/src/coding_agent/server/http_server.py`:
- Around line 3136-3145: The legacy SSE handler for
/sessions/{session_id}/events currently bypasses owner scoping; update that
handler (the legacy events endpoint function, e.g., get_session_events) to call
and enforce the same visibility check used by get_session_display_events by
invoking _get_visible_session(session_id, auth_context) (or reuse its guard
logic) before opening the wire stream, and remove any path that allows
subscribing after only API-key/session checks so both endpoints share identical
owner-scoping behavior.

---

Nitpick comments:
In `@coding-agent/webui/app/package.json`:
- Line 10: Update package.json dependency versions and audit the dependency
tree: run npm audit and Snyk against the repo, bump vitest from ^2.1.9 to a
compatible newer version (consider ^4.1.8), confirm react-markdown@^10.1.0 has
no transitive advisories or upgrade if needed, and verify jsdom remains at
^29.1.1; after changing versions update the lockfile (npm install / npm ci), run
the "test" script (vitest run) and the full test suite, and fix any
compatibility issues introduced by upgrades in files calling
vitest/react-markdown/jsdom.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 6af4da43-d883-48b6-b66e-40a3f2724b6f

📥 Commits

Reviewing files that changed from the base of the PR and between c9aed6b and 98d541e.

⛔ Files ignored due to path filters (1)
  • coding-agent/webui/app/pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (16)
  • coding-agent/docs/web-client-api-contract.md
  • coding-agent/src/coding_agent/server/http_server.py
  • coding-agent/src/coding_agent/server/session_manager.py
  • coding-agent/tests/ui/test_http_server.py
  • coding-agent/tests/ui/test_session_manager_runtime.py
  • coding-agent/webui/app/package.json
  • coding-agent/webui/app/src/App.test.tsx
  • coding-agent/webui/app/src/App.tsx
  • coding-agent/webui/app/src/components/DiffPanel.tsx
  • coding-agent/webui/app/src/components/SessionList.tsx
  • coding-agent/webui/app/src/components/Timeline.tsx
  • coding-agent/webui/app/src/index.css
  • coding-agent/webui/app/src/lib/api.ts
  • coding-agent/webui/app/src/lib/timeline.test.ts
  • coding-agent/webui/app/src/lib/timeline.ts
  • coding-agent/webui/app/src/lib/types.ts

@delete-cloud
delete-cloud merged commit 7a01e6d into main Jun 12, 2026
1 check passed
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