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:
uroma
2026-01-22 14:43:05 +00:00
Unverified
parent b82837aa5f
commit 55aafbae9a
6463 changed files with 1115462 additions and 4486 deletions

View 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