Live session awareness and multiple session overview - #136
Live session awareness and multiple session overview#136HermannBjorgvin wants to merge 10 commits into
Conversation
clawdmeter_sessions.py sidecar (stdlib-only) observes Claude Code hook events on loopback, tracks per-session state/context/todos/subagents, sorts attention-first, and writes an MTU-fitted wire payload to ~/.clawdmeter/sessions.json. The bash daemon ships it to the SS GATT characteristic on its 5s tick (byte-exact UTF-8 write), inert when the sidecar is off. Includes systemd unit, config keys, install.sh hook prompt, SESSIONS.md, and 46 unit tests. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
New BOARD_HAS_SESSION_VIEWS capability (on: amoled_216, sim) grows the usage-screen resolver to five sub-views: ONE-CHAT and SEVERAL-CHATS cards with sid-keyed identity, 260ms reorder slide, shared-phase attention pulse, and idle dimming. Session rows arrive on a new SS characteristic (…0005) parsed in main.cpp, waking the panel on activity. Lucide list-todo/users-round icons (RGB565A8), sim scenario playback for ss payloads, LV_MEM_SIZE 128K on flag-on envs. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Rows gain an appended tok field (index 11): context tokens in 1K units, -1 when unknown, read in the same transcript pass as ctx% so the pair can never disagree. Wire-format table added to SESSIONS.md. Tests grow to 50. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
) Cards show absolute token counts (190K / 1.2M) with the bar carrying the percentage; todo badge in terra-cotta, subagent badge in palette- matched purple (THEME_PURPLE), both recolored at render time. State line: dot flush with the bar edge, text vertically centered, badges + timer right-aligned; waiting text pulses with the dot. ONE-CHAT is a 5h quota panel plus a chat card ("95% of context used", token twin, pill-styled model tag), names ellipsized firmware-side against a worst-case model pill. Sim scenario uses realistic session names. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Focus-card names measure the model pill (and list-card names their token label) on every content update instead of reserving a static worst-case width, so ellipsized names run to within 12px of the pill and a hidden neighbor yields the full row. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Adds opt-in “live session awareness” end-to-end: the host can observe Claude Code sessions via hooks and stream a compact session roster over BLE, and supported firmware builds render those sessions as auto-selected sub-views inside the existing usage screen.
Changes:
- Firmware: adds session-row data model + BLE “SS” characteristic and UI sub-views (one-chat focus + multi-chat cards) gated by
BOARD_HAS_SESSION_VIEWSandboard_caps().has_session_views. - Host (Linux): adds a stdlib-only Python sidecar to ingest Claude Code hook events, maintain/sort session state, fit to an MTU budget, and hand off the payload to the existing bash daemon for BLE writes.
- Docs/config: adds capability-flag documentation, host setup docs, config knobs, and a systemd user unit for the sidecar.
Reviewed changes
Copilot reviewed 32 out of 38 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| install.sh | Installer now wires up optional session awareness (config prompt, hook installation, sidecar unit install/enable). |
| firmware/src/ui.h | Exposes ui_update_sessions() API for session-view updates. |
| firmware/src/ui.cpp | Implements session card views, resolver integration, animations, and quota mini-widgets (gated by capability). |
| firmware/src/theme.h | Adds THEME_PURPLE for the subagent badge color. |
| firmware/src/main.cpp | Adds session payload parsing and feeds session updates into UI when session data arrives. |
| firmware/src/icons.h | Adds new RGB565A8 badge icons for session cards (todo/subagents). |
| firmware/src/hal/board_caps.h | Adds has_session_views runtime capability bit. |
| firmware/src/data.h | Adds session wire constants, enums, and SessionRow/SessionList structs; adds compile-time gate default. |
| firmware/src/boards/waveshare_lcd_154/caps.cpp | Plumbs has_session_views into caps for this board. |
| firmware/src/boards/waveshare_lcd_154/board.h | Sets BOARD_HAS_SESSION_VIEWS to 0 for the 240×240 LCD board. |
| firmware/src/boards/waveshare_amoled_216/caps.cpp | Enables runtime capability mapping for session views on 2.16 S3. |
| firmware/src/boards/waveshare_amoled_216/board.h | Sets BOARD_HAS_SESSION_VIEWS to 1 for the 480×480 AMOLED-2.16 S3. |
| firmware/src/boards/waveshare_amoled_216_c6/caps.cpp | Plumbs has_session_views into caps for this board. |
| firmware/src/boards/waveshare_amoled_216_c6/board.h | Keeps session views off for C6 variant pending hardware smoothness checks. |
| firmware/src/boards/waveshare_amoled_206/caps.cpp | Plumbs has_session_views into caps for this board. |
| firmware/src/boards/waveshare_amoled_206/board.h | Keeps session views off pending portrait-layout work. |
| firmware/src/boards/waveshare_amoled_18/caps.cpp | Plumbs has_session_views into caps for this board. |
| firmware/src/boards/waveshare_amoled_18/board.h | Keeps session views off pending portrait-layout work. |
| firmware/src/boards/waveshare_amoled_18_c6/caps.cpp | Plumbs has_session_views into caps for this board. |
| firmware/src/boards/waveshare_amoled_18_c6/board.h | Keeps session views off (no PSRAM + portrait layout). |
| firmware/src/boards/template/caps.cpp | Updates template caps to include has_session_views. |
| firmware/src/boards/template/board.h | Adds template flag for session views (default off). |
| firmware/src/ble.h | Adds session characteristic accessors (ble_has_session_data, ble_get_session_data). |
| firmware/src/ble.cpp | Adds SS characteristic, shared write-guard, larger session buffer, and MTU request. |
| firmware/platformio.ini | Enables session views and increases LVGL heap for the target env. |
| docs/porting/capability-flags.md | Documents BOARD_HAS_SESSION_VIEWS and its special build-flag requirement. |
| daemon/tests/test_sessions.py | Adds unit tests covering sidecar state machine, sorting, fitting, and heuristics. |
| daemon/SESSIONS.md | Adds host setup guide, wire format reference, and configuration docs for session awareness. |
| daemon/config.example | Adds config keys and commentary for session awareness. |
| daemon/clawdmeter-sessions.service | Adds systemd user unit to run the session-awareness sidecar. |
| daemon/clawdmeter_sessions.py | Implements the hook listener sidecar (HTTP server, state machine, fitting, atomic handoff). |
| daemon/claude-usage-daemon.sh | Adds SS characteristic writes, UTF-8-safe byte writing, and change-detection for sessions payload. |
| assets/icon_users-round.svg | Adds Lucide source icon for subagent badge. |
| assets/icon_list-todo.svg | Adds Lucide source icon for todo badge. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| strcat(buf, "..."); | ||
| lv_text_get_size(&sz, buf, font, 0, 0, LV_COORD_MAX, LV_TEXT_FLAG_NONE); | ||
| if (sz.x <= max_w) break; | ||
| len--; |
| def _read_body(self): | ||
| try: | ||
| length = int(self.headers.get("Content-Length") or 0) | ||
| except ValueError: | ||
| length = 0 | ||
| if length <= 0: | ||
| return b"" | ||
| body = b"" | ||
| remaining = length | ||
| while remaining > 0: | ||
| chunk = self.rfile.read(min(remaining, 65536)) | ||
| if not chunk: | ||
| break | ||
| remaining -= len(chunk) | ||
| if len(body) < MAX_BODY_BYTES: | ||
| body += chunk # oversize tails are drained but not kept | ||
| return body |
The 5h/7d strip uses #129's measured layout — styrene_24 percentages in fixed right-aligned columns, styrene_20 dim tags, a 30px vertically centered band — and its vertical rhythm: list top at 148, 10px card gaps (90px pitch, keeping the mid-card clip affordance). Card internals unchanged. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Don't have a board on me so this is untested on real hardware. Will test it on actual hardware on Tuesday when I'm back at work. |
The SEVERAL-CHATS list runs to the physical bottom edge (y=480) with a 60px transparent-to-black style gradient band over it, so the clipped card tapers out instead of hitting a hard cut above a dead margin. Buffer-free (bg_main_opa/bg_grad_opa), input-transparent so the splash tap passes through, and created after the card container so reorder slides can never draw over it. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Still haven't looked at how the daemon and hooks work on this PR. I think it's likely I'll have some opinions on those but going to look at it again a bit later |
|
Readability is a concern here, the UI might need to be reworked a bit so the user doesn't need a microscope to monitor the sessions. |
List cards and the ONE-CHAT boxes span the panel width with a 20px inner text inset, so text keeps the old glass-edge clearance while bars and name budgets gain the freed 32px. The 5h/7d strip keeps its margins, which now align with the card text line. RESTING and the classic views are untouched. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The device's Styrene fonts cover ASCII 32..126 only, so the U+2026 ellipsis rendered as a tofu box in elided card names. "..." costs the same three UTF-8 bytes, so the payload byte-budget math is unchanged. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
List cards: name 20 -> 28, state line 16 -> 24, card 80 -> 94 -> 108 px,
bigger dot, wider state ellipsis budget. Quota strip: 5h/7d tags 20 -> 24,
percentages 24 -> 28, wider text columns — same band height, so the card
list doesn't move. Focus card: name 28 -> 48, secondary text (state,
model pill, context/token row) 20 -> 24, card 150 -> 176 px.
label_set_ellipsized() now middle-elides, keeping the trailing four
characters — the host's session discriminator ("-35" vs "-2c") survives
firmware width elision instead of being truncated away.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Implements #135.
The device now shows what Claude Code is doing right now: which chats are open,
how full each context window is, todo and subagent counts — and, above all,
whether any chat is stuck waiting on you. Nothing is operated by hand: the
display picks its own layout and ordering from Claude's state, and a chat that
needs you is always in the top slot with a pulsing indicator.
The feature is opt-in twice over: gated per board by a capability flag
(enabled on the 480×480 AMOLED-2.16 to start), and off by default on the
host until the hook integration is configured. Without either, the device
behaves exactly as it does on
main.On-device views (firmware)
No new screen. The usage screen's resolver (
update_view_state()infirmware/src/ui.cpp) selects among five sub-views: pairing hint, no-data,quota panels (RESTING), ONE CHAT, and SEVERAL CHATS, per the §2.1 flowchart in
the issue. A waiting chat pins the view; after the last chat closes the view
lingers 10 minutes (
CHAT_LINGER_MS) before returning to RESTING.firmware/src/ui.cpp): name (ellipsized firmware-sideagainst a worst-case
sonnet-5.2model pill), context bar, and a state line—
●indicator plus state text on the left, a right-aligned cluster oftodo badge (terra-cotta), subagent badge (purple
#9e87be, hue-matched tothe palette), and time-in-state.
95% of context used, the absolute token count (190K/1.2M), and amodel pill. The long-form wording deliberately teaches what the terse
multi-chat bars mean.
88px pitch — the clipped fourth card is the "more below" affordance. The
host pre-sorts (waiting → working → idle); the firmware renders received
order and never re-sorts.
700ms phase; rank changes slide cards 260ms ease-out with stable
sid-keyed identity; content refreshes move nothing. Idle cards dim to 60%
opacity via plain
opa(notopa_layered, which would allocate compositebuffers).
(
idle_note_activity()infirmware/src/main.cpp), so the panel is awakewhile Claude works and dark when nobody is.
BOARD_HAS_SESSION_VIEWScompile-time flag + runtimeboard_caps().has_session_views(documented indocs/porting/capability-flags.md). Flag-off boards compile the card poolout entirely — verified: the C6 build contains zero session symbols.
Flag-on envs run
LV_MEM_SIZE=128K(the ~90-widget card pool exhaustsLVGL's 64K default pool).
list-todo/users-roundat 24/16px, RGB565A8 with areal alpha plane, recolored at render time so the 60% dim rides the normal
draw path (
firmware/src/icons.h, sources inassets/).BLE wire
Session rows arrive on a new SS characteristic
4c41555a-4465-7669-6365-000000000005(firmware/src/ble.cpp), behind thesame single-owner write guard as the existing RX characteristic, with
setMTU(517)and a 1KB receive buffer. Payload:{"ss":[ // [sid, label, state, ctx%, elapsed_s, model, tool, ntools, nagents, tdone, ttotal, tok] ["a3","billing-svc-3f",6,95,62,1,0,0,0,3,7,190] ]}State codes 0–10 are append-only wire constants (
firmware/src/data.h).ctx−1 hides the bar rather than drawing it empty;tokis context tokensin 1K units (−1 or absent → the card falls back to showing the percentage).
Malformed payloads are dropped and the last good list stays on screen
(
firmware/src/main.cpp).Host: session sidecar (daemon)
daemon/clawdmeter_sessions.py— a stdlib-only Python hook listener, usableas a library or standalone:
127.0.0.1:<hook_port>; the listener is aread-only observer (answers 204, rejects non-loopback peers, can never
block or approve a tool call).
PostToolUseFailureso a failed tool can't leave the concurrent-tool count stuck. Tool state
clears on
Stop/UserPromptSubmit, notPostToolUse, so the state linedoesn't flicker between tools.
<config-dir>/sessions/<pid>.json,checked against pid + process start time so pid reuse can't fake a live
session), with a 30s grace and a 6h staleness sweep — never from activity
timeouts, because a chat blocked on a permission prompt is silent.
window heuristic: 200k default,
[1m]→ 1M, snap-up on overflow,context_window_kconfig pins it.tokcomes from the same read as thepercentage so the two can never disagree.
(bucket, -last_event_at); MTU fitting middle-elides labels to an8-char floor (trailing discriminators survive) before dropping rows from
the tail, so a waiting chat is never the one dropped.
~/.clawdmeter/sessions.json; thebash daemon (
daemon/claude-usage-daemon.sh) ships it to the SScharacteristic on its existing 5s tick with a byte-exact UTF-8 GATT write.
Fully inert when the sidecar isn't running or the firmware lacks the
characteristic.
daemon/clawdmeter-sessions.service(systemd user unit),hook_port/context_window_k/sessions_budget_bytesindaemon/config.example, aninstall.shprompt that merges the hook blockinto
~/.claude/settings.jsonvia--install-hooks, and setup docs +wire-format reference in
daemon/SESSIONS.md.Scope
rotation fix (Fix touch not tracking the auto-rotating display #120); ordering already keeps anything urgent above the fold.
hook_portthe device is exactly today's device.
waveshare_amoled_216. Other boards follow the §7checklist (flag flip + hardware look).
Verification
SDL2 build (attached), including pixel-level alignment checks and
pulse-phase captures. Not yet verified on 2.16 hardware — needs the §7
pass (three chats, permission block, reorder smoothness) on a real panel
before merge.
pytest daemon/tests/test_sessions.py) covering thestate machine, sort, eliding, MTU ladder, and window heuristic, plus a
loopback smoke test driving the listener with curl'd hook sequences.
Screenshots
🤖 Generated with Claude Code