theo-agent-dashboard

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README

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.