Skip to content

Persist streaming state when chat sidebar closes#532

Closed
ki-cooley wants to merge 50 commits intoglowingjade:mainfrom
ki-cooley:feature/persist-streaming
Closed

Persist streaming state when chat sidebar closes#532
ki-cooley wants to merge 50 commits intoglowingjade:mainfrom
ki-cooley:feature/persist-streaming

Conversation

@ki-cooley
Copy link

@ki-cooley ki-cooley commented Feb 17, 2026

Summary

  • StreamStateManager: New plugin-level class that takes ownership of active streams when the sidebar closes mid-stream, accumulates response messages, and hands them back when the sidebar reopens — enabling seamless stream continuation
  • Serialization fix: activities and contentBlocks are now included when saving assistant messages, so tool activity accordions and structured content blocks persist across sessions
  • Debounce flush: Calls .flush() on the debounced save before React unmounts, preventing the last ~300ms-1s of data from being lost
  • Auto-scroll on mount: Chat view scrolls to bottom when opening a conversation that already has messages

Test plan

  • TypeScript type check passes (npx tsc --noEmit)
  • Production build passes (npm run build)
  • E2E: Send multi-tool prompt, close sidebar, reopen — activities and contentBlocks visible
  • E2E: Close sidebar mid-stream, wait for completion, reopen — full response preserved (4,579 chars, 30 activities)
  • E2E: Close sidebar mid-stream, reopen before completion — stream resumes live with Stop Generation button
  • E2E: Close sidebar immediately after first response chunk — message saved (debounce flushed)

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Live activity tracking showing tool usage, file operations, and search results
    • Edit history with revert functionality for recent changes
    • Slash commands with skills integration for custom workflows
    • Backend agent support with streaming responses
    • Persistent memory system for context preservation
  • Improvements

    • Better mobile experience with keyboard handling
    • Code block expansion/collapse for lengthy content
    • Interspersed content layout for cleaner message display
    • Enhanced activity visualization with real-time status
  • Rebranding

    • Plugin rebranded from "Smart Composer" to "Claudsidian"

claude and others added 30 commits January 7, 2026 05:45
Implements the backend WebSocket server that:
- Connects to Claude API with streaming support
- Exposes vault operation tools (read, write, search, list, delete)
- Uses bidirectional RPC protocol for vault operations
- Supports token-based authentication
- Includes Dockerfile for Railway deployment

The backend acts as a bridge between the Obsidian plugin and Claude,
handling the agent loop and tool execution.
- Add MOCK_MODE environment variable for testing without API key
- Add mock agent that simulates Claude responses with tool usage
- Add automated test suite with 6 tests covering:
  - Simple prompt/response streaming
  - vault_list, vault_search, vault_read, vault_write tools
  - Ping/pong keepalive
- Add interactive test client for manual testing
- Update package.json with dev:mock and test scripts

All tests pass: 6/6
- Add railway.toml at repo root to point to backend/Dockerfile
- Update Dockerfile to build from repo root context
- Build TypeScript inside container instead of expecting pre-built dist/
- Add /health and / endpoints that return JSON status
- Attach WebSocket server to HTTP server
- Update Dockerfile health check to use /health endpoint
- Set buildContext = 'backend' in railway.toml
- Fix Dockerfile COPY paths to be relative to backend dir
- Update healthcheckPath to /health
Implements additional Claude Agent SDK-like tools for more powerful
vault operations:
- vault_edit: Precise string replacement in files
- vault_grep: Regex search across file contents with line numbers
- vault_glob: File pattern matching with glob syntax
- vault_rename: Move/rename files with link updates

