Fix project isolation: Make loadChatHistory respect active project sessions
- Modified loadChatHistory() to check for active project before fetching all sessions - When active project exists, use project.sessions instead of fetching from API - Added detailed console logging to debug session filtering - This prevents ALL sessions from appearing in every project's sidebar Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
489
SSE_ARCHITECTURE_DIAGRAMS.md
Normal file
489
SSE_ARCHITECTURE_DIAGRAMS.md
Normal file
@@ -0,0 +1,489 @@
|
||||
# SSE Architecture Diagrams
|
||||
|
||||
## Overview Diagram
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ Browser Client │
|
||||
├─────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ ┌──────────────┐ ┌──────────────┐ │
|
||||
│ │ Session UI │ │ Terminal UI │ │
|
||||
│ │ │ │ │ │
|
||||
│ │ ┌──────────┐ │ │ ┌──────────┐ │ │
|
||||
│ │ │ SSE │ │ │ │ SSE │ │ │
|
||||
│ │ │ Client │ │ │ │ Client │ │ │
|
||||
│ │ └────┬─────┘ │ │ └────┬─────┘ │ │
|
||||
│ └──────┼───────┘ └──────┼───────┘ │
|
||||
│ │ │ │
|
||||
│ └────────┬───────────────┘ │
|
||||
│ │ │
|
||||
│ ▼ │
|
||||
│ ┌────────────────┐ │
|
||||
│ │ REST API calls │ (POST prompt, GET status, etc.) │
|
||||
│ └────────────────┘ │
|
||||
└───────────────────┬──────────────────────────────────────────────┘
|
||||
│
|
||||
│ HTTP/HTTPS
|
||||
│
|
||||
┌───────────────────▼──────────────────────────────────────────────┐
|
||||
│ nginx (Optional) │
|
||||
├─────────────────────────────────────────────────────────────────┤
|
||||
│ • Disable buffering for SSE │
|
||||
│ • Proxy to Node.js backend │
|
||||
│ • Long-lived connection support │
|
||||
└───────────────────┬──────────────────────────────────────────────┘
|
||||
│
|
||||
│
|
||||
┌───────────────────▼──────────────────────────────────────────────┐
|
||||
│ Express Server (Node.js) │
|
||||
├─────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ ┌──────────────────────────────────────────────────────────┐ │
|
||||
│ │ Routes Layer │ │
|
||||
│ ├──────────────────────────────────────────────────────────┤ │
|
||||
│ │ │ │
|
||||
│ │ /api/session/:id/events → SSE streaming endpoint │ │
|
||||
│ │ /api/session/:id/prompt → Send command │ │
|
||||
│ │ /api/session/:id/status → Get status │ │
|
||||
│ │ /api/session/:id/context → Get context │ │
|
||||
│ │ ... │ │
|
||||
│ └───────────────────────┬──────────────────────────────────┘ │
|
||||
│ │ │
|
||||
│ ▼ │
|
||||
│ ┌──────────────────────────────────────────────────────────┐ │
|
||||
│ │ SSE Manager │ │
|
||||
│ ├──────────────────────────────────────────────────────────┤ │
|
||||
│ │ • Manages SSE connections │ │
|
||||
│ │ • Handles heartbeat │ │
|
||||
│ │ • Broadcasts events to clients │ │
|
||||
│ │ • Connection lifecycle management │ │
|
||||
│ └───────────────────────┬──────────────────────────────────┘ │
|
||||
│ │ │
|
||||
│ ▼ │
|
||||
│ ┌──────────────────────────────────────────────────────────┐ │
|
||||
│ │ Event Bus │ │
|
||||
│ ├──────────────────────────────────────────────────────────┤ │
|
||||
│ │ • Pub/Sub event system │ │
|
||||
│ │ • Filters by session ID │ │
|
||||
│ │ • Tracks listeners and metrics │ │
|
||||
│ │ • No memory leaks (proper cleanup) │ │
|
||||
│ └───────────┬───────────────┬───────────────┬───────────────┘ │
|
||||
│ │ │ │ │
|
||||
│ ▼ ▼ ▼ │
|
||||
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
|
||||
│ │ Claude │ │ Terminal │ │ Approval │ │
|
||||
│ │ Service │ │ Service │ │ Manager │ │
|
||||
│ │ │ │ │ │ │ │
|
||||
│ │ • Spawn PTY │ │ • Manage │ │ • Track │ │
|
||||
│ │ • Send cmd │ │ terminals │ │ requests │ │
|
||||
│ │ • Parse ops │ │ • Buffer │ │ • Timeouts │ │
|
||||
│ │ │ │ output │ │ │ │
|
||||
│ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ │
|
||||
│ │ │ │ │
|
||||
│ └──────────────────┴──────────────────┘ │
|
||||
│ │ │
|
||||
│ ▼ │
|
||||
│ ┌──────────────────────────────────────────────────────────┐ │
|
||||
│ │ Claude Code Processes │ │
|
||||
│ │ (one per session, managed by ClaudeService) │ │
|
||||
│ └──────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
└───────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## Event Flow Diagram
|
||||
|
||||
```
|
||||
User sends command:
|
||||
┌──────────┐ ┌──────────┐ ┌──────────────┐
|
||||
│ Client │───────▶│ REST │──────▶│ Claude │
|
||||
│ │ POST │ API │ │ Service │
|
||||
└──────────┘ /prompt│ │ └──────┬───────┘
|
||||
/session/ │
|
||||
:id/prompt │
|
||||
▼
|
||||
┌──────────────┐
|
||||
│ Claude Code │
|
||||
│ Process │
|
||||
└──────┬───────┘
|
||||
│
|
||||
│ Output
|
||||
▼
|
||||
┌──────────────┐
|
||||
│ Claude │
|
||||
│ Service │
|
||||
└──────┬───────┘
|
||||
│
|
||||
│ emit('session-output')
|
||||
▼
|
||||
┌──────────┐ ┌──────────────┐
|
||||
│ Client │◀───────────────────────│ Event Bus │
|
||||
│ │ SSE stream │ │
|
||||
└──────────┘ └──────────────┘
|
||||
│ │
|
||||
│ │
|
||||
│ ┌────────────────────────────┘
|
||||
│ │
|
||||
▼ ▼
|
||||
┌──────────────────────────────────────────┐
|
||||
│ Client Browser │
|
||||
│ ┌────────────────────────────────────┐ │
|
||||
│ │ EventSource receives event │ │
|
||||
│ │ event: session-output │ │
|
||||
│ │ data: {"content": "..."} │ │
|
||||
│ └────────────────────────────────────┘ │
|
||||
│ │ │
|
||||
│ ▼ │
|
||||
│ ┌────────────────────────────────────┐ │
|
||||
│ │ Update UI with output │ │
|
||||
│ └────────────────────────────────────┘ │
|
||||
└──────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## Connection Lifecycle Diagram
|
||||
|
||||
```
|
||||
Client Server
|
||||
│ │
|
||||
│ 1. GET /api/session/:id/events │
|
||||
│────────────────────────────────────▶│
|
||||
│ │ SSE Manager.addConnection()
|
||||
│ │ • Set headers
|
||||
│ │ • Flush response
|
||||
│ │ • Subscribe to events
|
||||
│ │
|
||||
│◀────────────────────────────────────│ 2. Send "connected" event
|
||||
│ event: connected │
|
||||
│ data: {"sessionId": "..."} │
|
||||
│ │
|
||||
│ 3. POST /api/session/:id/prompt │
|
||||
│────────────────────────────────────▶│
|
||||
│ {"command": "ls -la"} │ ClaudeService.sendCommand()
|
||||
│ │
|
||||
│ 4. Continue connection... │
|
||||
│◀────────────────────────────────────│ EventBus emits 'session-output'
|
||||
│ event: session-output │
|
||||
│ data: {"content": "file1.txt..."} │
|
||||
│ │
|
||||
│◀────────────────────────────────────│ EventBus emits 'session-output'
|
||||
│ event: session-output │
|
||||
│ data: {"content": "file2.txt..."} │
|
||||
│ │
|
||||
│◀────────────────────────────────────│ :heartbeat (every 30s)
|
||||
│ :heartbeat │
|
||||
│ │
|
||||
│ 5. Client disconnects │
|
||||
│────────────────────────────────────▶│ req.on('close')
|
||||
│ │ • Unsubscribe from events
|
||||
│ │ • Stop heartbeat
|
||||
│ │ • Remove from tracking
|
||||
```
|
||||
|
||||
## Reconnection Logic Diagram
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ Client Reconnection │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
|
||||
Connection Lost
|
||||
│
|
||||
▼
|
||||
┌──────────────────┐
|
||||
│ Is this a │──── Yes ──▶ Emit 'disconnected'
|
||||
│ permanent error? │ (404, session deleted)
|
||||
└────────┬─────────┘
|
||||
│ No
|
||||
▼
|
||||
┌──────────────────┐
|
||||
│ Reconnect │
|
||||
│ attempts < │──── No ───▶ Stop reconnecting
|
||||
│ max attempts? │ Emit 'disconnected' (permanent)
|
||||
└────────┬─────────┘
|
||||
│ Yes
|
||||
▼
|
||||
┌──────────────────┐
|
||||
│ Calculate delay │
|
||||
│ with exponential │
|
||||
│ backoff: │
|
||||
│ delay = min( │
|
||||
│ 1000 * 1.5^attempts,│
|
||||
│ 30000 │
|
||||
│ ) │
|
||||
└────────┬─────────┘
|
||||
│
|
||||
▼
|
||||
┌──────────────────┐
|
||||
│ Wait for delay │
|
||||
│ (1s, 1.5s, 2.25s,│
|
||||
│ ... up to 30s) │
|
||||
└────────┬─────────┘
|
||||
│
|
||||
▼
|
||||
┌──────────────────┐
|
||||
│ Emit 'reconnecting'│
|
||||
│ { attempt, │
|
||||
│ delay, │
|
||||
│ maxAttempts } │
|
||||
└────────┬─────────┘
|
||||
│
|
||||
▼
|
||||
┌──────────────────┐
|
||||
│ Create new │
|
||||
│ EventSource │
|
||||
│ connection │
|
||||
└────────┬─────────┘
|
||||
│
|
||||
└──────────────┐
|
||||
│
|
||||
┌──────────────┘
|
||||
│
|
||||
▼
|
||||
Connection Open?
|
||||
│
|
||||
┌────┴────┐
|
||||
│ │
|
||||
Yes No
|
||||
│ │
|
||||
│ └─────────────────────────┐
|
||||
│ │
|
||||
▼ ▼
|
||||
┌─────────────┐ Increment attempt count
|
||||
│ Emit │ and loop back
|
||||
│ 'connected' │
|
||||
└─────────────┘
|
||||
```
|
||||
|
||||
## Architecture Comparison
|
||||
|
||||
### Before (WebSocket - Current)
|
||||
|
||||
```
|
||||
┌────────────────────────────────────────────────────────────┐
|
||||
│ WebSocket Flow │
|
||||
├────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ Client Server │
|
||||
│ │ │ │
|
||||
│ │ 1. Connect to ws://... │ │
|
||||
│ ├─────────────────────────▶│ │
|
||||
│ │ │ WebSocket Server │
|
||||
│ │ │ • Parse session cookie │
|
||||
│ │ │ • Accept connection │
|
||||
│ │ │ │
|
||||
│ │◀─────────────────────────│ 2. Send "connected" │
|
||||
│ │ │ │
|
||||
│ │ 3. Send "subscribe" │ │
|
||||
│ │ message with │ │
|
||||
│ │ sessionId │ │
|
||||
│ ├─────────────────────────▶│ • Store client → session │
|
||||
│ │ │ mapping │
|
||||
│ │ │ │
|
||||
│ │ 4. Send "command" │ │
|
||||
│ ├─────────────────────────▶│ • Route to ClaudeService │
|
||||
│ │ │ │
|
||||
│ │ │ ClaudeService emits │
|
||||
│ │ │ • Find all clients │
|
||||
│ │◀─────────────────────────│ subscribed to session │
|
||||
│ │ │ • Send to each client │
|
||||
│ │
|
||||
│ Problem: Session context is in message, not URL │
|
||||
│ Problem: Client must manage subscription state │
|
||||
│ Problem: Multiple tabs can have conflicting subscriptions │
|
||||
└────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### After (SSE - Proposed)
|
||||
|
||||
```
|
||||
┌────────────────────────────────────────────────────────────┐
|
||||
│ SSE Flow │
|
||||
├────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ Client Server │
|
||||
│ │ │ │
|
||||
│ │ 1. GET /api/session/ │ │
|
||||
│ │ :id/events │ │
|
||||
│ ├─────────────────────────▶│ • Validate session ID │
|
||||
│ │ │ • Check session exists │
|
||||
│ │ │ • Create SSE connection │
|
||||
│ │ │ • Subscribe to all events │
|
||||
│ │ │ for this session │
|
||||
│ │ │ │
|
||||
│ │◀─────────────────────────│ 2. Send "connected" │
|
||||
│ │ │ │
|
||||
│ │ 3. POST /api/session/ │ │
|
||||
│ │ :id/prompt │ │
|
||||
│ ├─────────────────────────▶│ • Validate session ID │
|
||||
│ │ {command} │ • Route to ClaudeService │
|
||||
│ │ │ │
|
||||
│ │ │ ClaudeService emits │
|
||||
│ │ │ • EventBus broadcasts │
|
||||
│ │◀─────────────────────────│ to all subscribers │
|
||||
│ │ │ • SSE Manager sends to │
|
||||
│ │ │ connected clients │
|
||||
│ │
|
||||
│ Benefit: Session context in URL │
|
||||
│ Benefit: Automatic subscription │
|
||||
│ Benefit: Multiple tabs work correctly │
|
||||
│ Benefit: Works through nginx with proper config │
|
||||
└────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## Component Relationships
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────────────────────────┐
|
||||
│ Component Dependencies │
|
||||
├──────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ ┌─────────────┐ ┌─────────────┐ │
|
||||
│ │ Routes │────────▶│ Validation │ │
|
||||
│ │ │ │ Middleware │ │
|
||||
│ └──────┬──────┘ └─────────────┘ │
|
||||
│ │ │
|
||||
│ ├──▶ ┌─────────────┐ ┌─────────────┐ │
|
||||
│ │ │ SSE │────────▶│ Event Bus │ │
|
||||
│ │ │ Manager │ │ │ │
|
||||
│ │ └─────────────┘ └──────┬──────┘ │
|
||||
│ │ │ │
|
||||
│ │ ┌─────────────┐ │ │
|
||||
│ └───▶│ Claude │ │ │
|
||||
│ │ Service │ │ │
|
||||
│ └─────────────┘ │ │
|
||||
│ │ │ │
|
||||
│ └───────────────────────────┘ │
|
||||
│ (emits events) │
|
||||
│ │
|
||||
└──────────────────────────────────────────────────────────────┘
|
||||
|
||||
Key:
|
||||
──▶ Calls/Uses
|
||||
│ Dependency
|
||||
└ Returns data
|
||||
```
|
||||
|
||||
## Data Flow: Command to Output
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────────────────────────┐
|
||||
│ Complete Flow: Command → Output │
|
||||
└──────────────────────────────────────────────────────────────┘
|
||||
|
||||
1. USER ACTION
|
||||
User types "ls -la" in terminal UI and presses Enter
|
||||
|
||||
2. CLIENT SENDS COMMAND
|
||||
POST /api/session/session-123/prompt
|
||||
{
|
||||
"command": "ls -la"
|
||||
}
|
||||
|
||||
3. SERVER VALIDATES
|
||||
• validateSessionId middleware checks format
|
||||
• validateCommand middleware checks body
|
||||
• Checks session exists in ClaudeService
|
||||
|
||||
4. CLAUDE SERVICE PROCESSES
|
||||
claudeService.sendCommand(sessionId, "ls -la")
|
||||
• Writes to Claude Code process stdin
|
||||
• Returns immediately (response comes later)
|
||||
|
||||
5. SERVER RESPONDS
|
||||
HTTP 202 Accepted
|
||||
{
|
||||
"success": true,
|
||||
"message": "Command sent"
|
||||
}
|
||||
|
||||
6. CLAUDE CODE PROCESSES
|
||||
• Process runs command
|
||||
• Generates output
|
||||
|
||||
7. CLAUDE SERVICE RECEIVES OUTPUT
|
||||
claudeService.handleOutput(sessionId, "file1.txt\nfile2.txt")
|
||||
•
|
||||
▼
|
||||
eventBus.emit('session-output', {
|
||||
sessionId: 'session-123',
|
||||
type: 'stdout',
|
||||
content: 'file1.txt\nfile2.txt',
|
||||
timestamp: 1234567890
|
||||
})
|
||||
|
||||
8. EVENT BUS DISTRIBUTES
|
||||
• Finds all subscribers to 'session-output' for session-123
|
||||
• Calls each subscriber's handler
|
||||
|
||||
9. SSE MANAGER SENDS
|
||||
For each SSE connection for session-123:
|
||||
res.write('event: session-output\n')
|
||||
res.write('data: {"type":"stdout","content":"..."}\n')
|
||||
res.write('\n')
|
||||
|
||||
10. CLIENT RECEIVES
|
||||
EventSource receives event
|
||||
eventSource.addEventListener('session-output', (e) => {
|
||||
const data = JSON.parse(e.data)
|
||||
console.log(data.content) // "file1.txt\nfile2.txt"
|
||||
})
|
||||
|
||||
11. UI UPDATES
|
||||
Terminal displays output
|
||||
```
|
||||
|
||||
## Error Handling Flow
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────────────────────────┐
|
||||
│ Error Handling Flow │
|
||||
└──────────────────────────────────────────────────────────────┘
|
||||
|
||||
Error occurs in ClaudeService
|
||||
│
|
||||
▼
|
||||
eventBus.emit('session-error', {
|
||||
sessionId: 'session-123',
|
||||
error: 'Command failed',
|
||||
code: 'CMD_ERROR',
|
||||
recoverable: true
|
||||
})
|
||||
│
|
||||
▼
|
||||
Event Bus distributes to subscribers
|
||||
│
|
||||
├──▶ SSE Manager
|
||||
│ │
|
||||
│ ▼
|
||||
│ Send to client:
|
||||
│ event: session-error
|
||||
│ data: {"error":"...","code":"CMD_ERROR"}
|
||||
│
|
||||
└──▶ Other subscribers
|
||||
(logging, monitoring, etc.)
|
||||
|
||||
Client receives error
|
||||
│
|
||||
▼
|
||||
eventStream.on('error', (data) => {
|
||||
showErrorToUser(data.error)
|
||||
if (data.recoverable) {
|
||||
showRetryButton()
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
These diagrams illustrate:
|
||||
1. Overall system architecture
|
||||
2. Event flow from client to server and back
|
||||
3. Connection lifecycle
|
||||
4. Reconnection logic
|
||||
5. Before/after comparison
|
||||
6. Component dependencies
|
||||
7. Complete command-to-output flow
|
||||
8. Error handling flow
|
||||
|
||||
Last Updated: 2025-01-21
|
||||
Reference in New Issue
Block a user