Add per-session context-window donuts#125
Merged
Merged
Conversation
Each agent circle in the Overview band's Agents row now renders as a donut filled to the agent's context-window occupancy (currentTokens / tokenLimit), sourced from the SDK session.usage_info event through the push-status pipeline. An agent that has not reported usage (or after a restart — the value is not persisted) falls back to the solid circle. - Shared: ContextUsage type + fraction helper; WorktreeStatus.ContextUsage. - Reporting extension: subscribe to session.usage_info, forward it as a new status-preserving usage_info wire kind (currentTokens, tokenLimit). - Server: SessionEvent.UsageInfo + status-preserving fold into SessionStatus.ContextUsage; wire DTO + parseEvent/kindText; thread through CodingToolResult -> WorktreeStatus. In-memory only (not persisted). - Client: GroupMember carries the per-agent fraction; agentColumn renders a .overview-donut with an inline --ctx-fill; index.html conic-gradient donut. - DemoFixture showcase values; tests for the fold, DTO parse, and aggregation.
Address focused-review findings F1-F4 (doc/spec staleness introduced by the usage_info addition): - docs/spec/session-status-push.md:147,156 - wire contract now lists currentTokens/tokenLimit and the usage_info -> UsageInfo mapping (7 -> 8). - src/Server/SessionActivityService.fs - parseEvent/parseReport doc comments updated from seven/7 kinds to eight/8. - src/Tests/SessionActivityServiceTests.fs - module summary updated to eight kinds.
The Agents-row donut now fills its accent arc to the fraction of context still free (ContextUsage.remainingFraction), so a healthy low-usage agent reads as a nearly full ring and one approaching its limit thins to a sliver — instead of a field of tiny slivers for the common low-usage case. fraction (used) stays as the truthful base; the view takes its complement and the CSS var is renamed --ctx-remaining. Copilot-Session: eeeb328d-1083-4a2f-b9ff-f9362084eca8
Replace the single per-worktree context donut with one donut per live session across worktree cards, the Overview band Agents row, and the drill-down chips; collapsed repo headers show per-session plain dots. This absorbs the tm-multi-dot per-session-dot work (now obsolete) and dissolves the donut-blinks-to-solid bug: usage is kept per session instead of collapsed onto a single footer session that flipped to None whenever the winner changed.
- Shared: SessionDot { Status; ContextUsage option }; WorktreeStatus.Sessions replaces per-worktree ContextUsage.
- Server: CodingToolResult.SessionStatuses built in fromPushSessions from the open, freshness-adjusted sessions (each keeps its own usage), ordered Working->Waiting->Idle then most-recent; WorktreeApi + DemoFixture threaded through.
- Client: CardViews.sessionDots renders per-session donuts (dots on repo headers); OverviewData.GroupMember carries Sessions; OverviewBand clusters per-session donuts in the row and drill-down chips; index.html .ct-donut + --ctx-remaining.
- Tests: per-session SessionStatuses coverage; fixtures + mocked E2E JSON updated for the new field.
Copilot-Session: eeeb328d-1083-4a2f-b9ff-f9362084eca8
There was a problem hiding this comment.
Pull request overview
Adds per-session context-window usage indicators from the Copilot SDK through server aggregation to dashboard donuts.
Changes:
- Adds
usage_infoingestion and per-session context models. - Renders usage donuts across cards, Overview, and drill-downs.
- Updates fixtures, tests, and specifications.
Show a summary per file
| File | Description |
|---|---|
src/Extension/reporting/extension.mjs |
Forwards usage events. |
src/Shared/Types.fs |
Adds usage and session-dot types. |
src/Server/SessionActivity.fs |
Folds usage snapshots. |
src/Server/SessionActivityService.fs |
Parses the new wire event. |
src/Server/SessionActivityStore.fs |
Rehydrates usage as ephemeral. |
src/Server/CodingToolStatus.fs |
Produces ordered session dots. |
src/Server/WorktreeApi.fs |
Exposes sessions to clients. |
src/Server/DemoFixture.fs |
Adds demonstration usage data. |
src/Client/OverviewData.fs |
Carries sessions into Overview groups. |
src/Client/OverviewBand.fs |
Renders Overview donuts. |
src/Client/CardViews.fs |
Renders card and header session dots. |
src/Client/index.html |
Styles dots and donuts. |
src/Tests/WorktreeFixtures.fs |
Updates shared fixture shape. |
src/Tests/SessionActivityTests.fs |
Tests usage folding. |
src/Tests/SessionActivityStoreTests.fs |
Updates stored-status fixtures. |
src/Tests/SessionActivityServiceTests.fs |
Tests wire parsing and validation. |
src/Tests/OverviewDataTests.fs |
Tests session aggregation. |
src/Tests/NavigationTests.fs |
Updates navigation fixtures. |
src/Tests/EventProcessingTests.fs |
Updates event fixtures. |
src/Tests/ConfirmModalTests.fs |
Updates modal fixtures. |
src/Tests/CodingToolPushSourceTests.fs |
Tests per-session ordering and usage. |
src/Tests/CanvasAwarenessTests.fs |
Updates canvas fixtures. |
src/Tests/ArchiveTests.fs |
Updates archive models and JSON. |
docs/spec/worktree-monitor.md |
Documents card donuts. |
docs/spec/session-status-push.md |
Documents the wire contract. |
docs/spec/beads-overview-band.md |
Documents Overview rendering. |
Review details
- Files reviewed: 26/26 changed files
- Comments generated: 5
- Review effort level: Medium
Comment on lines
+119
to
+121
| // A pure gauge: record the latest occupancy, never touch Status (or any other field). | ||
| | UsageInfo(currentTokens, tokenLimit) -> | ||
| { s with ContextUsage = Some { CurrentTokens = currentTokens; TokenLimit = tokenLimit } } |
Comment on lines
+35
to
+37
| prop.className $"ct-dot ct-donut {ctClassName s.Status}" | ||
| prop.title (ctTooltip s.Status) | ||
| prop.style [ style.custom ("--ctx-remaining", string (ContextUsage.remainingFraction usage)) ] ] |
Comment on lines
+118
to
+119
| prop.className [ "overview-circle"; "overview-donut"; accent ] | ||
| prop.style [ style.custom ("--ctx-remaining", string (ContextUsage.remainingFraction usage)) ] ] |
Comment on lines
+213
to
+214
| prop.className [ "overview-chip-dot"; "overview-donut" ] | ||
| prop.style [ style.custom ("--ctx-remaining", string (ContextUsage.remainingFraction usage)) ] ] |
Comment on lines
+114
to
+119
| match s.ContextUsage with | ||
| | Some usage -> | ||
| Html.span | ||
| [ prop.key key | ||
| prop.className [ "overview-circle"; "overview-donut"; accent ] | ||
| prop.style [ style.custom ("--ctx-remaining", string (ContextUsage.remainingFraction usage)) ] ] |
… progress-bar drilldown chips - Overview band: flatten the per-worktree cluster so every session circle shares one uniform gap regardless of which worktree it belongs to. - Cards: bump the per-session dots/donuts from 10px to 15px to match the band circles. - Drill-down: drop the per-session chip donuts; each chip now fills its background as a subtle left-to-right progress bar to its most-loaded session's context usage (used), faint enough to preserve label contrast. Copilot-Session: eeeb328d-1083-4a2f-b9ff-f9362084eca8
Flatten every worktree's per-session dots into a single row in the collapsed repo header (List.collect) with one uniform gap, instead of per-worktree .ct-dots clusters that spaced within-worktree dots tighter than across-worktree ones. Copilot-Session: eeeb328d-1083-4a2f-b9ff-f9362084eca8
The Agents band previously grouped each worktree once, under its single collapsed activity/skill. Now grouping is per session: each live session lands in the group its OWN status and running skill classify to, so a worktree whose sessions differ (e.g. one running pr while two sit idle) splits across the matching groups instead of clumping. A group's count is its number of sessions, and a member carries only the subset of its sessions in that group. - Shared: SessionDot gains Skill (the session's own running skill). - Server: fromPushSessions threads each session's Skill into SessionStatuses. - Client: sessionGroupKind classifies a session from its status/skill; aggregate folds per session; band circles render only the group's matching sessions. - DemoFixture + tests updated; new tests cover split-across-groups, per-session count, and per-group session subsets. Copilot-Session: eeeb328d-1083-4a2f-b9ff-f9362084eca8
0101
enabled auto-merge (squash)
July 20, 2026 09:25
0101
disabled auto-merge
July 20, 2026 09:34
0101
enabled auto-merge (squash)
July 20, 2026 09:53
UsageInfo no longer shares the status last-write-wins clock: a dedicated apply branch updates ContextUsage on its own ContextUsageAt ordering clock, so a usage report can't block a slightly-earlier status transition nor be discarded by a newer one (both arrival orders covered). Add per-session Sessions to the overview-band fixture plus E2E assertions for the donut arc (--ctx-remaining), multi-session clustering and the drill-down --ctx-used fill; the per-session aggregate had left the empty-Sessions fixture rendering no Agents section. Bump the ct-dot size assertion to 15px. Copilot-Session: ec7f3da2-617e-41f5-9c1b-6ca9ddc73c65
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Agents can quietly fill their context window, and nothing on the dashboard surfaced it. Operators had no at-a-glance signal for which sessions are approaching their limit and should be wrapped up or resumed fresh.
Changes
Adds a context-window donut per live session across the dashboard, wired end-to-end from the SDK through to the UI.
Wire pipeline (SDK → UI)
src/Extension/reporting/extension.mjs— subscribe tosession.usage_infoand forward a new status-preservingusage_infowire kind (currentTokens,tokenLimit). A non-positive/non-finite limit is dropped; a negative current is clamped.src/Server/SessionActivity.fs/SessionActivityService.fs—SessionEvent.UsageInfofolds intoSessionStatus.ContextUsage(a pure gauge; never changes status); DTO parse +kindTextround-trip. In-memory only (ephemeral upstream, not persisted).Per-session model (absorbs the
tm-multi-dotbranch)src/Shared/Types.fs—ContextUsagewithfraction/remainingFraction; newSessionDot { Status; ContextUsage option };WorktreeStatus.Sessionsreplaces the per-worktreeContextUsage.src/Server/CodingToolStatus.fs—CodingToolResult.SessionStatusesbuilt infromPushSessionsfrom the open, freshness-adjusted sessions (each keeps its own usage), ordered Working -> Waiting -> Idle then most-recently-seen.Rendering
Why per-session (not per-worktree)
An earlier single per-worktree donut sourced usage from the collapsed "footer" session, so it blinked back to a solid circle whenever a newer session became the winner before reporting usage (the common case under CLI session churn). Keeping usage on each
SessionDotremoves the collapse and the blink.Tests
SessionStatusescoverage inCodingToolPushSourceTests(per-session ordering, each session keeps its own usage, stale/closed sessions excluded, empty forNoSession/retained fallback).OverviewDataTests, fixtures, and the mocked E2E JSON inArchiveTestsfor the newSessionsfield.dotnet test src/Tests/Tests.fsproj --filter "Category=Fast"-> 1275 passed, 0 failed.Rebased onto current
main(includes #121-#124); clean, no conflicts.