|
| 1 | +# Chat Architecture |
| 2 | + |
| 3 | +## Data Flow |
| 4 | + |
| 5 | +``` |
| 6 | +Client (SolidJS) Server (Express) OpenClaw Gateway |
| 7 | + │ │ │ |
| 8 | + ├──GET /api/threads/:key/history────►│──readRecentMessages(JSONL)─────────┤ |
| 9 | + │◄─────────JSON {turns, hasMore}─────┤ │ |
| 10 | + │ │ │ |
| 11 | + ├──GET /api/threads/:key/events─────►│ SSE connection │ |
| 12 | + │◄─────────SSE: status, work,────────┤◄──WS events (chat, agent)──────────┤ |
| 13 | + │ stream, turn, etc │ │ |
| 14 | + │ │──JSONL poll (2s interval)───────────┤ |
| 15 | + │◄─────────SSE: work (tool calls)────┤ (reads new bytes from file) │ |
| 16 | +``` |
| 17 | + |
| 18 | +## History Loading |
| 19 | + |
| 20 | +- **HTTP GET** `/api/threads/:key/history` — fast, file-based JSONL parsing (reads last 2000 messages) |
| 21 | +- Cached at two levels: |
| 22 | + 1. `historyCache` in openclaw.ts — keyed by sessionKey, invalidated by file mtime+size (capped at 50) |
| 23 | + 2. `historyResponseCache` in routes.ts — 5s TTL, prevents re-serialization (auto-cleaned every 30s) |
| 24 | +- Client fetches history immediately on SSE connect, with 3 retries on failure |
| 25 | + |
| 26 | +## Live Events (SSE) |
| 27 | + |
| 28 | +- Server-Sent Events at `/api/threads/:key/events` |
| 29 | +- Events: `status`, `stream`, `work`, `turn`, `compacting`, `error`, `backend-status`, `queue`, `user-message` |
| 30 | +- On connect: sends `backend-status` and `queue`, replays cached live state (status, work items, stream text) |
| 31 | +- Backend WS events → chat.ts EventEmitter → SSE endpoint |
| 32 | +- Keep-alive ping every 30s |
| 33 | + |
| 34 | +## JSONL Polling |
| 35 | + |
| 36 | +- Gateway WS doesn't stream tool_call/tool_result events |
| 37 | +- When agent status is `working`/`thinking`, polls JSONL file every 2s |
| 38 | +- Reads only NEW bytes (tracks file position) |
| 39 | +- Starts on: backend status change to working/thinking, or SSE client connect if agent is already active |
| 40 | +- Stops on: status → idle, or last SSE client disconnects |
| 41 | +- Tracks seen tool IDs to avoid duplicates |
| 42 | + |
| 43 | +## Caches |
| 44 | + |
| 45 | +| Cache | Location | Invalidation | Bounds | |
| 46 | +| -------------------- | ----------- | ------------------------- | ------------------- | |
| 47 | +| historyCache | openclaw.ts | file mtime+size | 50 entries (LRU) | |
| 48 | +| historyResponseCache | routes.ts | 5s TTL, turn events | auto-cleaned 30s | |
| 49 | +| currentWork | chat.ts | cleared on turn/idle | capped at 200 items | |
| 50 | +| currentStatus | chat.ts | cleared on turn | per-thread | |
| 51 | +| currentStreamText | chat.ts | cleared on turn/tool_call | per-thread | |
| 52 | + |
| 53 | +## SSE Client Tracking |
| 54 | + |
| 55 | +- `sseClientCount` map tracks active SSE connections per thread |
| 56 | +- `trackSSEClient` on connect, `untrackSSEClient` on `req.close` |
| 57 | +- JSONL polling stops when count reaches 0 |
| 58 | + |
| 59 | +## Failure Modes |
| 60 | + |
| 61 | +- Stuck status: 5-minute timeout auto-resets to idle |
| 62 | +- History fetch failure: client retries 3 times with backoff |
| 63 | +- SSE disconnect: EventSource auto-reconnects (browser built-in) |
| 64 | +- WS disconnect: exponential backoff reconnect with jitter |
0 commit comments