feat(desktop): NIP-AM agent-usage UI — bars, range tabs, focused view + cache breakdown - #4001
Open
wpfleger96 wants to merge 2 commits into
Open
feat(desktop): NIP-AM agent-usage UI — bars, range tabs, focused view + cache breakdown#4001wpfleger96 wants to merge 2 commits into
wpfleger96 wants to merge 2 commits into
Conversation
wpfleger96
force-pushed
the
duncan/agent-usage-backend
branch
from
July 31, 2026 20:53
1613f34 to
12f1f29
Compare
wpfleger96
force-pushed
the
duncan/agent-usage-ui
branch
from
July 31, 2026 21:06
b6491fc to
4173c78
Compare
wpfleger96
force-pushed
the
duncan/agent-usage-backend
branch
from
August 3, 2026 18:28
12f1f29 to
635b030
Compare
wpfleger96
force-pushed
the
duncan/agent-usage-ui
branch
from
August 3, 2026 18:28
4173c78 to
730583e
Compare
wpfleger96
force-pushed
the
duncan/agent-usage-backend
branch
from
August 3, 2026 18:56
635b030 to
39e1a91
Compare
wpfleger96
force-pushed
the
duncan/agent-usage-ui
branch
2 times, most recently
from
August 3, 2026 19:31
d0e14d3 to
d27d77c
Compare
wpfleger96
marked this pull request as ready for review
August 3, 2026 19:36
wpfleger96
force-pushed
the
duncan/agent-usage-backend
branch
from
August 4, 2026 01:03
39e1a91 to
66a09a8
Compare
wpfleger96
force-pushed
the
duncan/agent-usage-backend
branch
9 times, most recently
from
August 8, 2026 21:43
eb24590 to
762e47b
Compare
wpfleger96
added a commit
that referenced
this pull request
Aug 10, 2026
…hive + P4a aggregation/D6 (#4000) ## What Implements Phases 2 and 4a of the Usage v2 plan (plan events `d0268cd0`/`0e95b035`), extending the archive backend to emit, transport, archive, and aggregate both cache categories and billing identity fail-closed. ### P2 — emission, transport, archive **Tri-state accumulators** (`Unseen`/`Exact`/`Unknown`) for cache-read and cache-write in `buzz-agent` turn and session state. Absent field = Unknown (never zero) through the full pipeline. No `unwrap_or(0)` on the cache path. Both cache folds are gated on usage-bearing responses (same gate as the total-state and identity folds) — a response with no usage at all must not poison either accumulator. **Overflow-aware input token parsing and accumulation** — closed end-to-end from parse through wire to ACP: - `sum_usage()` returns `SumUsageResult` (`Exact(u64)` | `Overflow`) — checked arithmetic, never clamps. `anthropic_input_tokens()` returns `Option<SumUsageResult>` since it sums three fields (`input_tokens + cache_read_input_tokens + cache_creation_input_tokens`) that can collectively overflow. Single-field callers (`prompt_tokens`, `completion_tokens`, etc.) convert via `.into_exact()` — their single-field sums cannot overflow. - `LlmResponse.input_tokens_overflowed: bool` propagates the parse-layer signal into the run loop. When set, `input_tokens` is `None` (clamped value discarded), the context-gate baseline (`last_request_input_tokens`) is frozen at its prior reading, and `turn_input_tokens` is poisoned to `TurnIOState::Poisoned` before any emission — including mid-turn `emit_usage_update` calls. A dedicated enum on `LlmResponse.input_tokens` would ripple into ~20 existing test assertions on `r.input_tokens == Some(...)`; the bool flag confines the change to the two call sites that check it. - `TurnIOState` (`Unseen`/`Exact`/`Poisoned`) for input and output: per-round fold uses `checked_add`; overflow poisons permanently at turn and session level, no healing. Absence does not poison (pass-2-cleared contract unchanged). Wire emission omits `accumulatedInputTokens`/`accumulatedOutputTokens` when poisoned — never null, never `u64::MAX`. ACP treats absent = publisher-poisoned: `delta_reliable: false`, null turn fields, null cumulative for that category; session cumulative stays unknown for all subsequent turns once poisoned. **Conditional wire emission** for `accumulatedCachedInputTokens` and new `accumulatedCacheWriteTokens`: fields are omitted when the cumulative is Unseen or Unknown. ACP `_goose/unstable/session/update` contract documented next to the payload with tests for all absence/zero variants. **`PricingIdentity` stamping (publisher-side)**: - `pricing_authority()`: canonical parsed-URL endpoint comparison against the official allowlist — HTTPS only, exact allowlisted host (lookalike-safe), default port (omitted or explicit :443), required API base path, rejects userinfo/query/fragment/path-prefix lookalikes. - Model: the actually-requested `request_model` after mesh/auto resolution (not `effective_model_str`). - Turn discipline: identity retained only while ALL usage in the current turn carries one identical proven identity; any mismatch, unproven-usage-bearing response, or unpaired cumulative snapshot poisons to absent; a later matching notification does not heal a mixed turn. **ACP `UsageTracker` identity fold**: per-in-flight-turn tri-state identity accumulator replacing last-update-wins. Any absent identity on a token-advancing notification or exact mismatch poisons to absent; poison survives later updates; reset in `begin_turn()`/`take()`; reset also when a request fails (baseline cleared so preflight gate cannot stay frozen sub-threshold on retries). **M3 migration**: adds `turn_cache_write_tokens`, `cumulative_cache_write_tokens`, `pricing_authority`, `pricing_model`, `pricing_cache_class` to `agent_metric_index`. Additive, idempotent, guarded per-column by marker. M2 migration also guarded per-column (turn and cumulative cache-read columns checked and added independently; marker commits only after both are present). Fresh-DB schema includes all columns. **First-turn baselines**: `seed_zero_baseline` seeds `last_input: Some(0)`, `last_output: Some(0)`, `last_cached_input: Some(0)`, `last_cache_write: Some(0)`, and `last_total: Some(0)` — all have the known-zero-at-spawn argument. Absent fields from incoming snapshots still produce unknown (tri-state unchanged). Sessions buzz-acp did not spawn (no seed) remain fail-closed on turn one. **`ReportedUsage` TS mirror**: `cacheReadTokens`, `cacheWriteTokens`, `freshInputTokens` added to `tauriArchive.ts` as `UsageField` members, field-for-field with the Rust struct. ### P4a — aggregation layer **Extended S-1 ladder** to cache-read and cache-write via the same `ladder_token` path as the existing token fields. **`freshInputTokens` derivation**: checked arithmetic, fail-closed — absent cache fields produce Unknown (not zero), overflow and `cacheRead+cacheWrite > input` both produce `incomplete: true`. Aggregated as a `UsageField`. **D6 comparator**: `sort_value()` = provider total when known, else `input+output` when both known, else `None` (unknown-last). Replaces the prior total-only comparator for both agent-level and model-level sort. Ships a pinned test vector that the TS render layer (P5) must match. ## Test coverage - `buzz-agent`: 440 lib + 15 integration (golden_transcripts) — includes 13 new `cache_total_state_tests`; 14 new `turn_io_state_tests`; 3 new `sum_usage_*` tests (exact single-field, exact two-field, overflow signals correctly); 3 new `parse_anthropic_*` tests (overflow flag set + value cleared, normal sum no flag, absent usage no flag); end-to-end golden transcript drives real subprocess with Anthropic-shaped `input_tokens: u64::MAX, cache_read: 1` response and asserts `accumulatedInputTokens` absent from the emitted `usage_update` — no logic duplication; 3 wire pin tests; 4 `fold_pricing_identity_*` tests; `pricing_authority()` explicit-:443 acceptance - `buzz-acp`: 700 tests (691 lib + 9 integration) — 4 new usage tests (absent input → unreliable+null; absent output → unreliable+null; goose-shaped both present unchanged; poison mid-session); 3 ACP behavior tests; 7 pool lifecycle tests - Desktop (Rust): 2259+ tests — 14 new P4a pinned tests; 2 M3 round-trip tests; 1 serde key-shape test; 2 M2 partial-schema migration tests; first-turn cache round-trip test ## Related PRs - P1 NIP-AM spec: [#4632](#4632) - P3 pricing table: [#4629](#4629) - UI (P5): [#4001](#4001) --------- Signed-off-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz> Co-authored-by: npub1mn7jgtj4w2pd0g0zeuhxsa6jy6p0rewxz4kujt98my82ahfmp72sxjexk7 <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
wpfleger96
force-pushed
the
duncan/agent-usage-ui
branch
from
August 10, 2026 16:20
d27d77c to
d71c2a0
Compare
wpfleger96
force-pushed
the
duncan/agent-usage-ui
branch
from
August 12, 2026 21:31
d71c2a0 to
01ab4be
Compare
Adds the agent-usage UI feature on top of the merged NIP-AM backend. - `desktop/src/features/agent-usage/`: daily bars chart, 7/30-day range tabs, focused agent view with caveats, hooks, and lib utilities. All state is driven by the `get_agent_usage_series` Tauri command. - Profile panel: usage ingress row + focused view wired into agent profiles; gated on owner-scoped visibility (`viewerIsOwner && isBot`). - `AgentsView`: agent-usage overview section. - `AgentsScreen`/`ProfilePanelContext`: usage navigation state. - `e2eBridge.ts` + `tests/helpers/bridge.ts`: `get_agent_usage_series` mock handler + fixture types, including the M2 cache fields (`cacheReadTokens`, `cacheWriteTokens`, `freshInputTokens`) the wire type carries. The UI absorbs but does not display these fields. - `tests/e2e/agent-usage.spec.ts` + `agent-usage-screenshots.spec.ts`: Playwright coverage for loading, empty state, bars, range switching, error/retry, focused view, caveats, and cache invalidation. - `desktop/playwright.config.ts`: agent-usage specs added to suite. - `desktop/package.json`: recharts dependency. Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
wpfleger96
force-pushed
the
duncan/agent-usage-ui
branch
from
August 12, 2026 21:34
01ab4be to
3d723a7
Compare
…t-usage focused view The plumbing already returned cacheReadTokens/cacheWriteTokens/freshInputTokens at every scope of AgentUsageSeries, but the focused view rendered only the four top-level stats and never surfaced them. Add an Input-breakdown subsection to the totals card (Cache read / Cache write / Fresh input) gated on any known cache value, and a compact per-model breakdown line. Fields follow the existing unknown-not-zero semantics: absent subsets are omitted, a known-but-incomplete value shows its lower bound with a Partial marker, and a scope with no cache data renders no subsection at all. Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
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.
What
Adds the full agent-usage UI feature: daily token-usage bars, 7/30-day range tabs, and a focused per-agent view, surfaced in agent profiles behind an owner-scoped visibility gate. The focused view displays total/input/output/cost plus a cache-token breakdown (cache-read, cache-write, fresh-input) in the totals card and per model. The NIP-AM backend it consumes merged in #4000.
Files
desktop/src/features/agent-usage/— bars chart, range tabs, focused view, hooks, liblib/agentUsage.ts(formatModelCacheBreakdown,hasKnownCacheData),AgentUsageFocusedView.tsx(Input-breakdown subsection + per-model line)UserProfilePanel,UserProfilePanelSections,UserProfilePanelTabs,UserProfilePanelUtilsAgentsScreen,AgentsView(reorder)ProfilePanelContexte2eBridge.ts,tests/helpers/bridge.ts(mock handler + fixture types)agent-usage.spec.ts(21 tests),agent-usage-screenshots.spec.ts(3 tests)playwright.config.ts,package.json(recharts)Cache-token display
The wire type (
AgentUsageSeries) already carriedcacheReadTokens/cacheWriteTokens/freshInputTokensat every scope (totals, per-day buckets, per-model); the focused view now renders them. An Input-breakdown subsection in the totals card shows the three subsets asTokenStats, gated on any known cache value, and each per-model row carries a compact breakdown line beneath it.Fields follow the existing unknown-not-zero semantics: an absent subset is omitted from the per-model line and renders
—in the totals (never0); a known-but-incomplete value shows its lower bound with aPartialmarker; and a scope with no cache data at all shows no subsection. Rows that never reported cache tokens therefore surfacePartial/—honestly rather than a fabricated zero.Stack
Stack:
agent-usage-backend #4000(merged at5e4c05f90b) → this PRReview note
The base UI content was reviewed at
3d6c9c413(PR #2035, 9/9/9). This PR is the UI half of that reviewed diff, restacked onto currentmain, with the cache-token display added on top.The restack re-expressed the usage feature in the profile-panel structure that #5574 ("Refine channel settings and profile panels") introduced: main realigned agent profiles around shared
ProfileIngressRowrows and asetView/setTabstate machine. The usage ingress is now aProfileIngressRowin the Info tab (matching the Activity/Diagnostics siblings), and the focused view mounts on theview === "usage"branch withsetView("summary", { replace: true })on ineligibility. The owner-scoped visibility gate (viewerIsOwner && isBot) is preserved exactly.