Three remaining pass-2 findings fixed:
1. sumKnownBucketTotals aggregation precedence (Thufir's explicit ruling).
One unknown report-bearing bucket no longer erases the known subtotal.
Track anyWithValue separately from sawAny; unknown buckets set partial=true
but do not touch sumValue. Return unknown/null ONLY when no report-bearing
bucket contributed any numeric value. Added three new tests: exact+unknown
(preserves exact subtotal, partial=true), approximate+unknown (preserves
approx subtotal, partial=true), all-unknown (returns unknown/null).
2. approx-partial daily bar trailing copy.
trailingText for approx-partial now renders '≈N*' (asterisk suffix)
instead of '≈N', giving sighted users a distinct partial signal beyond
opacity alone. T2 e2e test extended with an incomplete-i/o bucket and
an assertion on '≈1K*' to verify the distinguishable copy.
3. e2e guard surface isolation.
Header and focused-total assertions in the T1b regression guard now
target dedicated testids (agent-usage-header-value and
agent-usage-focused-total-value) so each surface fails independently
rather than passing via a descendant bar's ≈ text. Added testId prop
to UsageStat (optional, threading to the value <p> only), wired from
ApproxTokenStat. Added data-testid='agent-usage-header-value' to the
header value span in AgentUsageSection.
MINOR: fixed stale caveat-gate comment at agent-usage.spec.ts that still
attributed the sentence to hasUnknownUsage=true instead of direct i/o
incompleteness.
Claim reconciliation: the stated 21/21 Playwright count was wrong — the
file defines 17 tests and I ran 17/17. The prior run was against this
file at this head.
Gates: 3571 unit tests pass, tsc --noEmit clean, 17/17 agent-usage
Playwright specs pass under the smoke project.
Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
1. Gate unknown-intervals caveat on direct i/o incompleteness
(isPartialField(inputTokens) || isPartialField(outputTokens)) instead
of hasUnknownUsage, which ORs total/cost incompleteness too and cannot
prove an i/o interval claim. Narrow copy to 'Some input/output usage
could not be counted and is omitted rather than shown as zero.' Fix the
incorrect deltaReliable comment.
2. deriveDisplayTotal fail-closed: approximate only when BOTH input and
output are non-null. One-sided i/o returns unknown for the total (the
independent display fields may still show the known value). Replace
one-sided approximate tests with fail-closed tests.
3. sumKnownBucketTotals returns a single provenance-bearing DisplayTotal
instead of parallel {knownTotal, approxTotal, partial}. Exact when
every report-bearing bucket is exact; approximate (summing
exact+approx display values) when any bucket is approximate; unknown
when any report-bearing bucket has no display value. Partial derives
from the union of each bucket's DisplayTotal.partial (i/o and total
completeness) — not from total absence. Add mixed-bucket test.
4. Partial provenance end-to-end: AgentUsageSection header consumes the
single DisplayTotal and derives partial from its .partial field.
deriveBarState carries dt.partial in the approx bar (new
'approx-partial' kind) so genuinely incomplete i/o is identified in
daily bar styling and accessible copy. Approximation alone no longer
triggers Partial.
5. Behavioral e2e guard: new scenario seeds both i/o + totalTokens:null
and asserts all four surfaces (overview row, header, daily bar,
focused total) render '≈'. Fix stale 'in 800' assertion to '≈ 1K'.
Update the coverage-dates partial-explanation fixture to use
incomplete i/o fields so the new gate fires correctly.
Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Fold Thufir's three blocking amendments into cbfbe1ed0:
1. Centralize display metric as DisplayTotal {kind, value, partial}.
deriveApproxTotal() replaced by deriveDisplayTotal() returning an
explicit exact|approximate|unknown discriminant with provenance:
- exact: totalTokens.value present; partial mirrors wire incomplete flag
- approximate: totalTokens null, i/o known; value = bigint-safe i/o sum;
partial = inputTokens.incomplete || outputTokens.incomplete
- unknown: no token counts at all; value null, partial false
All surfaces (Section header/bars/rows, DailyBars, FocusedView stat)
now consume this single result — no surface can drift independently.
2. Re-sort agents/models by display tier.
sortAgentsByKnownTotal/sortModelsByKnownTotal replaced by
sortAgentsByDisplayTotal/sortModelsByDisplayTotal using a three-tier
key (exact=0, approximate=1, unknown=2), descending by value within
tier, then existing tiebreaks. With all-null prod totals the list
order and bar widths now agree instead of disagreeing.
3. Split caveat paragraph into per-condition sentences.
The single agent-usage-focused-partial-explanation block replaced by
two separately-gated <p> elements:
- agent-usage-focused-unknown-intervals-caveat: gates on hasUnknownUsage
- agent-usage-focused-invalid-reports-caveat: gates on invalidReportCount > 0
Each sentence claims only what its gate proves.
Test updates:
- deriveApproxTotal tests replaced with deriveDisplayTotal tests covering
all three kinds, partial provenance, and i/o-only combinations.
- Sort tests updated for new export names and assertions.
- Mixed exact/approximate/unknown population tests added for both
sortAgentsByDisplayTotal and sortModelsByDisplayTotal.
- e2e spec updated from single partial-explanation testid to the two
per-condition testids.
- 3567 tests pass, tsc --noEmit clean.
Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
No real publisher emits totalTokens today (0 of 1,934 prod reports
do). The UI was keying its flagship visuals off that field, producing
'No usage reported' over millions of real tokens.
Four display surfaces fixed:
- Daily bars header: '≈ N tokens' when knownTotal null but i/o known
- Daily bar heights: 'approx' bar kind scaled to i/o sum (bg-primary/70)
instead of the hatched unknown baseline
- Per-agent row: trailing text '≈ N' + progress bar from i/o approx
- Focused view 'Total tokens' stat: '≈ N,NNN' exact display
PARTIAL badge: no longer keys on totalTokens.incomplete (permanently
true); now keys on inputTokens/outputTokens incompleteness only, so
honest partial rows still badge but the permanently-null-total case
does not.
Caveat paragraph: gated on agent.hasUnknownUsage || invalidReportCount
> 0 only, not on the permanent totals-unknown state. Removed the false
persistent-error banner from every real prod user's focused view.
All approximations use ≈ prefix at every callsite — the wire contract
is unchanged; this is display only.
New lib export: deriveApproxTotal (5 unit tests). sumKnownBucketTotals
now returns approxTotal alongside knownTotal (2 new unit tests + 2
existing tests updated). e2e seeds updated: mockAgentUsage default
now uses {inputTokens, outputTokens, totalTokens: null} — the real
prod shape — so the suite can no longer pass while prod renders empty.
Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Stack: #2035 → this PR
## What
Group the per-agent model breakdown by `(harness, model)` instead of
`model` alone, so the same model running under two different harnesses
(e.g. `claude-sonnet` via `goose` vs `claude-code`) produces two
distinct rows rather than collapsing into one.
## Changes
**Rust / SQLite**
- `agent_metric_index`: add nullable `harness TEXT` column. Schema
migration M1: `ALTER TABLE ... ADD COLUMN` + delete-then-backfill
rebuild so all existing rows get `harness` populated from retained
`archived_events.raw_json` — no data loss; ingest and backfill share the
same `from_payload` parser (frozen-plan requirement preserved).
- `AgentMetricIndexRow`: parse `harness` from
`AgentTurnMetricPayload.harness` (REQUIRED field per NIP-AM), write/read
through all store paths (`ROW_COLUMNS`, INSERT, `row_from_sql`).
- `AgentScope.models` grouping key widened from `Option<String>` to
`(Option<String>, Option<String>)` i.e. `(harness, model)`. Sort
tiebreak: harness ascending → model ascending, `None` last in each.
- `ModelUsage` wire type: add `harness: Option<String>` field.
**Frontend**
- `tauriArchive.ts` / `bridge.ts`: add `harness` to `AgentUsageModel`
type.
- `AgentUsageFocusedView`: render `harness` as a dimmed sub-label next
to each model name on breakdown rows. `null` harness (pre-migration data
or unknown) renders no label — single-harness data is visually
unchanged.
- `agentUsage.ts` `sortModelsByKnownTotal`: tiebreak updated for
compound `(harness, model)` key.
**Tests**
- Rust: collapse-fix test (same model / two harnesses → two rows),
migration test (old-shape rows get harness populated after rebuild),
harness sort coverage.
- TS: `sortModelsByKnownTotal` harness tiebreak tests, same-model
two-harness sort test.
- E2E: `bridge.ts` fixture type updated, `agent-usage.spec.ts` harness
assertion, `agent-usage-screenshots.spec.ts` shot 02 harness-label
visibility check.
## Gates
All green locally:
- `just desktop-check` ✓
- `just desktop-typecheck` ✓
- `just desktop-build` ✓
- `just desktop-test` (3487 pass, 0 fail) ✓
- `cargo test` in `desktop/src-tauri` (1575 pass, 0 fail) ✓
---------
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Co-authored-by: npub1mn7jgtj4w2pd0g0zeuhxsa6jy6p0rewxz4kujt98my82ahfmp72sxjexk7 <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
Surfaces per-agent NIP-AM turn-metric usage (tokens, cost, model
breakdown, 7d/30d bucket series) in the desktop app, reading from the
Phase 2 get_agent_usage_series archive command:
- agent-usage/lib/agentUsage.ts: pure client-side helpers -- bucket
boundary construction, bigint-safe token formatting, and the ranked
overview-row projection shared by both UI surfaces.
- agent-usage/hooks.ts: react-query wrapper around
get_agent_usage_series with the app's standard retry:1 default.
- agent-usage/ui/AgentUsageSection.tsx: ranked per-agent usage rows on
the Agents overview, with a 7d/30d window switch, retry-on-error,
and a collection-off banner (with or without retained-data coverage
copy) linking to Local Archive settings.
- agent-usage/ui/AgentUsageFocusedView.tsx: per-agent drill-down
(bucketed series + model breakdown) reached via an overview row or
the profile panel's Info-tab usage ingress row; wired into
AgentsView, UserProfilePanel, and ProfilePanelContext.
- Partial badge on rows whose total is a known lower bound
(has_unknown_usage), surfaced via the accounting engine's per-field
completeness contract.
Extends the Rust archive test suite (agent_usage_tests.rs) to close
gaps in the pure accounting engine: the f64 cost ladder had no direct
outcome.cost assertions, assign_bucket_index's start-inclusive/
end-exclusive edges were only exercised indirectly, validate_request's
chrono finite-range rejection was unexercised, and the A2 ranking
tiebreak (pubkey ascending on an exact totalTokens tie) had no test
distinguishing it from insertion order.
tests/e2e/agent-usage.spec.ts (wired into playwright.config.ts's smoke
project) exercises both UI surfaces against a mocked
get_agent_usage_series: ranked rows, window switching, click-through
to the focused view from both the overview row and the profile
panel's ingress row, retry recovery from a query error, the
collection-off banner and its settings deep link, and the Partial
badge.
Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>