addendum-live-debugging-2026-07-19.md (10964B)
1 # Addendum: Live Debugging Session (2026-07-19) 2 3 ## Overview 4 5 This document details all work done during a live debugging session that went 6 beyond the original spec, plan, and tasks. The session focused on connecting the 7 dashboard to a real Hermes API server, fixing integration mismatches, and 8 iterating on UI bugs discovered through live browser testing. 9 10 --- 11 12 ## 1. Hermes API Integration Fixes 13 14 ### 1.1 API Key Configuration 15 16 **Problem:** Dashboard had `hermes_api_key: ""` (empty) in `data/config.json`. 17 All requests to Hermes returned 401 Unauthorized. 18 19 **Fix:** Found the actual API key from `~/.hermes/.env` (`API_SERVER_KEY`) and 20 set it in the dashboard's config: 21 ``` 22 hermes_api_key: 8d249f19fb11f37d4d92e95f10c29fd2b3227e025883630c977eb6c94970c472 23 ``` 24 25 ### 1.2 Response Envelope Unwrapping 26 27 **Problem:** Hermes wraps list responses in `{object: "list", data: [...]}` and 28 single objects in `{object: "hermes.xxx", xxx: {...}}`. The dashboard expected 29 raw arrays/objects. 30 31 **Fix:** Updated all proxy routes to unwrap: 32 - `sessions.py`: `data.get("data", data.get("sessions", data))` 33 - `models.py`: `data.get("data", data.get("models", []))` 34 35 ### 1.3 Session Field Mapping (name → title) 36 37 **Problem:** Dashboard sent `{name: "..."}` but Hermes expects `{title: "..."}`. 38 39 **Fix:** Updated `hermes_client.py` `create_session` and `update_session` to 40 map `name` → `title` in request bodies. Updated tests to expect `title`. 41 42 ### 1.4 Model Normalization 43 44 **Problem:** Hermes returns `{id, owned_by, ...}` for models. Frontend expects 45 `{id, name}`. 46 47 **Fix:** Added `_normalize_model()` in `hermes_client.py` that maps 48 `id → name`. 49 50 ### 1.5 Session Normalization 51 52 **Problem:** Hermes sessions have `{title, started_at, end_reason, ...}`. 53 Frontend expects `{name, created_at, status, ...}`. 54 55 **Fix:** Added `_normalize_session()` in `hermes_client.py` that maps: 56 - `title` → `name` 57 - `started_at` → `created_at` 58 - `end_reason` → `status` 59 - `last_active` → `last_activity` 60 - `source` → `source` (passthrough) 61 62 ### 1.6 Fork Response (parent_id) 63 64 **Problem:** Frontend expected `parent_id` in fork response (spec FR-032). 65 Hermes doesn't include it. 66 67 **Fix:** Added `session["parent_id"] = session_id` after extracting the forked 68 session in `sessions.py`. 69 70 --- 71 72 ## 2. WebSocket Fixes 73 74 ### 2.1 WebSocket URL 75 76 **Problem:** Frontend connected to `/api/ws` but backend route is `/ws/chat`. 77 78 **Fix:** Updated frontend WsClient URL to `/ws/chat`. 79 80 ### 2.2 WS Message Format 81 82 **Problem:** Frontend sent `{type: "chat.message", content: "..."}` but backend 83 expects `{type: "chat", session_id: "...", content: "..."}`. 84 85 **Fix:** Updated ChatView to send `{type: "chat", session_id, content, 86 attachments, model}`. 87 88 ### 2.3 Status Message Format 89 90 **Problem:** Backend sent `{type: "status", connected: true}` (flat) but 91 frontend checked `msg.data?.connected` (nested). 92 93 **Fix:** Changed backend to send `{type: "status", data: {connected: true}}`. 94 95 --- 96 97 ## 3. Layout Fixes 98 99 ### 3.1 Sidebar Positioning 100 101 **Problem:** `md:relative` on sidebar overrode `fixed` positioning at desktop 102 breakpoint, making the sidebar a flex child. This pushed the main content area 103 above the viewport (`top: -715px`). 104 105 **Fix:** Removed `md:relative`, kept sidebar `fixed` at all breakpoints. Added 106 `md:ml-64` to main content to offset for the fixed sidebar. 107 108 ### 3.2 scrollIntoView Scrolling Page 109 110 **Problem:** `MessageList` used `bottomRef.current?.scrollIntoView()` which 111 scrolled the entire page, not just the message container. 112 113 **Fix:** Changed to `container.scrollTop = container.scrollHeight` targeting the 114 `overflow-y-auto` container directly. 115 116 ### 3.3 overflow-hidden on Main 117 118 **Problem:** `overflow-hidden` on `<main>` prevented flex-1 from calculating 119 height correctly. 120 121 **Fix:** Changed to `min-h-0` (the correct flex-shrink pattern). 122 123 --- 124 125 ## 4. Message Loading Fixes 126 127 ### 4.1 No Messages on Session Click 128 129 **Problem:** Clicking a session didn't load its messages. Messages only arrived 130 via WebSocket streaming. 131 132 **Fix:** Added `useEffect` that fetches `/sessions/{id}/messages` when 133 `activeSessionId` changes. 134 135 ### 4.2 Duplicate Messages on Refresh 136 137 **Problem:** TWO identical `useEffect` hooks both loaded messages on 138 `activeSessionId` change. On refresh, both fired → duplicate messages. 139 140 **Fix:** Removed the duplicate effect. Changed from `forEach + addMessage` 141 (incremental append) to `setMessages` (atomic replace). 142 143 ### 4.3 Tool Result Messages as Chat Bubbles 144 145 **Problem:** `role: "tool"` messages (Hermes tool results) were displayed as 146 regular chat messages, creating duplicate/ugly content. 147 148 **Fix:** Backend now filters out `role: "tool"` messages from the 149 `/sessions/{id}/messages` response. Tool results are linked to their parent tool 150 calls via `tool_call_id`. 151 152 --- 153 154 ## 5. Tool Call Fixes 155 156 ### 5.1 Tool Calls Stuck on "Running..." 157 158 **Problem:** Hermes sends `tool.completed` events with `{tool_name, preview, 159 message_id}` but NOT `{tool_call_id}`. Backend mapped `toolCallId = ""` (empty), 160 so `updateToolCall` couldn't find the tool call to update. 161 162 **Fix:** Changed to `tool_id = data.get("tool_call_id", "") or 163 data.get("tool_name", "")` — falls back to `tool_name` when `tool_call_id` is 164 absent. 165 166 ### 5.2 XML Tags in Tool Results 167 168 **Problem:** `<untrusted_tool_result>` wrappers from Hermes were rendered 169 literally in the chat UI. 170 171 **Fix:** Added `_strip_untrusted_tags()` regex function in `hermes_client.py`. 172 Applied to: 173 - Tool result content in `normalize_messages()` 174 - Assistant message content in `normalize_messages()` 175 - Live `tool.completed` events in `chat.py` mapper 176 177 ### 5.3 Tool Result Linking in History 178 179 **Problem:** When loading messages from history, tool results were separate 180 `role: "tool"` messages not linked to their parent tool calls. 181 182 **Fix:** `normalize_messages()` builds a `tool_call_id → result` map from 183 `role: "tool"` messages, then injects results into the matching tool calls. 184 185 --- 186 187 ## 6. Session Management Fixes 188 189 ### 6.1 Session Auto-Naming 190 191 **Problem:** Sessions always showed "Untitled" — never got a proper name. 192 193 **Fix:** After sending the first message, ChatView calls 194 `updateSession(sessionId, {name: truncatedMessage})` where the title is the 195 first 50 characters of the message. 196 197 ### 6.2 Session Filtering (Matrix/Element Style) 198 199 **Problem:** Dashboard showed all 50+ sessions from CLI, Matrix, cron, etc. 200 201 **Fix:** Added source-based filtering. Default view shows only `api_server` 202 sessions. Toggle button: "Show N sessions from other sources". Sessions from 203 `cron`, `matrix`, `cli` are hidden by default. 204 205 ### 6.3 Session Sorting 206 207 **Problem:** Sessions appeared in arbitrary order. New sessions at bottom. 208 209 **Fix:** Added sorting by `last_activity ?? created_at` descending (newest 210 first) in `_applyFilter()`. 211 212 ### 6.4 Archive Functionality 213 214 **Problem:** No way to hide sessions without deleting them from Hermes. 215 216 **Fix:** Added: 217 - `archived` field to Session interface 218 - `archiveSession()` in session store (PATCH with `{archived: true}`) 219 - "Archive" button in session context menu (⋮ → Archive) 220 - Archived sessions filtered from default view 221 222 --- 223 224 ## 7. Vision Model Configuration 225 226 **Change:** Set Grok 4.5 as the vision model in `~/.hermes/config.yaml`: 227 ```yaml 228 auxiliary: 229 vision: 230 provider: xai 231 model: grok-4.5 232 ``` 233 234 --- 235 236 ## 8. Platform-Aware Keyboard Shortcut 237 238 **Change:** Composer hint text now shows "⌘+Enter to send" on Mac and 239 "Ctrl+Enter to send" on other platforms. Uses `navigator.platform` detection. 240 241 --- 242 243 ## 9. Typing Indicator 244 245 **Problem:** No visual feedback between sending a message and receiving the 246 first SSE event. 247 248 **Fix:** Added `thinking` state to chat store. Set `true` on message send, 249 cleared on `run.started`. Shows animated bouncing dots with "Thinking..." text 250 in the MessageList. 251 252 --- 253 254 ## 10. Test Session Cleanup 255 256 Deleted 22 test sessions created during debugging (all `api_server` source with 257 0 messages or no name). Left 50 external sessions (cli, matrix, cron) intact. 258 259 --- 260 261 ## 11. Mock Server Updates 262 263 Updated `mock_hermes.py` paths to match real Hermes API: 264 - `/sessions` → `/api/sessions` 265 - `/models` → `/v1/models` 266 - Session creation uses `title` not `name` 267 268 --- 269 270 ## Known Remaining Issues (as of 2026-07-19) 271 272 1. **Archive doesn't persist across refresh** — `archived` flag is only in 273 frontend state, not synced to Hermes or local DB. 274 2. **New sessions appear at bottom until refresh** — `createSession` appends to 275 the list; `_applyFilter` sort only runs on `listSessions`. 276 3. **Message count doesn't update until refresh** — `message_count` is only 277 fetched from Hermes on session list load, not updated in real-time. 278 4. **Frontend mutation score: 55%** (target 80%) — SessionList (0%, 134 mutants) 279 is biggest gap. 280 5. **Grok 4.5 vision API intermittent 503/404** — vision model temporarily 281 unavailable during testing. 282 283 ## 2026-07-19 — Fix: Pasted image attachments not forwarded to Hermes (fix-002) 284 285 **Bug**: User pastes image into Composer → thumbnail shows → send → agent never 286 sees the image. Root cause: `_handle_chat` in `chat.py` extracted `session_id` 287 and `content` from the WS payload but ignored the `attachments` field entirely. 288 The uploaded file sat on disk unused. 289 290 **Fix**: Added `_load_attachment_content()` to `chat.py` which resolves attachment 291 IDs by loading metadata from SQLite, reading files from disk, and converting 292 images to base64 data URLs. `_handle_chat` now extracts `attachments` from the 293 WS data and appends resolved content to the message before forwarding to Hermes. 294 295 **Files changed**: 296 - `backend/app/routes/chat.py` — added `_load_attachment_content`, modified `_handle_chat` 297 - `backend/tests/test_chat.py` — 3 new tests (image data URL, non-image path, no-attachments preservation) 298 299 **Tests**: 224 passed, 0 failures (3 new tests added). 300 301 ## 2026-07-19 — Fix: Attachment DB never initialized (fix-002 addendum) 302 303 **Root cause update**: The PRIMARY bug was that `main.py` never called `set_db()` for 304 the `attachments` module. The `_db` module-level variable stayed `None`, so every 305 upload attempt crashed with `RuntimeError: Attachment DB not initialized`. The 306 frontend got a 500 and no attachment was ever stored. 307 308 The SECONDARY bug (attachments not forwarded to Hermes via `_handle_chat`) was also 309 fixed but was moot until the DB init was resolved. 310 311 **Files changed**: 312 - `backend/app/routes/attachments.py` — Added `set_db()` (matching topics.py pattern) 313 - `backend/app/main.py` — Import + call `set_attachment_db(_DB)` in lifespan 314 - `backend/app/routes/chat.py` — `_load_attachment_content` + `_handle_chat` attachment forwarding 315 - `backend/tests/test_chat.py` — 3 new tests 316 317 **Browser test**: Logged in via headless browser, uploaded a 1x1 red PNG via file input, 318 sent "Do you see this test image?". Agent called `vision_analyze` and responded: 319 "That's a 1x1 pixel PNG — a single red pixel." Image was successfully received. 320 321 **Tests**: 224 passed, 0 failures.