Also adds UI formatting for new tool results with clickable wikilinks.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Loads CLAUDE.md from vault root for project-specific instructions
- Loads .claude/instructions.md as alternative location
- Loads custom skills from .claude/skills/*.md
- Skills can be invoked by name (e.g., "/weekly-review")
- Updates system prompt with new tool capabilities

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add model field to PromptMessage protocol
- Pass selected model from BackendProvider to WebSocket
- Backend agent now uses model from request instead of hardcoded default
- Default still falls back to claude-opus-4-5-20250514 if not specified

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- claude-opus-4-5-20251101 (was 20250514)
- claude-sonnet-4-5-20250929
- claude-haiku-4-5-20251001
- claude-opus-4-1-20250805

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Collapsed file lists: show first 3 files with "+N more" instead of full lists
- Edit batching: suppress duplicate edit messages within 5 second window
- Clean up temp test scripts and debug files
- Add ConflictManager and instance singleton for backend
- Update various UI components for better integration

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Backend:
- Enable built-in Anthropic web_search tool for real-time web access
- Update system prompt to mention web search capability

Frontend:
- Add enterKeyHint="send" for mobile keyboard submit button
- Blur input after submit to hide mobile keyboard
- Add code block truncation (15 lines) with expand/collapse

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Enables the backend to connect to external MCP servers via SSE transport,
discover their tools, and forward tool calls from Claude. This allows
cookbook search tools (and any future MCP servers) to work through the
backend provider, which is required for mobile support.

- New src/mcp-client.ts: MCP client manager using @modelcontextprotocol/sdk
- Modified agent.ts: merges MCP tools with vault tools, routes tool calls
- Modified index.ts: initializes MCP client after server starts (non-blocking)
- Added @modelcontextprotocol/sdk dependency

Configured via MCP_SERVERS env var (JSON):
  {"server-name":{"type":"sse","url":"https://example.com/mcp/sse"}}

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace @anthropic-ai/sdk + manual agent loop with @anthropic-ai/claude-agent-sdk.
SDK's query() handles tool execution, message history, and MCP connections
automatically. Auth now supports CLAUDE_CODE_OAUTH_TOKEN (subscription) or
ANTHROPIC_API_KEY. Default model updated to claude-opus-4-6.

- Rewrite agent.ts: replace manual streaming loop with SDK query()
- New vault-tools.ts: Zod v4 schemas + tool() + createSdkMcpServer()
- Delete mcp-client.ts (SDK handles MCP connections internally)
- Delete mcp-tools.ts (replaced by vault-tools.ts)
- Update index.ts: remove MCP client init, dual auth check
- Upgrade zod ^3.23 → ^4.0 (Agent SDK peer dependency)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The Agent SDK CLI requires git at runtime. Also set HOME=/tmp/claude-home
for writable config directory in Docker, and log full error stacks.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…p diag

- Add stderr callback to query() to capture CLI subprocess errors
- Create /tmp/claude-home/.claude/ in Dockerfile so CLI has writable HOME
- Remove slow startup diagnostic that blocked module load

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The Agent SDK CLI refuses --dangerously-skip-permissions when running
as root. Create a 'claude' user and switch to it before running.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Tool names like "mcp__vault-tools__vault_read" are now cleaned to
just "vault_read" before sending to the plugin. This makes the
activity accordion show meaningful tool names instead of MCP internals.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The SDK handles external MCP tool execution internally, so we never
got tool_end events for tools like search_cookbooks. Now we track
pending tool starts and emit synthetic tool_end events when the SDK
moves to the next turn (text streaming or new assistant message).

This ensures the plugin's activity accordion correctly shows all
tool executions with proper start/end lifecycle.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replaces the old tool message display with a modern activity tracking system:
- ActivityAccordion: collapsible exploration summary (auto-expand during streaming)
- ActivityItem: individual activities with live timers, clickable file links
- EditDiffBlock: green/red diff display with Undo button for file changes
- EditHistory: singleton snapshot manager for revert functionality
- Full support for cookbook search tools (search_cookbooks, list_cookbook_sources)
- ActivityEvent type system with 13 activity types + generic fallback
- BackendProvider emits structured ActivityEvent objects during streaming
- tool-result-formatter parses raw results into structured diff/count/path data
- 320 lines of Obsidian-native CSS with theme variable integration
- Cleaned up debug logging artifacts from main.ts and settings.ts

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…tocol-level

Railway's proxy doesn't forward WebSocket ping/pong frames, causing the
server's protocol-level heartbeat to terminate connections after ~45 seconds.
Now tracks last message activity time and allows 90s of inactivity. The
plugin already sends application-level ping messages every 25s which keep
the connection alive through the proxy.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Tell the agent to always include exact page numbers and source names
when citing cookbook search results, preventing vague references.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Fix activity ID mismatch between BackendProvider and VaultRpcHandler
  that prevented undo from finding the correct snapshot. Track activity
  IDs from tool_start events and consume them in the RPC handler.
- Fix revert for new files: delete file instead of writing empty content.
- Add clickable file links in EditDiffBlock that open files in editor.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…MCP restriction

- Add externalResourceDir plugin setting for vault-relative PDF path
- Fix duplicate activity items by deduplicating editActivities by filePath+type
- Map search_cookbooks/list_cookbook_sources/web_search to proper activity types
- Remove blanket mobile MCP disable; let transports fail gracefully
- Update backend system prompt with source filter and citation format guidance
- Settings migration 12→13

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
ki-cooley and others added 20 commits February 12, 2026 01:21
LLM was stripping [[cookbooks/file.pdf#page=N]] wikilinks and reformatting
as plain text. Updated Cookbook Research Tools section to mark wikilink
preservation as CRITICAL with explicit examples and instructions.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
BackendProvider: Race message promise against abort signal so clicking
Stop immediately wakes the generator and sends cancel to backend.

WebSocketClient: Notify active stream handlers on disconnect so
generators don't hang forever when the connection drops.

server.ts: Send safety-net complete message in finally block so the
client never gets stuck waiting for a terminal event.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When an external MCP server dies (e.g. cookbook-rag OOM), the SDK's
query() hangs forever waiting for a tool response. This wraps the
iterator with a 2-minute timeout that aborts the agent and yields
an error if no message arrives.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…, stream thinking tokens

- Replace 2-min withMessageTimeout (killed legitimate multi-tool turns) with
  activity-based approach: heartbeat updated by SDK messages, vault tool
  handlers, and stderr; 15s interval check; 5-min inactivity threshold
- Stream thinking_delta tokens in real time (Cursor-like UX)
- Emit tool_start from content_block_start stream events (earlier than
  waiting for full assistant message), with dedup against assistant fallback
- Add heartbeat callback to createVaultMcpServer, called by all 9 vault
  tool handlers on execution

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Revert early tool_start from content_block_start (sent empty input {},
  caused "Cookbook search: ''" in activity UI). Tool_start now emits from
  the assistant message which has the complete input parameters.
- Keep thinking_delta streaming from stream events (real-time thinking)
- Add pending tool cleanup in catch block so cancelled requests don't
  leave stuck "running" activities in the UI
- Remove unused streamStartedToolIds tracking

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Fix 2: Track completeSent flag in server.ts to prevent safety-net
  complete from racing with tool_end events. Delay handler deletion
  in WebSocketClient by 1s after complete.
- Fix 3: Enable extended thinking (budgetTokens: 10000) in SDK query.
  Add diagnostic logging for thinking block start and deltas.
- Fix 4: Increase maxTurns 10→25, inactivity timeout 5→10min,
  RPC timeout 30→60s. Add result logging for timeout diagnosis.
- Add interspersed layout spec doc for future implementation.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…on error

- Remove `thinking: { type: 'enabled', budgetTokens: 10000 }` option that
  was causing the Agent SDK CLI to fail silently (2s empty response + exit 1)
- Track `completedSuccessfully` flag to suppress the spurious "process exited
  with code 1" error the SDK throws after normal completion

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Root cause of empty responses was ANTHROPIC_API_KEY env var on Railway
overriding the working CLAUDE_CODE_OAUTH_TOKEN. The thinking option
itself is fine.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- maxTurns 25→50 to support complex multi-source research queries
- Detect error_max_turns and append truncation notice to response
- Add system prompt guidance: batch tool calls 5-8 at a time, present
  final synthesis instead of repeating findings in multiple formats

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace grouped activity layout (all activities at top, text at bottom)
with chronologically interspersed layout — text and activities appear
in the order they happen during agent execution, like Cursor.

Also restore rendered markdown preview in edit diff blocks with a
Raw/Rendered toggle button, defaulting to rendered view.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Strip [[]] from file names in edit diff headers, activity item links,
and activity accordion labels. The link styling already distinguishes
clickable filenames without needing bracket decoration.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add images field to PromptMessage protocol (both frontend and backend)
- Skip file reading/RAG in PromptGenerator for backend provider — pass
  file paths instead so the agent reads via vault_read
- Extract images from request in BackendProvider, send separately from
  prompt text via WebSocket
- Forward images through server.ts to agent.ts, construct multimodal
  Anthropic content blocks when images are present
- Add console.log tracing for image flow debugging

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Fix image inputs and @ file mentions for backend provider
- Fix streaming auto-scroll: use wheel/touchmove events to detect user
  scrolling instead of scroll events (which also fire on content growth),
  preventing false auto-scroll disable during streaming
- Add persistent memory via .claude/memory.md: auto-create on plugin
  load, load into prompt generator, agent can read/write via vault tools
- Fix dotfile support in VaultRpcHandler: use adapter API for paths
  containing dotfiles (.claude/) since Obsidian vault API doesn't index them
- Add mobile scroll CSS (overscroll-behavior, webkit-overflow-scrolling)
- Wrap autoScrollToBottom in requestAnimationFrame for DOM sync

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add auto-scroll fix, persistent memory, mobile UX, and dotfile support
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add auto-release on push to main for BRAT
When the sidebar closes mid-stream, the ResponseGenerator keeps running
in the mutation's async context but the React subscriber dies, losing
accumulated content. This adds a plugin-level StreamStateManager that
takes ownership of active streams on close and hands them back on reopen.

Changes:
- Add StreamStateManager to hold detached streams and rehydrate on mount
- Fix serialization to include activities and contentBlocks on save
- Flush debounced save on ChatView close to prevent data loss
- Auto-scroll to bottom when opening a conversation with existing messages

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@ki-cooley ki-cooley closed this Feb 17, 2026
@coderabbitai
Copy link

coderabbitai bot commented Feb 17, 2026

Caution

Review failed

The pull request is closed.

📝 Walkthrough

Walkthrough

This pull request introduces a complete backend WebSocket service for the Claudsidian Obsidian plugin, enabling local Claude agent execution with vault file operations. It rebrands the plugin from "Smart Composer" to "Claudsidian," adds a Node.js backend with Docker/Railway deployment configs, implements activity tracking UI, and extends the type system to support streaming activities and interspersed content blocks.

Changes

Cohort / File(s) Summary
Rebranding
manifest.json, src/components/modals/*, src/components/settings/SettingsTabRoot.tsx, src/ChatView.tsx, CLAUDE.md
Updated all references from "Smart Composer" to "Claudsidian" including manifest id/name, UI text, GitHub links, and settings labels.
Backend Infrastructure (Node.js)
backend/src/agent.ts, backend/src/server.ts, backend/src/protocol.ts, backend/src/index.ts, backend/src/mock-agent.ts, backend/src/vault-tools.ts, backend/src/utils.ts
New WebSocket server, Claude agent orchestration, RPC protocol for vault operations, mock agent for testing, and utility helpers (logging, retry, JSON parsing).
Backend Configuration & Deployment
backend/Dockerfile, backend/railway.json, railway.toml, backend/.env.example, backend/.gitignore, backend/tsconfig.json, backend/package.json, backend/README.md
Docker containerization, Railway deployment configs with healthchecks, environment variable templates, build configuration, and documentation for backend setup and WebSocket protocol.
Backend Testing
backend/test/automated-test.ts, backend/test/test-client.ts
Automated E2E test suite and interactive REPL test client for validating WebSocket communication, RPC handling, and vault operations.
Plugin Backend Integration
src/core/backend/BackendProvider.ts, src/core/backend/WebSocketClient.ts, src/core/backend/VaultRpcHandler.ts, src/core/backend/ConflictManager.ts, src/core/backend/EditHistory.ts, src/core/backend/StreamStateManager.ts, src/core/backend/protocol.ts, src/core/backend/instance.ts, src/core/backend/tool-result-formatter.ts
Frontend WebSocket client, RPC handler for vault operations, conflict/edit history management, stream state persistence, protocol definitions, and tool result parsing and formatting.
Activity & Streaming UI
src/components/chat-view/ActivityAccordion.tsx, src/components/chat-view/ActivityItem.tsx, src/components/chat-view/EditDiffBlock.tsx, src/components/chat-view/InterspersedContent.tsx, src/components/chat-view/ToolMessage.tsx
New Cursor-style activity display system with live streaming indicators, diff/edit visualization, collapsible activity groups, and interspersed content block rendering. Filters backend tool calls from UI.
Chat & Input Components
src/components/chat-view/Chat.tsx, src/components/chat-view/AssistantToolMessageGroupItem.tsx, src/components/chat-view/MarkdownCodeComponent.tsx, src/components/chat-view/chat-input/ChatUserInput.tsx, src/components/chat-view/chat-input/LexicalContentEditable.tsx, src/components/chat-view/useAutoScroll.ts, src/components/chat-view/useChatStreamManager.ts
Extended Chat with stream state management and flushSave lifecycle; added activity/contentBlock support to assistant messages; added code folding to markdown blocks; improved mobile keyboard handling; refined auto-scroll logic to track user interactions; added stream detachment capability.
Template/Skill System
src/components/chat-view/chat-input/plugins/template/TemplatePlugin.tsx, src/hooks/useSkills.ts
Unified slash-command menu combining templates, skills, and commands; new hook to load and search skills/commands from .claude directories with frontmatter parsing.
Settings & Provider Management
src/components/settings/modals/AddChatModelModal.tsx, src/components/settings/modals/AddEmbeddingModelModal.tsx, src/components/settings/modals/ProviderFormModal.tsx, src/components/settings/sections/ProvidersSection.tsx, src/components/settings/sections/ChatSection.tsx, src/constants.ts, src/types/provider.types.ts
Added backend provider type with URL/auth token fields; updated provider filtering for model selection; separated edit/delete actions in provider list; added external resource directory setting; set backend-agent as default model; updated recommended model lists.
Type System Extensions
src/types/chat.ts, src/types/chat-model.types.ts, src/types/llm/response.ts, src/hooks/useChatHistory.ts
Added ActivityEvent, ActivityType, ContentBlock types for streaming activities and interspersed layouts; extended assistant messages with contentBlocks and activities fields; updated chat history serialization; extended LLM response delta with activity/contentBlock fields.
Prompt & Response Generation
src/utils/chat/promptGenerator.ts, src/utils/chat/responseGenerator.ts
Added backend-specific prompt path (no RAG/file reading), persistent memory support (.claude/memory.md), text file classification; extended response merging to handle activities and content blocks with deduplication and coalescing.
Core Integration
src/core/llm/manager.ts, src/core/mcp/mcpManager.ts, src/main.ts
Wired BackendProvider into LLM manager; made MCP resilient to mobile environment failures; integrated VaultRpcHandler, ConflictManager, EditHistory, and StreamStateManager into plugin lifecycle; added RPC webhook, memory file initialization, and backend connection logic.
Settings Migrations
src/settings/schema/migrations/12_to_13.ts, src/settings/schema/migrations/index.ts, src/settings/schema/setting.types.ts, src/settings/schema/settings.ts
Added v12→v13 migration for externalResourceDir field; updated schema and settings parsing.
Documentation & Configuration
docs/interspersed-layout-spec.md, .claude/settings.json, styles.css, package.json
Added interspersed layout design spec; added Claude settings with bash permissions; expanded styles for activity accordion, edit diffs, slash commands, and code folding; minor package.json formatting.

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant Plugin as Obsidian Plugin
    participant WS as WebSocket Client
    participant Backend as Backend Server
    participant Agent as Claude Agent
    participant Vault as Vault Bridge
    
    User->>Plugin: Send prompt + context
    Plugin->>WS: sendPrompt(prompt, handlers)
    WS->>Backend: PromptMessage (via WebSocket)
    
    Backend->>Agent: runAgent(prompt, context)
    
    loop Agent Processing
        Agent->>Agent: Load skills, system prompt
        Agent->>Agent: Call Claude with streaming
        
        alt Tool Invoked
            Agent->>Backend: Emit tool_start event
            Backend->>WS: ToolStartMessage
            WS->>Plugin: onToolStart handler
            
            Backend->>Vault: RPC vault_read/write/etc
            Vault->>Backend: RPC response
            
            Agent->>Backend: Emit tool_end event
            Backend->>WS: ToolEndMessage
            WS->>Plugin: onToolEnd handler
        else Text Generated
            Agent->>Backend: Emit text_delta event
            Backend->>WS: TextDeltaMessage
            WS->>Plugin: onTextDelta handler
        end
    end
    
    Agent->>Backend: Emit complete event
    Backend->>WS: CompleteMessage
    WS->>Plugin: onComplete handler
    Plugin->>User: Display response + activities
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related PRs

Poem

🐰 A rabbit hops through server gates,
WebSockets open, agents activate,
Vault tools dance, diffs unfold—
Claude's wisdom, now to behold!
From Smart Composer's chrysalis, Claudsidian takes flight.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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.

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.

2 participants