The branch had ratcheted the desktop file-size gate four times. One of those
entries re-declared `src-tauri/src/lib.rs` at 1001 while a pre-existing entry
at line 58 sets 1013 — the override map is a JS `Map`, so the later key won and
the branch silently lowered a ceiling it never needed to touch (lib.rs is 920).
Splitting at existing seams removes the other two: the M1 schema migration
moves to `archive/store_migrations.rs` behind one `pub(super)` entry point, and
the kind-44200 archive tests move to `archive/mod_agent_metric_tests.rs`,
`#[path]`-included from `mod_tests.rs` so they keep reaching the shared
fixtures through `use super::*`. `mod_tests.rs` returns to its pre-branch 1208
ceiling rather than dropping it, since that debt predates this work.
`formatCoverageDate` was duplicated verbatim in both usage components; it now
lives beside the other formatters in `lib/agentUsage.ts`.
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>
Four Playwright tests covering the new Agent Usage UI surfaces:
01 overview card (default/collapsed), 02 focused usage subview,
03 multi-day bars chart, 04 empty state (collection on, no data).
Also adds agent-usage-screenshots.spec.ts to the smoke project
testMatch so CI can discover and run it alongside the other
screenshot specs.
Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
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>
Completes Phase 2 of the NIP-AM local agent usage feature. tauriArchive
decodes the backend's persistedAgentMetrics count defensively (missing
field -> 0, per A5/M2's backward-compatible wire contract) and exposes
an onAgentMetricsChanged notifier, fired only when a subscription
mutation touches kind 44200 specifically.
ArchiveSyncManager routes both its idle/size-triggered and destroy-time
flushes through one sendBatch helper that notifies usage listeners
only when the backend reports persistedAgentMetrics > 0 — rejections,
duplicate-only batches, and non-metric successes never notify, keeping
the refresh signal backend-authoritative.
Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Adds the Rust backend half of the NIP-AM local agent usage feature
(Rev 3 frozen plan): a rebuildable agent_metric_index parsed from
archived kind-44200 rows, a pure per-field accounting ladder
(agent_usage.rs) computing token/cost deltas with adjacent-cumulative
preference and direct-value fallback, and the get_agent_usage_series
Tauri command wiring backfill, orphan repair, collection-enabled
detection, and A13's hasArchivedEvidence into one series response.
commit_archive now indexes kind-44200 rows in the same transaction as
the canonical event insert, and upsert_archived_event/gc_orphaned_events
return/enforce the new-row and cascade-delete invariants (A5/A6) that
persistedAgentMetrics and the index's rebuildability depend on.
get_agent_usage_series' SQLite core is split into a plain sync fn so
it can be driven directly against an in-memory Connection in tests
without a Tauri AppState.
Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>