Commit Graph
7 Commits
Author SHA1 Message Date
npub1mn7jgtj4w2pd0g0zeuhxsa6jy6p0rewxz4kujt98my82ahfmp72sxjexk7andWill Pfleger d914db0dd2 fix(agent-usage): clear Thufir pass-2 findings (aggregation, partial bar copy, e2e isolation)
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>
2026-07-29 17:16:31 -04:00
npub1mn7jgtj4w2pd0g0zeuhxsa6jy6p0rewxz4kujt98my82ahfmp72sxjexk7andWill Pfleger 031541a301 fix(agent-usage): clear Thufir pass-1 findings (5 correctness fixes)
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>
2026-07-29 16:50:19 -04:00
npub1mn7jgtj4w2pd0g0zeuhxsa6jy6p0rewxz4kujt98my82ahfmp72sxjexk7andWill Pfleger b384fc9a56 fix(agent-usage): centralize DisplayTotal, re-sort by display tier, split caveat
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>
2026-07-29 14:23:51 -04:00
npub1mn7jgtj4w2pd0g0zeuhxsa6jy6p0rewxz4kujt98my82ahfmp72sxjexk7andWill Pfleger cbfbe1ed05 fix(agent-usage): fall back to ≈in+out display when totalTokens null
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>
2026-07-29 14:07:34 -04:00
194ceb64f5 feat(desktop): add harness to NIP-AM agent-usage breakdown (#2790)
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>
2026-07-25 12:04:11 -04:00
npub1mn7jgtj4w2pd0g0zeuhxsa6jy6p0rewxz4kujt98my82ahfmp72sxjexk7andWill Pfleger 122b043603 Merge remote-tracking branch 'origin/main' into duncan/agent-usage-archive
* origin/main: (25 commits)
  feat(cli): filter archived instances from --template roster resolution (#2207)
  chore(release): release Buzz Desktop version 0.4.21 (#2209)
  fix(desktop): clarify data deletion action (#2208)
  feat: brand authentication complete page (#2192)
  fix(buzz-acp,buzz-agent): surface stall duration and fate (#2204)
  fix(desktop): resolve activity feed showing channel UUID instead of message content (#2201)
  feat(cli): add agents archive/unarchive/archived subcommands (#2173)
  chore(acp): strip stale finding-number references from comments (#2202)
  chore(release): release Buzz Mobile version 0.4.9 (#2200)
  Simplify first-community onboarding choices (UI only) (#2194)
  fix(mobile): sanitize Android image uploads (#2188)
  fix(cli): paginate channel directory queries (#2181)
  Fix persisted agent defaults display (#2182)
  [codex] Fix missing release tag detection (#2191)
  feat(web): authenticate gated community browsing (#2190)
  fix(timeout): unified turn-timeout fix — cap inheritance, steer renewal, activity-aware requeue, LLM stall surfacing (#2175)
  fix(desktop): scope draft store per workspace relay (#2179)
  chore(release): release Buzz Mobile version 0.4.8 (#2187)
  [codex] create release tags with dedicated App (#2186)
  fix(desktop): avoid forwarding click event as channel callback (#2174)
  ...

Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
2026-07-21 00:53:16 -07:00
npub1mn7jgtj4w2pd0g0zeuhxsa6jy6p0rewxz4kujt98my82ahfmp72sxjexk7andWill Pfleger a073cc8b84 feat(desktop): add Agent Usage UI backed by the NIP-AM local archive
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>
2026-07-20 23:28:50 -07:00