Skip to content

cmux NEXT: Session Info's Usage and the sidebar hover card show what each session spends (carry #27923) - #28773

Merged
teamleaderleo merged 33 commits into
feat-cmux-nextfrom
lane/carry-27923
Nov 1, 2026
Merged

teamleaderleo merged 33 commits into
feat-cmux-nextfrom
lane/carry-27923

Conversation

@teamleaderleo

@teamleaderleo teamleaderleo commented Oct 31, 2026 •

Copy link
Copy Markdown
Collaborator

Carry of #27923

This PR carries #27923 (its owner's lane is gone). It keeps that PR's commits and authorship as they are: the branch is #27923's head cd4225c with origin/feat-cmux-next merged in (a merge, not a rebase or squash), plus two new commits.

Conflicts resolved (merge 99e7ca0):

  • plans/cmux-next/sidebar-owner.md: base added "Runaway session warning badge (2026-10-30)" where this PR added "Hover card names each agent chat's usage (2026-10-30)". Kept both sections, base's first. This PR's two Leo quotes (the new section and its table row) now name the host the way base's section does, [one agent host's].
  • webviews/src/agent-session/acpmux/Localizable.xcstrings: base added the feature tip, credits and Background tasks keys at the top where this PR added its chatInspector.usage.* keys. Kept both sides' keys (2024 keys, no duplicates, valid JSON).

BLOCK fixed (9de6ae6): in Session Info's Usage, the All sessions list sorted by 24h cost showed a session's 24h token count when its cost was unknown (UsageSection.tsx sortValue). A token count sat under a cost heading. It now shows the em dash (—), as every other unknown number in Usage does. The Context sort is unchanged: it still shows context tokens.

  • Test: session-usage.test.tsx "sorted by 24h cost, a session whose cost is unknown shows an em dash, never its token count; by context it shows its context". Red without the fix (received "9M", expected "—"; 8 pass, 1 fail). Green with it: 9 pass, 0 fail. inspector-section-order.test.tsx also passes (17 pass across both files).
  • Gallery: new synthetic state usage-unknown-cost (e0319f0, committed before the fix so the same state renders both ways). It is sorted by 24h cost and includes a peer Codex session that has tokens and context but no readable cost.

Overlap: pr-overlap could not run (the shared GitHub REST quota was exhausted at the time). Base's runaway badge (#27899) touches the same sidebar-owner plan, and the conflict above is resolved.

Evidence (this carry)

Gallery agent-pane.chat-inspector, state usage-unknown-cost (synthetic data), 320 px, Chromium, captured in one fast-checks run (run 41159679892). Before is e0319f0 (the gallery state without the fix); after is 9de6ae6 (the head).

Before After
Light before light after light
Dark before dark after dark

Viewed after image: in both themes, All sessions is sorted by 24h cost ("24h cost" pressed, filling only its own button). dep-fleet $1,421, dep-land $1,288 and inspector-polish $61.20 come first, then unpriced-codex (lane-vm) ends in a right-aligned em dash (—). The before stills show "52M" there, its 24h token count. Nothing else in the panel changed: this chat's own Usage rows, Connectors and the sections below are identical.

The original PR's stills below (in pr-media/27923/) still hold for the rest of the surface; the carry changes no other paint.

#27923 closes once this lands.


Summary (from #27923)

Part of HQ#1920, HQ#5123 (the telemetry lane) and HQ#5226.

Leo, 2026-10-30: "that visibility into [fleet host] guys was (and is?) pretty bad i feel like we can make more improvements to acpmux/cmux-next"

And, on keeping it lean: "and acpmux ls should probably not be overly... verbose? i guess" / "to keep everything efficient... tho idk". The app follows the same rule: the sidebar row stays as it is, and Usage is folded by default to one value (this chat's 24h cost). The full numbers and the All sessions list show only when someone opens it, and the list is sorted only while it is open.

A usage audit found four long-running deputies with no autocompact cap. Each carried 290-330k of context per call and fanned out 67-90 subagents a day. Nothing in acpmux or cmux NEXT showed it. The companion acpmux change puts usageStats on every session summary, ls --json row and peer row: context per call, the autocompact cap, 24h tokens and cost, compactions and subagents. This PR shows those numbers in the app.

  • Session Info: Usage. A new row in the top block, after Context. Folded, it shows this chat's 24h cost (or its 24h tokens when no cost is known), like Context's one-line value. Open, it shows:

    • this chat's numbers: cost and tokens in the last 24h, context per call against its window, the autocompact cap ("No cap" when there is none), compactions, subagents (live, last hour, last 24h) and the launcher;
    • All sessions: every session acpmux lists on this Mac and its peers, sorted by 24h cost or Context with a two-option toggle (24h cost first). Each row shows its harness mark, title, host and the sorted value. Five rows show, then View all. A row opens that chat (chat.offer.open, as a sender chip does); this chat's own row is plain text.
      This is the fleet sessions view. It sits where Session Info already lives, not on a new surface, and the person can drag it like any other section.
  • Sidebar hover card. A workspace whose agent chat reports usage gets one row after the agent row: "312K context · $1,421 in 24h", with the context-window ring. A chat with no usage gets no row. AgentChatDetail.of(summary:) reads usageStats from the watch feed it already parses, so the card needs no new request.

  • Design rules followed: the sort toggle fills only its own button, never the row. There is no dot and no animation. Both strings catalogs carry all 21 locales. plans/cmux-next/sidebar-owner.md logs the hover-card look change (Sidebar-Look: trailer).

  • Unknown is unknown. A number acpmux has not read (null, or a missing usageStats) shows as an em dash, never 0 or $0.00. Its 24h counts stay null until acpmux's reads cover the whole day. Only autocompact: "none" reads as No cap; unknown reads as an em dash.

  • Every number has a next step, using existing actions:

    • context per call → Compact: the agent's own /compact, through the path a context-full failure's Compact already uses, with the same reach (never a View Only chat, a read-only pane or the next chat);
    • subagents → Show: opens the Subagents section;
    • no cap → Restart with cap: shown disabled, with a tooltip, because acpmux cannot restart a session with an autocompact cap yet. The step is named here rather than built as a new subsystem;
    • a session in All sessions → clicking its row opens that chat;
    • the hover card's usage row → the card already opens its workspace on click, and from there Session Info's Usage has the actions above. The hover card itself gets no buttons.

Field names follow ao-vis-data's final contract: usageStats.{launcher, autocompact, autocompactCap, contextTokens, contextWindow, compactions24h, subagentsLive, subagents1h, subagents24h, tokens24h, costUsd24h, updatedAt}. Until acpmux sends the field, nothing changes: Usage and the hover-card row do not show.

Performance

  • No new timer, observer or animation loop. The numbers refresh on the session list acpmux already sends (watch and session_changed), and acpmux recomputes them on each list read and at turn end.
  • No per-token O(rows) work. Building the snapshot used to map this.sessions into a new array on every emit. It now reuses one array until acpmux's list changes (listedEntries), so a token-rate snapshot gives the pane's session stores (hover, sub-session, Usage) the same array. Usage scans for usageStats only when that array changes, and sorts only while open, when the list or the sort changes.
metric before after delta harness or run link
snapshot sessions array allocations per streamed token (unchanged session list) 1 0 -1 direct.ts listedEntries by construction; no bench run
Usage list sort, 100 sessions (p50 / p95), on a list or sort change only n/a (no list) 0.011 / 0.021 ms new bun usage-bench.ts, 200 runs, Linux x86_64 32 vCPU VM
Usage list sort, 500 sessions (p50 / p95) none on base (no Usage list) 0.032 / 0.041 ms new same
Usage list sort, 2,000 sessions (p50 / p95 / max) none on base (no Usage list) 0.184 / 0.298 / 0.949 ms new same
work per streamed token for Usage and the hover card row none on base (no Usage list) 0 (no timer; the sessions array is the same object) none by construction: Usage recomputes on [sessions, sort], the hover card on the watch feed's session_changed

The sidebar row itself is unchanged: no number or badge on the row (ao-vis-warn's warning badge is that PR's). The numbers live only in the hover card, Session Info's Usage and its All sessions list. The hover card row is one O(1) usageFact per card build, on the same session_changed the card already reads; I did not bench it separately.

Testing

Review fixes: a Sol review at ef9b192 returned BLOCK because the snapshot decoded usageStats into a new object on every emit, so an open Usage section redrew its rows on each token. a72f7a8 keeps the decoded object while acpmux's numbers are unchanged (sameUsageStats) and memoizes UsageSection. Its test failed without the fix and passes with it.

Red first (e4d77b1): cd webviews && bun test test/session-usage.test.tsx failed on base with "Cannot find module '../src/agent-session/acpmux/usageStats'" (0 pass, 1 fail). Green at the head: 8 pass (unknowns render as an em dash; Compact sends; Restart with cap is disabled).

  • bun test test/session-usage.test.tsx test/chat-inspector.test.tsx test/subagent-limit-stepper.test.tsx test/session-hover-card.test.tsx src/agent-session/acpmux/inspector/ test/chat-inspector-*.test.ts*: all pass.
  • Direct-client tests run together (30 files): 233 pass, 11 fail. The same 11 fail the same way on the base e3355039fc7 in one combined run, and each file passes on its own. That is cross-file interference already on base.
  • bun run typecheck, scripts/web-lint.sh and scripts/cmux-next/prepush-gate.py pass.
  • Swift: WorkspaceHoverCardUsageTests (decoding, row order and text, ring, no row without usage) is new and runs on the fleet in the cmux-next tests job; this lane VM runs no Swift.

Observed / Not run

  • Observed: the webview tests, type check, web lint and the pre-push gate above.
  • Not run here: the Swift suites and a DEV build (no fleet client on this host). They run in CI.

Captures

Webview (gallery agent-pane.chat-inspector, 420 px, Chromium). Before is base e3355039 variant full; after is 6bc2ccc variant usage (the base merge after it changed no UI file).

before after
dark before dark after dark
light not captured: the job died on the shared API quota (no rerun, by the quota rule); the dark before shows the same layout after light

Before: the top block is Environment, Context, Connectors. After: Usage sits after Context, folded to "$61.20 · 24h"; open, it shows this chat's numbers, Show beside Subagents, and All sessions sorted by 24h cost. The gallery passes no Compact handler, so Compact does not show here; session-usage.test.tsx covers it. More gallery states: usage-context, usage-all, usage-no-cap.

Visual review: the independent reviewer (codex gpt-6.1-sol high, fast) viewed the after images (dark and light) and gave VISUAL VERDICT: SHIP. It found no control repeated on every row, nothing bloated, cut off, colliding or wrapping. The icon and text columns line up with Context and Connectors, and the sort highlight covers only the "24h cost" button. The light still has good contrast. The changes after 6bc2ccc paint nothing different in these stills: the disabled pressed option keeps its fill, Show's highlight needs a click, and dropping cursor: default paints nothing.

No capture: the native hover-card row is one added fact row, using the card's existing row and ring (HoverCardAccessory.ring), and the sidebar row itself is unchanged. There is no hover-card still: cmux-ci on a lane VM only runs test (through the GitHub broker), not capture, so the hover-card capture has to come from a fleet host. The native gallery's "Agent running" card shows the row (WorkspaceHoverCardGallery.swift), and WorkspaceHoverCardUsageTests checks its text, order and ring.

Changelog

  • Added: Session Info's Usage shows a chat's 24h cost, context per call, autocompact cap and subagents, plus every session on this Mac and its peers sorted by 24h cost or context. The sidebar's workspace hover card shows the agent chat's context and 24h cost.

Risk

risk:normal. A UI addition that reads an optional field. Without usageStats from acpmux, every surface is unchanged. Localization: 16 pane strings and 2 sidebar strings, all 21 locales.

#27923 closes once this lands.

Agent session: agent session

🤖 Generated with Claude Code

teamleaderleo and others added 17 commits October 30, 2026 03:46
…geStats (red)

Leo, 2026-10-30: "that visibility into huge-blue guys was (and is?) pretty bad
i feel like we can make more improvements to acpmux/cmux-next".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-e281136dd51f7f0cd599
…sion spends

Leo, 2026-10-30: "that visibility into huge-blue guys was (and is?) pretty bad
i feel like we can make more improvements to acpmux/cmux-next".

acpmux now reports usageStats on every session summary, ls row and peer row
(context per call, autocompact cap, 24h tokens and cost, subagents). Session
Info gets a Usage row after Context: folded, this chat's 24h cost; open, its
numbers, then every session acpmux lists on this Mac and its peers, sorted by
24h cost or by context, five then View all, each row opening its chat. The
sidebar's workspace hover card gets one row after the agent: context per call
and the day's cost, with the context ring.

The snapshot's sessions array is now reused until acpmux's list changes, so a
snapshot per token gives the session stores nothing new; Usage sorts only on a
list or sort change, and adds no timer.

Green: bun test test/session-usage.test.tsx (6 pass; red before: module missing).

Agent-Session: https://cmux.com/deeplink/agent?id=aid-e281136dd51f7f0cd599

Sidebar-Look: Leo 2026-10-30, "that visibility into huge-blue guys was (and is?) pretty bad i feel like we can make more improvements to acpmux/cmux-next"
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-e281136dd51f7f0cd599
Review (Sol) found that the snapshot decoded usageStats into a new object on
every emit, so an open Usage section redrew its rows on each token. direct.ts
now keeps the decoded object while acpmux's numbers are the same
(sameUsageStats), and UsageSection is memoized on those stable props.

Red: bun test test/session-usage.test.tsx failed without the fix (sameUsageStats
missing); green: 7 pass.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-e281136dd51f7f0cd599
…xt step

Follows ao-vis-data's final usageStats contract: autocompact is capped, none or
unknown, and every count can be null until acpmux's reads cover the day. Null
renders as an em dash, never 0 or $0.00, and only autocompact none reads No cap.

Each of this chat's numbers offers its next step through existing actions:
Compact beside the context (the agent's /compact, with the reach a context-full
failure's Compact has) and Show beside the subagents (opens that section). No
cap offers Restart with cap, disabled with a tooltip: acpmux has no restart
with a cap yet, so the step is named and not invented. A list row opens its
chat.

Part of HQ#1920, HQ#5123 (telemetry lane), HQ#5226.

Green: bun test test/session-usage.test.tsx (8 pass).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-e281136dd51f7f0cd599
# Conflicts:
#	plans/cmux-next/sidebar-owner.md
inspector-section-order.test.tsx mounts the gallery's full variant, which now
lists Usage in the top block: its default order, the drag across the block and
a saved order missing Usage and Connectors now place Usage after Context.

Green: bun test test/inspector-section-order.test.tsx (8 pass).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-e281136dd51f7f0cd599
# Conflicts:
#	webviews/src/agent-session/acpmux/Localizable.xcstrings
Swift's currency format rounds half to even by default, so $1,420.50 read
$1,420 in the hover card and $1,421 in Session Info's Usage (Intl). The row
now rounds half away from zero, as HomeEngineStrings does; the test pins it.

Sidebar-Look: no visual change
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-e281136dd51f7f0cd599
inspector-dead-clicks and clickable-cursor were red at 5199abf on the Usage
section: Show did nothing in the DOM when Subagents was already open, the
pressed sort option took a click that did nothing, and both button classes set
cursor: default. Show now opens Subagents, brings it into view and lights it
through the same path a reveal uses (light(), shared with the reveal effect);
the pressed sort option is disabled; the cursor lines are gone.

Red at 5199abf, green here (bun test, each file alone):
test/inspector-dead-clicks.test.tsx, test/clickable-cursor.test.ts;
test/session-usage.test.tsx and test/inspector-section-order.test.tsx pass.

Sidebar-Look: no visual change

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-e281136dd51f7f0cd599
# Conflicts:
#	webviews/src/agent-session/acpmux/Localizable.xcstrings
# Conflicts:
#	plans/cmux-next/sidebar-owner.md
# Conflicts:
#	webviews/src/agent-session/acpmux/Localizable.xcstrings
Conflicts: plans/cmux-next/sidebar-owner.md (kept base's runaway badge
section and this PR's hover card usage section, host name redacted as
base does) and webviews/src/agent-session/acpmux/Localizable.xcstrings
(kept both sides' keys).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-63b9964940c20354b639
…ost is unknown

Synthetic state usage-unknown-cost: a peer session with tokens and
context but no readable 24h cost, so the cost-sorted list shows what
that row reads.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-63b9964940c20354b639
…ted list

Sorted by 24h cost, a session whose cost acpmux could not read showed
its 24h token count under the cost heading. It now shows the unknown
mark (—), as every other unknown number in Usage does. The Context sort
is unchanged. Test: session-usage.test.tsx, red before, green after.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-63b9964940c20354b639
@github-actions github-actions Bot added the risk:normal Risk tier (docs/ci/risk-tiers.md) label Oct 31, 2026
@github-actions

github-actions Bot commented Oct 31, 2026 •

Copy link
Copy Markdown
Contributor

cmux status (one comment, edited in place; each line is updated by the job that owns it)

Review a8f49cb: HQ feed card · gallery before/after runs · checks

App of this head (app.zip, kept 1 day)

gh workflow run reload-build.yml -R manaflow-ai/cmux --ref feat-cmux-next -f tag=pr28773 -f ref=lane/carry-27923 -f platform=macos -f backend_mode=local

Gallery before/after for a8f49cb: 16 states rendered, 16 images differ, 2868 more touched states not rendered. Images are in the run's artifact, not on this public PR; the independent review of this head reads them on its own, so the PR needs no hand-made screenshots of these states.

gh run download 41324769038 -R manaflow-ai/cmux -n gallery-advisory-28773-a8f49cb914d5d3c954e3eb145ae24feded9cdd24 (then read gallery-review.json)

States
State Theme Status Diff Picked by
agent-pane.chat-inspector/usage Apple System Colors Light new 0% own SessionInfo.gallery.tsx
agent-pane.chat-inspector/usage Apple System Colors new 0% own SessionInfo.gallery.tsx
agent-pane.chat-inspector/usage-no-cap Apple System Colors Light new 0% own SessionInfo.gallery.tsx
agent-pane.chat-inspector/usage-no-cap Apple System Colors new 0% own SessionInfo.gallery.tsx
agent-pane.chat-inspector/usage-context Apple System Colors Light new 0% own SessionInfo.gallery.tsx
agent-pane.chat-inspector/usage-context Apple System Colors new 0% own SessionInfo.gallery.tsx
agent-pane.chat-inspector/usage-unknown-cost Apple System Colors Light new 0% own SessionInfo.gallery.tsx
agent-pane.chat-inspector/usage-unknown-cost Apple System Colors new 0% own SessionInfo.gallery.tsx
agent-pane.chat-inspector/usage-all Apple System Colors Light new 0% own SessionInfo.gallery.tsx
agent-pane.chat-inspector/usage-all Apple System Colors new 0% own SessionInfo.gallery.tsx
agent-pane.chat-inspector/full Apple System Colors Light changed 6.56% own SessionInfo.gallery.tsx
agent-pane.chat-inspector/full Apple System Colors changed 6.49% own SessionInfo.gallery.tsx
agent-pane.chat-inspector/workflow-running Apple System Colors Light changed 4.68% own SessionInfo.gallery.tsx
agent-pane.chat-inspector/workflow-running Apple System Colors changed 4.61% own SessionInfo.gallery.tsx
agent-pane.chat-inspector/workflow-finished Apple System Colors Light changed 4.47% own SessionInfo.gallery.tsx
agent-pane.chat-inspector/workflow-finished Apple System Colors changed 4.41% own SessionInfo.gallery.tsx
agent-pane.chat-inspector-surfaces/transcript Apple System Colors Light unchanged 0% own SessionInfo.tsx
agent-pane.chat-inspector-surfaces/transcript Apple System Colors unchanged 0% own SessionInfo.tsx
agent-pane.chat-inspector-surfaces/diff-in-place Apple System Colors Light unchanged 0% own SessionInfo.tsx
agent-pane.chat-inspector-surfaces/diff-in-place Apple System Colors unchanged 0% own SessionInfo.tsx
agent-pane.chat-inspector-surfaces/document-beside Apple System Colors Light unchanged 0% own SessionInfo.tsx
agent-pane.chat-inspector-surfaces/document-beside Apple System Colors unchanged 0% own SessionInfo.tsx
agent-pane.chat-inspector-surfaces/diff-closed Apple System Colors Light unchanged 0% own SessionInfo.tsx
agent-pane.chat-inspector-surfaces/diff-closed Apple System Colors unchanged 0% own SessionInfo.tsx
agent-pane.chat-width/standard Apple System Colors Light unchanged 0% own App.tsx
agent-pane.chat-width/standard Apple System Colors unchanged 0% own App.tsx
agent-pane.chat-width/wide Apple System Colors Light unchanged 0% own App.tsx
agent-pane.chat-width/wide Apple System Colors unchanged 0% own App.tsx
agent-pane.chat-width/wide-blocks Apple System Colors Light unchanged 0% own App.tsx
agent-pane.chat-width/wide-blocks Apple System Colors unchanged 0% own App.tsx
agent-pane.composer-edge/short-edge Apple System Colors Light unchanged 0% own App.tsx
agent-pane.composer-edge/short-edge Apple System Colors unchanged 0% own App.tsx

Pick other states with a Gallery: entry.id, entry.id/variant line in the PR body.

Unclear: web / react-apps-base-check (4) failed: no known signature; failed step: Run base reds and compare. This PR merged before its CI finished: fix forward on main.

CI failed on a8f49cb914 (run 41324768969 attempt 1): 2 unknown.
macOS jobs did not run: compile and app tests were skipped: Fast static checks skipped.

Job Verdict Why
web / react-apps-base-check (4) unknown no known signature; failed step: Run base reds and compare
web / react-apps-check unknown no known signature; failed step: Check webview test shards

Not re-run automatically: web / react-apps-base-check (4), web / react-apps-check are not machine failures.

Written by scripts/ci/classify_failures.py (ci-failure-attribution.yml); signatures are its SIGNATURES table. A machine verdict is the runner's fault, not this PR's; yours means the failing file is one this PR changes, also red on main that main's latest full suite fails the same way, seen on other PRs that it failed on another pull request's run lately.

red: web / Web status; a job it gathers is red on this change (its line here); do not merge until green; see lane-rules
red: web / react-apps-base-check (4); NEW webview test reds: test/session-info-section-order-browser.test.tsx > Session Info's six-dot handles > the default order draws the three blocks: 6px inside a block, none where it runs, 8px, a hairline, 8px between; do not merge until green; see lane-rules
red: web / react-apps-check; a job it gathers is red on this change (its line here); do not merge until green; see lane-rules

Default changes

This pull request changes 6 defaults at a8f49cb914d5 (what everyone gets without configuring anything):

  • tunable-source.CmuxNextBridge/AgentTurnStates.swift:of: (none) -> "7979e505dd3f3a1b3e636b9f0ff7dce38ee14d023aa2d3e745881d4280a8617c"
  • tunable-source.CmuxNextBridge/AgentTurnStates.swift:of#: (none) -> "bae8c5f95a57bf206e5512eef997e34b9f4aa1a5fac5f366a890cdcab085013f"
  • tunable-source.CmuxNextSidebar/Logic/WorkspaceHoverCardContent.swift:facts: "d6385a5eabe45c951775809d959b8e8646aea09054c4d1ebaedb60be67b89957" -> "dcc5310d553b35d194fdbeb596f1374f42ee1a4bae6ca2faaadd6b282342857a"
  • tunable-source.CmuxNextSidebar/Logic/WorkspaceHoverCardContent.swift:usageFact: (none) -> "047135be601f3081743deae61de1bedf38350a8c753c019ea4b19242028c53fc"
  • tunable-source.CmuxNextSidebar/Strings.swift:usageContext: (none) -> "c506e575713a3f5b33c18fc98529ab89711ca882f6586301dab0fa6c246992c4"
  • tunable-source.CmuxNextSidebar/Strings.swift:usageCost: (none) -> "668db3b1916e5acb7ba49659ab02ad3ad79bcac99b9a3a320a65d6695cd32dfc"

Needs a review. It passes with an independent review receipt on this commit: a comment or review whose first line is Review (<reviewer>): SHIP at a8f49cb914d5d3c954e3eb145ae24feded9cdd24, from a member, not a self-review (the newest verdict naming the head decides; a later BLOCK beats an earlier SHIP), or with an approving review on this commit from @lawrencecchen or @austinywang (the call owners in .github/defaults-call-owners.json, never the author). A receipt posted as a review re-runs this check; after a plain comment, re-run it. The needs a call label alone does not pass, and a new push needs a new receipt.

Merge receipt: merged as d85d0eec8b58 with these defaults changed, WITHOUT a SHIP receipt or an approving call owner review on its head.

Regenerate the snapshot with python3 scripts/ci/defaults_guard.py snapshot.

Shipped in:

teamleaderleo and others added 2 commits October 31, 2026 03:04
direct.ts: base memoized the snapshot's session entries as sessionEntryList
(sessionEntriesOf), the same change this PR made as listedEntries; base's
names are kept, with the PR's summaryUsage beside them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-f71664ebfb6cf5206479
A plain session list carries acpmux's last usage pass, or none at all
while nothing asked since acpmux started, so Usage and the sessions by
usage list stayed empty or stale. Red until the pane asks with
`usage: true` (SessionPager.usage, withUsage, onShowUsage).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-f71664ebfb6cf5206479
@blacksmith-sh

blacksmith-sh Bot commented Oct 31, 2026 •

Copy link
Copy Markdown

Found 4 test failures on Blacksmith runners:

Failures

Test View Logs
test/new-tab-typing.test.ts/new-tab-typing.test View Logs
test/new-tab-typing.test.ts/new-tab-typing.test View Logs
test/whats-new-text-selection.test.ts/whats-new-text-selection.test View Logs
test/whats-new-text-selection.test.ts/whats-new-text-selection.test View Logs

Fix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need.

teamleaderleo and others added 2 commits October 31, 2026 03:22
Base moved on 57 commits, among them the regenerated Chief memory
inspector page (the fast checks' web bundle step was red at the last
merge's base). No conflicts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A plain session list carries acpmux's last usage pass (hub/usage_view.rs),
or none while nothing asked since acpmux started, and the pane never
asked: Usage and the sessions by usage list stayed empty or stale.

Each time Session Info shows, the pane reads _acpmux/sessions with
usage: true a page at a time (SessionPager.usage) and lays the numbers
over the list and the chat's own summary (withUsage), keeping a turn
end's newer numbers and the same array when nothing moved. One read at a
time; no timer. After it, each chat's turn end brings its own usage over
session_changed, as before.

The cmux host's pane policy allows usage on _acpmux/sessions
(policy.json, AcpmuxPaneMethods+Params.swift, the params case): the host
refuses a frame with a param it lacks.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-f71664ebfb6cf5206479
# Conflicts:
#	Packages/macOS/CmuxNext/Sources/CmuxNextAgentLanes/AgentTurnStateFeed.swift
#	Packages/macOS/CmuxNext/Sources/CmuxNextBridge/AgentTurnStates.swift
#	Packages/macOS/CmuxNext/Sources/CmuxNextSidebar/Model/SidebarWorkspaceDetail.swift
#	plans/cmux-next/sidebar-owner.md
#	webviews/src/agent-session/acpmux/App.tsx
#	webviews/src/agent-session/acpmux/session-info/SessionInfo.gallery.tsx
#	webviews/src/agent-session/acpmux/session-info/UsageSection.css
#	webviews/src/agent-session/acpmux/session-info/UsageSection.tsx
#	webviews/src/agent-session/acpmux/session-info/sectionOrder.ts
#	webviews/src/agent-session/acpmux/session-info/sessionInfoState.ts

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

9 issues found across 29 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="Packages/macOS/CmuxNext/Tests/CmuxNextAppTests/WorkspaceHoverCardUsageTests.swift">

<violation number="1" location="Packages/macOS/CmuxNext/Tests/CmuxNextAppTests/WorkspaceHoverCardUsageTests.swift:41">
P2: These assertions are tied to the current machine’s locale even though `usageFact` uses localized strings and locale-sensitive number/currency formatting. Running the test suite under a supported non-English locale will fail; make the formatting locale-injectable for the test or assert against explicitly localized/locale-independent expectations.</violation>
</file>

<file name="Packages/macOS/CmuxNext/Sources/CmuxNextBridge/AgentTurnStates.swift">

<violation number="1" location="Packages/macOS/CmuxNext/Sources/CmuxNextBridge/AgentTurnStates.swift:104">
P2: The `tokens` parser truncates fractional values, so malformed or non-integral `contextTokens`/`contextWindow` values are displayed as smaller valid counts instead of an em dash. Reject non-integral numbers with an exact `Int` conversion.</violation>

<violation number="2" location="Packages/macOS/CmuxNext/Sources/CmuxNextBridge/AgentTurnStates.swift:418">
P2: `applyUsage` can overwrite newer `session_changed` usage with an older in-flight read, leaving the hover card stale until another refresh. Preserve `usageStats.updatedAt` and apply a read only when it is at least as new as the stored snapshot.</violation>
</file>

<file name="plans/cmux-next/sidebar-owner.md">

<violation number="1" location="plans/cmux-next/sidebar-owner.md:284">
P3: The owner record overstates the hover-card contract: cost-only or context-only `usageStats` still produce a usage row without a ring. Document the ring as conditional on both context and context-window values being known.</violation>
</file>

<file name="webviews/src/agent-session/acpmux/usageStats.ts">

<violation number="1" location="webviews/src/agent-session/acpmux/usageStats.ts:70">
P2: `sameUsageStats` treats the read timestamp as a usage change. Since `SessionPager.sameBut` explicitly ignores `updatedAt`, unchanged usage rereads still create a new summary object and redraw the Usage section; exclude `updatedAt` from this equality check.</violation>
</file>

<file name="Packages/macOS/CmuxNext/Tests/CmuxNextAppTests/HoverCardUsageReadTests.swift">

<violation number="1" location="Packages/macOS/CmuxNext/Tests/CmuxNextAppTests/HoverCardUsageReadTests.swift:42">
P2: This test only inspects the static request payload; it never exercises `AgentTurnStateFeed.connect`, so a regression that sends additional usage reads after connection would still pass. Start the feed against a recording stand-in (or inject a connection) and assert the actual outbound requests.</violation>

<violation number="2" location="Packages/macOS/CmuxNext/Tests/CmuxNextAppTests/HoverCardUsageReadTests.swift:81">
P2: This pagination test bypasses the feed’s pagination path, so `handle` could stop sending pages or send the wrong cursor while the test still passes. Feed the response through a recording connection and assert the emitted follow-up request.</violation>
</file>

<file name="webviews/src/agent-session/acpmux/sessionPages.ts">

<violation number="1" location="webviews/src/agent-session/acpmux/sessionPages.ts:178">
P2: `usage()` drops `nextCursor` whenever a page has no valid session rows, so an empty intermediate page prevents usage reads for all following sessions. Preserve the cursor on empty pages, as `wholePageOf()` does.</violation>

<violation number="2" location="webviews/src/agent-session/acpmux/sessionPages.ts:182">
P2: Usage is keyed only by bare `sessionId`, so a local chat and a peer chat with the same ID overwrite each other and both rows can display the wrong usage. Key the read and lookup by the qualified session identity, including the peer.</violation>
</file>

Reply with feedback, questions, or to request a fix.

View guided diff | Turn on auto-fix | Re-trigger cubic

let kinds = card.facts.map(\.kind)
let usage = try #require(card.facts.first { $0.kind == "usage" })
#expect(kinds.firstIndex(of: "usage") == kinds.firstIndex(of: "agent").map { $0 + 1 })
#expect(usage.text == "312K context · $1,421 in 24h")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: These assertions are tied to the current machine’s locale even though usageFact uses localized strings and locale-sensitive number/currency formatting. Running the test suite under a supported non-English locale will fail; make the formatting locale-injectable for the test or assert against explicitly localized/locale-independent expectations.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At Packages/macOS/CmuxNext/Tests/CmuxNextAppTests/WorkspaceHoverCardUsageTests.swift, line 41:

<comment>These assertions are tied to the current machine’s locale even though `usageFact` uses localized strings and locale-sensitive number/currency formatting. Running the test suite under a supported non-English locale will fail; make the formatting locale-injectable for the test or assert against explicitly localized/locale-independent expectations.</comment>

<file context>
@@ -0,0 +1,56 @@
+        let kinds = card.facts.map(\.kind)
+        let usage = try #require(card.facts.first { $0.kind == "usage" })
+        #expect(kinds.firstIndex(of: "usage") == kinds.firstIndex(of: "agent").map { $0 + 1 })
+        #expect(usage.text == "312K context · $1,421 in 24h")
+        #expect(usage.icon == .contextUsage)
+        #expect(usage.accessory == .ring(0.312, tone: .secondary))
</file context>

let amount = number.doubleValue
return amount.isFinite && amount >= 0 ? amount : nil
}
let tokens = { (value: Any?) -> Int? in amount(value).flatMap { $0 < Double(Int.max) ? Int($0) : nil } }

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: The tokens parser truncates fractional values, so malformed or non-integral contextTokens/contextWindow values are displayed as smaller valid counts instead of an em dash. Reject non-integral numbers with an exact Int conversion.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At Packages/macOS/CmuxNext/Sources/CmuxNextBridge/AgentTurnStates.swift, line 104:

<comment>The `tokens` parser truncates fractional values, so malformed or non-integral `contextTokens`/`contextWindow` values are displayed as smaller valid counts instead of an em dash. Reject non-integral numbers with an exact `Int` conversion.</comment>

<file context>
@@ -84,17 +94,40 @@ public nonisolated struct AgentChatDetail: Hashable, Sendable {
+            let amount = number.doubleValue
+            return amount.isFinite && amount >= 0 ? amount : nil
+        }
+        let tokens = { (value: Any?) -> Int? in amount(value).flatMap { $0 < Double(Int.max) ? Int($0) : nil } }
         let count = { (value: Any?) -> Int in max(0, (value as? NSNumber)?.intValue ?? 0) }
         let detail = AgentChatDetail(model: text(summary["model"]), turnStartedAtMs: (turn?["startedAt"] as? NSNumber)?.uint64Value,
</file context>
Suggested change
let tokens = { (value: Any?) -> Int? in amount(value).flatMap { $0 < Double(Int.max) ? Int($0) : nil } }
let tokens = { (value: Any?) -> Int? in amount(value).flatMap { Int(exactly: $0) } }

const next = usageStatsOf(value);
if (!next || !previous) return next;
const keys = new Set([...Object.keys(next), ...Object.keys(previous)]) as Set<keyof SessionUsageStats>;
for (const key of keys) if (next[key] !== previous[key]) return next;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: sameUsageStats treats the read timestamp as a usage change. Since SessionPager.sameBut explicitly ignores updatedAt, unchanged usage rereads still create a new summary object and redraw the Usage section; exclude updatedAt from this equality check.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At webviews/src/agent-session/acpmux/usageStats.ts, line 70:

<comment>`sameUsageStats` treats the read timestamp as a usage change. Since `SessionPager.sameBut` explicitly ignores `updatedAt`, unchanged usage rereads still create a new summary object and redraw the Usage section; exclude `updatedAt` from this equality check.</comment>

<file context>
@@ -0,0 +1,112 @@
+  const next = usageStatsOf(value);
+  if (!next || !previous) return next;
+  const keys = new Set([...Object.keys(next), ...Object.keys(previous)]) as Set<keyof SessionUsageStats>;
+  for (const key of keys) if (next[key] !== previous[key]) return next;
+  return previous;
+}
</file context>

#expect(turns.detail(for: Self.chat("s1"))?.model == "m")
}

@Test func aUsagePageNamesItsNextCursor() throws {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: This pagination test bypasses the feed’s pagination path, so handle could stop sending pages or send the wrong cursor while the test still passes. Feed the response through a recording connection and assert the emitted follow-up request.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At Packages/macOS/CmuxNext/Tests/CmuxNextAppTests/HoverCardUsageReadTests.swift, line 81:

<comment>This pagination test bypasses the feed’s pagination path, so `handle` could stop sending pages or send the wrong cursor while the test still passes. Feed the response through a recording connection and assert the emitted follow-up request.</comment>

<file context>
@@ -0,0 +1,95 @@
+        #expect(turns.detail(for: Self.chat("s1"))?.model == "m")
+    }
+
+    @Test func aUsagePageNamesItsNextCursor() throws {
+        guard case .usage(let numbers, let next) = try #require(Self.usage([["sessionId": "s1", "usageStats": Self.stats],
+                                                                             ["sessionId": "s2", "status": "idle"]],
</file context>

}

@Test func theWatchAsksForEveryChatsUsageOnceWhenItConnects() throws {
let reads = Self.requests().filter { $0["method"] as? String == "_acpmux/sessions" }

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: This test only inspects the static request payload; it never exercises AgentTurnStateFeed.connect, so a regression that sends additional usage reads after connection would still pass. Start the feed against a recording stand-in (or inject a connection) and assert the actual outbound requests.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At Packages/macOS/CmuxNext/Tests/CmuxNextAppTests/HoverCardUsageReadTests.swift, line 42:

<comment>This test only inspects the static request payload; it never exercises `AgentTurnStateFeed.connect`, so a regression that sends additional usage reads after connection would still pass. Start the feed against a recording stand-in (or inject a connection) and assert the actual outbound requests.</comment>

<file context>
@@ -0,0 +1,95 @@
+    }
+
+    @Test func theWatchAsksForEveryChatsUsageOnceWhenItConnects() throws {
+        let reads = Self.requests().filter { $0["method"] as? String == "_acpmux/sessions" }
+        #expect(reads.count == 1, "one usage read per connection, never one per change")
+        let params = try #require(reads.first?["params"] as? [String: Any])
</file context>

Comment on lines +178 to +180
const page = pageOf<S>(
await this.request("_acpmux/sessions", { usage: true, limit: SESSION_PAGE, ...(cursor ? { cursor } : {}) }),
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: usage() drops nextCursor whenever a page has no valid session rows, so an empty intermediate page prevents usage reads for all following sessions. Preserve the cursor on empty pages, as wholePageOf() does.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At webviews/src/agent-session/acpmux/sessionPages.ts, line 178:

<comment>`usage()` drops `nextCursor` whenever a page has no valid session rows, so an empty intermediate page prevents usage reads for all following sessions. Preserve the cursor on empty pages, as `wholePageOf()` does.</comment>

<file context>
@@ -163,8 +163,62 @@ export class SessionPager<S extends Summary> {
+    const seen = new Set<string>();
+    let cursor: string | undefined;
+    do {
+      const page = pageOf<S>(
+        await this.request("_acpmux/sessions", { usage: true, limit: SESSION_PAGE, ...(cursor ? { cursor } : {}) }),
+      );
</file context>
Suggested change
const page = pageOf<S>(
await this.request("_acpmux/sessions", { usage: true, limit: SESSION_PAGE, ...(cursor ? { cursor } : {}) }),
);
const page = wholePageOf<S>(
await this.request("_acpmux/sessions", { usage: true, limit: SESSION_PAGE, ...(cursor ? { cursor } : {}) }),
cursor,
);

public func applyUsage(_ usage: [String: AgentChatDetail], source: String = AgentTurnStateStore.localSource) {
for (id, numbers) in usage {
guard let record = sessions[id], record.source == source else { continue }
let detail = (record.detail ?? AgentChatDetail()).withUsage(of: numbers)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: applyUsage can overwrite newer session_changed usage with an older in-flight read, leaving the hover card stale until another refresh. Preserve usageStats.updatedAt and apply a read only when it is at least as new as the stored snapshot.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At Packages/macOS/CmuxNext/Sources/CmuxNextBridge/AgentTurnStates.swift, line 418:

<comment>`applyUsage` can overwrite newer `session_changed` usage with an older in-flight read, leaving the hover card stale until another refresh. Preserve `usageStats.updatedAt` and apply a read only when it is at least as new as the stored snapshot.</comment>

<file context>
@@ -376,6 +409,17 @@ public final class AgentTurnStateStore {
+    public func applyUsage(_ usage: [String: AgentChatDetail], source: String = AgentTurnStateStore.localSource) {
+        for (id, numbers) in usage {
+            guard let record = sessions[id], record.source == source else { continue }
+            let detail = (record.detail ?? AgentChatDetail()).withUsage(of: numbers)
+            if record.detail != detail { record.detail = detail }
+        }
</file context>

await this.request("_acpmux/sessions", { usage: true, limit: SESSION_PAGE, ...(cursor ? { cursor } : {}) }),
);
for (const session of page.sessions)
if (session.usageStats !== undefined) found.set(session.sessionId, session.usageStats);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Usage is keyed only by bare sessionId, so a local chat and a peer chat with the same ID overwrite each other and both rows can display the wrong usage. Key the read and lookup by the qualified session identity, including the peer.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At webviews/src/agent-session/acpmux/sessionPages.ts, line 182:

<comment>Usage is keyed only by bare `sessionId`, so a local chat and a peer chat with the same ID overwrite each other and both rows can display the wrong usage. Key the read and lookup by the qualified session identity, including the peer.</comment>

<file context>
@@ -163,8 +163,62 @@ export class SessionPager<S extends Summary> {
+        await this.request("_acpmux/sessions", { usage: true, limit: SESSION_PAGE, ...(cursor ? { cursor } : {}) }),
+      );
+      for (const session of page.sessions)
+        if (session.usageStats !== undefined) found.set(session.sessionId, session.usageStats);
+      cursor = page.next;
+      if (cursor !== undefined && (seen.has(cursor) || seen.size >= MAX_PAGES)) break;
</file context>

| Group rail shape | Chrome's line on that edge: 2 pt, flat ends, the header bar's own color, a 6 pt cut below the header, down to the last member's bottom. Debug `sidebar.groupLineLayout`: `edge` (default, the row above), `joined` (no cut, the bar's bottom-left corner square: one L, band header only), `inside` (the header reaches the corner inset further left, 4 pt compact and 5 pt comfortable, capped at the list's edge: the line under its corner). Members never move. | Leo 2026-10-23 ("WTF HAPPENED TO FOLLOWING CHROME"; asks ask-5b16be6ee9, ask-40133bf821, ask-7e10ebd553), #24513 |
| Row text | As far left as it originally was. A row with no icon starts its title at the row inset; no empty icon slot pushes it right. A top-level group's rows keep the ungrouped rows' left edge. Only nested groups step in. | Lawrence 11405 ("text needs to be as left as possible (how it originally was)"); #23975 |
| Home unread | A blue dot, the same dot as other unread rows. No count badge. | Lawrence 11384 |
| Hover card usage | A workspace whose agent chat reports acpmux `usageStats` gets one row after the agent row: the context each call carries and the last 24 hours' cost ("312K context · $1,421 in 24h"), with the context-window ring. No usage, no row. | Leo 2026-10-30 ("that visibility into [one agent host's] guys was (and is?) pretty bad i feel like we can make more improvements to acpmux/cmux-next") |

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: The owner record overstates the hover-card contract: cost-only or context-only usageStats still produce a usage row without a ring. Document the ring as conditional on both context and context-window values being known.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At plans/cmux-next/sidebar-owner.md, line 284:

<comment>The owner record overstates the hover-card contract: cost-only or context-only `usageStats` still produce a usage row without a ring. Document the ring as conditional on both context and context-window values being known.</comment>

<file context>
@@ -281,6 +281,7 @@ A workspace close removes only the workspace. The daemon close path and remote-c
 | Group rail shape | Chrome's line on that edge: 2 pt, flat ends, the header bar's own color, a 6 pt cut below the header, down to the last member's bottom. Debug `sidebar.groupLineLayout`: `edge` (default, the row above), `joined` (no cut, the bar's bottom-left corner square: one L, band header only), `inside` (the header reaches the corner inset further left, 4 pt compact and 5 pt comfortable, capped at the list's edge: the line under its corner). Members never move. | Leo 2026-10-23 ("WTF HAPPENED TO FOLLOWING CHROME"; asks ask-5b16be6ee9, ask-40133bf821, ask-7e10ebd553), #24513 |
 | Row text | As far left as it originally was. A row with no icon starts its title at the row inset; no empty icon slot pushes it right. A top-level group's rows keep the ungrouped rows' left edge. Only nested groups step in. | Lawrence 11405 ("text needs to be as left as possible (how it originally was)"); #23975 |
 | Home unread | A blue dot, the same dot as other unread rows. No count badge. | Lawrence 11384 |
+| Hover card usage | A workspace whose agent chat reports acpmux `usageStats` gets one row after the agent row: the context each call carries and the last 24 hours' cost ("312K context · $1,421 in 24h"), with the context-window ring. No usage, no row. | Leo 2026-10-30 ("that visibility into [one agent host's] guys was (and is?) pretty bad i feel like we can make more improvements to acpmux/cmux-next") |
 | Trailing indicators | One center x in every row kind and density (section, status, band and chat history headers' chevrons too, a band header in every look, cards included, through `SidebarRegionLayout.itemTrailingTrim`): Home and band items, selected and unselected workspace rows, pinned, nested and top-level group headers (chevron and resting spinner), members, at compact, comfortable and expanded, Minimal Mode on and off. The dot or spinner owns the trailing column; a disclosure, a count, the keycap and the hover close and more sit left of it. On hover the mark gives way: a hovered workspace row draws no dot or spinner while its close, more or pin show (at the default end spot the end-most control takes the column), a hovered loading group header shows its chevron and no spinner, and leaving clears the controls at once as the mark returns. | Lawrence 11914 ("the home blue dot isn't lined up with the other trailing dots"), 11959 ("trailing dots and spinners must line up horizontally across sections"); Leo 2026-10-28 ("If you're hovering over a workspace or whatever, just don't show the spinner. Also ... why the fuck is it not aligned? Oh, because it's the inner tab thing") |
 | Status colors | One color per meaning in every row kind (Home, band items, workspace rows, group headers, nested): blue `systemBlue` unread or new, orange `systemOrange` needs attention (a permission prompt, a question, waiting on you), red `systemRed` an error. Tokens `Palette.statusUnread/statusAttention/statusError`; sidebar indicators opt in with `StatusIndicatorView.usesStatusColors`. A row shows one: red, else orange, else blue, else its spinner. A group header, open or collapsed, rolls up its subtree the same way; a collapsed header with none of those keeps its loading spinner. | Lawrence 11983 ("make sure sidebar has blue, orange/yellow for needs attention, and red for errors"); takes back 10-18's "an error is Ready (blue)" and 10-20's "All we care about is the loading" for headers |
</file context>
Suggested change
| Hover card usage | A workspace whose agent chat reports acpmux `usageStats` gets one row after the agent row: the context each call carries and the last 24 hours' cost ("312K context · $1,421 in 24h"), with the context-window ring. No usage, no row. | Leo 2026-10-30 ("that visibility into [one agent host's] guys was (and is?) pretty bad i feel like we can make more improvements to acpmux/cmux-next") |
| Hover card usage | A workspace whose agent chat reports acpmux `usageStats` gets one row after the agent row: the context each call carries and the last 24 hours' cost ("312K context · $1,421 in 24h"), with a context-window ring when both values are known. No usage, no row. | Leo 2026-10-30 ("that visibility into [one agent host's] guys was (and is?) pretty bad i feel like we can make more improvements to acpmux/cmux-next") |

…on for AgentLaneStore and AgentSessionRef (they left CmuxNextAgentLanes' exports on feat)
@cursor

cursor Bot commented Nov 1, 2026

Copy link
Copy Markdown

Bugbot needs on-demand usage enabled

Bugbot uses usage-based billing for this team and requires on-demand usage to be enabled.

A team admin can enable on-demand usage in the Cursor dashboard.

@lawrencecchen

Copy link
Copy Markdown
Contributor

Conflict wave: I merged feat-cmux-next (5a27309) into this carry.

  • Swift: AgentChatDetail, the turn-state feed and SidebarWorkspaceDetail are unions of this PR's usage fields and feat's subagent counts and sessionName.
  • Web: the Usage section, its strings, CSS and test take feat's Session Info names (carry #29651: Session Info is called Session Info in its code, not Inspector #29921). The test is renamed session-info-usage.test.tsx (12 pass), and the fixture name inspector-polish became session-info-polish for check:session-info-names.
  • f38ca14: HoverCardUsageReadTests imports CmuxNextAgentActivity and CmuxNextDaemon for AgentLaneStore and AgentSessionRef, which left CmuxNextAgentLanes' exports on feat. This is import-only.
    Fleet at f38ca14: compile ad78118006ce43f20d927cd1 PASSED (CmuxNext + app); suites 6dffa5e5ea1316c68f01e370: HoverCardUsageReadTests 4/4 and WorkspaceHoverCardUsageTests 3/3 PASSED. Webviews check reds (fmt, lint, vp check) are identical at the base. The PR had no ready-to-land; it needs an independent review before that label.

@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

Review (independent Codex gpt-6.1-sol, high, via the review router; full diff from merge base 5a27309 to this head): SHIP at f38ca14.

[P2] [follow-up] webviews/src/agent-session/acpmux/sessionPages.ts:181 (also Packages/macOS/CmuxNext/Sources/CmuxNextAgentLanes/AgentTurnStateFeed.swift:188) - Usage results are keyed only by sessionId, although local and peer rows can share that ID; one host’s stats overwrite the other’s and are applied to both rows. Key usage by (peer, sessionId)/qualified session ID through the web and native paths.
[P2] [follow-up] webviews/src/agent-session/acpmux/session-info/SessionInfo.tsx:1201 - Refresh runs only when the panel content mounts. Reopening during the close transition keeps the content mounted, so no new usage read occurs and the panel can show stale values. Trigger the refresh on the visible-state transition.

…27923-u

# Conflicts:
#	webviews/src/agent-session/acpmux/Localizable.xcstrings
@teamleaderleo
teamleaderleo merged commit d85d0ee into feat-cmux-next Nov 1, 2026
165 of 169 checks passed
@teamleaderleo
teamleaderleo deleted the lane/carry-27923 branch November 1, 2026 16:13
cmux-bot Bot pushed a commit that referenced this pull request Nov 1, 2026
…p block (#31160)

* base red: Session Info's six-dot geometry test counts Usage in the top block

#28773 added Usage after Context in the top block (sectionOrder.ts
GROUP_SECTIONS.about). The browser geometry test still measured
Context straight to Connectors, so it read Usage's 28px row as a gap.
It now measures Environment, Context, Usage, Connectors (0px each) and
checks Usage and Connectors carry no hairline.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-2f629b93d5a20c58e6f8

* test: keep the six-dot geometry test's section list within the format width

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-2f629b93d5a20c58e6f8

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
teamleaderleo added a commit that referenced this pull request Nov 3, 2026
…ault (#32850)

Leo's order for the summary panel: "1. Machines showing up so I know if it's
cloud or not 2. context 3. Subagents/background tasks running 4. Sources".
DEFAULT_ORDER is now environment, context, usage, subagents, tasks,
sources, changes, outputs, connectors. Usage keeps its place after Context
(#28773); the sections he did not name follow Sources; none is dropped. A
saved order (any drag) still wins, and the stored format is unchanged, so no
migration. Block membership (the hairlines, the #27291 per-block format) is
unchanged.

The gallery's full chat asserts the order; a new unit test pins the width
stages (gutter at 1800px, push at 900px, push at the 200px floor waiting for
the toggle at 370px, never over the chat in a tall pane; a short pane's
popover is the one overlay).


Agent-Session: https://cmux.com/deeplink/agent?id=aid-7a7568197607c3cdfb81

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

This branch was previously deployed

3 inactive deployments
artifacts — a8f49cb9 Deployed Nov 1, 2026 by teamleaderleo via add the Windows daemon to the tree #96822
sentry-dsyms — a8f49cb9 Deployed Nov 1, 2026 by teamleaderleo via upload cmux-tui dSYMs to Sentry #96822
ci-routing-agent — a8f49cb9 Deployed Nov 1, 2026 by teamleaderleo via cmux-tui tree preflight #96822
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

risk:high Risk tier (docs/ci/risk-tiers.md)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants