Commit graph

273 commits

Author SHA1 Message Date
iamtoruk
800c106250 Fix streaming dedup: keep last occurrence of each message.id within session files
Claude Code writes the same message.id multiple times during streaming.
The first write has partial tokens (often 1) and no tool_use blocks.
The last write has authoritative token counts and all tool_use/MCP blocks.

Old behavior kept the first occurrence (keep-first), silently dropping
real output tokens (+6.3% undercount) and all MCP tool calls.

New behavior keeps the last occurrence's content but preserves the first
occurrence's timestamp for correct date bucketing.

Validated against 21,390 real session files: 40.5% had duplicate IDs,
output tokens were understated by up to 78% per session.
2026-05-02 22:30:17 -07:00
iamtoruk
341aa46f78 Strip ANSI escapes from bash commands across all providers
Use strip-ansi (already in dep tree via Ink) in extractBashCommands
to prevent terminal escape codes from leaking into dashboard bash
breakdown keys. Route goose, gemini, qwen, and openclaw through
extractBashCommands instead of inline split, which also gives them
multi-command extraction (matching claude/codex/droid behavior).
2026-05-02 20:59:24 -07:00
iamtoruk
fe1007cc63 Add missing Antigravity model aliases for gemini-3-pro, flash-image, flash-lite 2026-05-02 20:26:45 -07:00
Nihal Jain
73cf90cb2c Add Antigravity Gemini model IDs with preview pricing aliases 2026-05-03 05:46:33 +05:30
iamtoruk
c511627e87 Fix dashboard hang and ExperimentalWarning on Windows
Strip Ink v7 DEC mode 2026 synchronized output markers (BSU/ESU) on
Windows. ConPTY does not implement this protocol and buffers
indefinitely, causing the dashboard to hang with no output. The patch
intercepts standalone BSU/ESU writes on stdout while preserving full
interactivity (keyboard, live refresh, cursor management).

Fix ExperimentalWarning timing: the process.emit patch was restored
synchronously in the finally block, but Node defers the warning via
process.nextTick. Delay restore by one tick so the patch is still
active when the warning fires.

Closes #195
2026-05-02 11:54:58 -07:00
Resham Joshi
3ea4a62408
Add Goose provider, fix Codex fork dedup
Some checks are pending
CI / semgrep (push) Waiting to run
Goose: read token usage from ~/.local/share/goose/sessions/sessions.db.
Lazy-loaded, zero overhead for non-Goose users.

Codex: use sessionId instead of file path in dedup key so forked
sessions sharing the same session_id don't double-count tokens.
2026-05-02 09:58:52 -07:00
Resham Joshi
8c845253c2
Add Antigravity IDE provider
Fetch token usage from Antigravity's local language server via RPC.
Falls back to cached results when the IDE is closed.
2026-05-02 08:58:23 -07:00
Nihal Jain
791f2b077d
Add gpt-5.5 model display name for Codex 2026-05-02 08:57:44 -07:00
ozymandiashh
ffe14fecb7 review: silence stream errors and tighten comment wording
- Attach a no-op `stream.on('error', () => {})` so a late read-ahead
  error that races with a successful first-line yield can never escape
  as an unhandled 'error' event. Defense in depth: empirically the
  destroy() in finally already swallows it on Node 18+, but the listener
  removes any version-dependent surprise.
- Tighten the comment to say "up to FIRST_LINE_READ_CAP" instead of
  "regardless of length"; the cap is real and worth being precise about.
2026-05-02 02:38:36 +03:00
ozymandiashh
ff8b20a79e review: drop streamError flag, add multi-chunk and torn-write tests
- Stop tracking a separate streamError flag. createReadStream's default
  64 KiB highWaterMark means the stream may already be reading chunk 2
  when we break out of the loop after yielding the first line; if that
  later chunk errors, the flag could reject an otherwise-valid line.
  readline's async iterator already re-throws stream errors on Node 16+,
  which the existing catch handles.
- Test: 120 KB session_meta line forces multi-chunk line assembly.
- Test: truncated mid-write first line is rejected, not parsed as half
  an object.
2026-05-02 02:34:41 +03:00
ozymandiashh
98bbe5b678 review: cap first-line read size and add edge-case tests
- Cap createReadStream at 1 MiB so a malformed file with no newline
  cannot make readline buffer indefinitely (real session_meta lines
  are 22-27 KB).
- Capture stream errors explicitly; readline's async iterator does
  not always re-throw underlying stream errors per Node docs.
- Test: assert project is extracted from the >16 KB session_meta to
  prove the line was actually parsed, not just discovered.
- Test: session_meta line with no trailing newline is still accepted.
- Test: empty rollout file is silently skipped.
2026-05-02 02:30:17 +03:00
ozymandiashh
945da9f0ba fix(codex): read full first line for session validation
`readFirstLine` allocated a fixed 16 KB buffer, but Codex CLI 0.128+
embeds the entire base_instructions / system prompt in the
`session_meta` line, pushing it past 20 KB. When the buffer doesn't
catch a newline, `isValidCodexSession` rejects the session, so every
recent Codex session is silently excluded from totals.

Switch to a streaming readline read so the first line is captured
regardless of length, and add a regression test that creates a
40 KB session_meta payload.

Locally, this changes my 30-day Codex total from €267 (only ~half
of sessions parsed) to €878 (all sessions parsed).
2026-05-02 02:17:53 +03:00
Resham Joshi
ffc0e486d3
Merge pull request #188 from getagentseal/feat/menubar-hardening
Harden menubar: refresh loop, concurrency, data sync, edge cases
2026-05-01 08:03:03 -07:00
iamtoruk
39fc05595c Harden menubar: fix refresh loop, concurrency, data sync, and edge cases
- Fix refresh loop: proper while loop with 30s sleep and force:true
  instead of single-fire Task that never repeated
- Fix loading overlay: counter-based isLoading so concurrent fetches
  don't flicker the overlay on/off
- Fix rapid tab switching: cancel previous switchTask, check
  Task.isCancelled after CLI returns to discard stale results
- Fix tab strip vs hero desync: fetch provider-specific and all-provider
  data in parallel so costs arrive from same data snapshot
- Fix stale menubar icon after wake: forceRefresh now fetches today/all
  in parallel alongside the current selection
- Fix accent color: ThemeState is now @Observable so color changes
  propagate via observation, removing .id() view hierarchy teardown
- Fix currency flash: defer store.currency and symbol update until a
  rate is available so symbol and rate apply atomically
- Fix export: terminationHandler instead of waitUntilExit (no UI freeze),
  HHmmss in filename to prevent overwrite on double-export
- Fix CurrencyState: @MainActor isolation with proper Sendable
  conformance, nonisolated on pure static functions
- Fix streak count: iterate calendar days instead of sparse history
  entries so gaps are counted as streak-breakers
- Fix TrendBar identity: stable date-based id instead of UUID
- Add GPT-5.3 and DeepSeek model display names
2026-05-01 08:01:25 -07:00
Resham Joshi
bbe99fa298
Merge pull request #180 from josteinaj/locate-workspaceStorage-in-vscode-dev-container
Some checks are pending
CI / semgrep (push) Waiting to run
VSCode: correctly locate workspaceStorage when working inside a dev container
2026-04-30 18:41:20 -07:00
Resham Joshi
78ad04c77f
Merge pull request #186 from getagentseal/fix/menubar-timezone-184
Fix timezone handling for non-UTC users
2026-04-30 17:33:27 -07:00
iamtoruk
68c6f2c710 Fix timezone handling: menubar UTC bugs, --timezone flag, DST-safe dates
Three fixes for issue #184:

1. Menubar Swift code used UTC instead of local timezone in two places:
   computeHistoryStats hardcoded TimeZone("UTC") and
   effectiveTokensInLast7Days used ISO8601DateFormatter (UTC default).
   Both now use .current to match CLI-produced local date keys.

2. Add --timezone flag and CODEBURN_TZ env var to override the system
   timezone for all date grouping. Sets process.env.TZ before any Date
   operations so all existing local-timezone code works unchanged.

3. Replace MS_PER_DAY arithmetic with Date constructor day-of-month
   math for yesterday/backfill computations. Subtracting 86400000ms
   from midnight skips a day on DST spring-forward (23-hour day).

Fixes #184
2026-04-30 17:33:02 -07:00
iamtoruk
8ab9ea916b Add per-file result cache for Codex provider
Fixes #183. Users with large Codex session directories (45 GB, 10K+
files) experienced CPU pegging because every 30-second refresh re-parsed
all session files from scratch.

Three optimizations:

1. readFirstLine now reads 16 KB via fs.open() instead of loading the
   entire file through readSessionFile. Cuts discovery I/O from ~45 GB
   to ~160 MB for 10K files.

2. Per-file result cache (codex-results.json) with mtime+size
   fingerprinting. Parsed results are cached on first run; subsequent
   runs return cached data instantly for unchanged files.

3. Cache-accelerated discovery skips header validation for cached files,
   pulling the project name directly from the cache manifest.

Cache safety: fingerprint captured before read (no TOCTOU), atomic
write via temp+fsync+rename, 0o600 permissions, Object.hasOwn for
prototype pollution defense, eviction of deleted files on flush,
try/finally ensures flush even on parse errors.
2026-04-30 16:43:41 -07:00
Jostein Austvik Jacobsen
2de8909823
locate workspaceStorage in vscode dev container 2026-04-30 22:51:52 +02:00
David
116388427e fix(copilot): discover VS Code Insiders transcripts 2026-04-30 16:27:22 +02:00
AgentSeal
7b8f4df856 Fix review findings: error handling, constant dedup, migration persist
- Wrap hydrateCache() in try/catch so disk errors don't crash commands
  that previously never touched the cache
- Export MS_PER_DAY, BACKFILL_DAYS, toDateString from daily-cache.ts
  and remove duplicates from cli.ts
- Remove double hydrateCache() call in report JSON path
- Persist migrated cache to disk so old-version files aren't
  re-migrated on every run
- Export emptyCache() for use as fallback on hydration failure
2026-04-28 22:56:47 +02:00
AgentSeal
888f592bd2 Make daily cache durable: hydrate from all commands, migrate instead of nuke
- Extract ensureCacheHydrated() from menubar-json path into daily-cache.ts
- Call it from every command that parses sessions (report, status, today,
  month, export, optimize, compare, yield) so CLI-only users also persist
  historical data that survives source file deletion
- Replace strict version equality check with fill-defaults migration for
  cache versions 2-4, preserving history across schema changes
- Back up old cache to .bak before discarding on unmigrateable versions
- Fix Copilot auto bucket display names in menubar (Copilot (Anthropic),
  Copilot (OpenAI))
- Fix Roo Code / KiloCode provider key matching in menubar tab strip
2026-04-28 22:41:01 +02:00
Resham Joshi
fbb2c4e69c
Merge pull request #171 from ksp2000/feature/copilot-auto-model-buckets
refactor(copilot): use auto model buckets for transcript inference
2026-04-28 12:17:50 -07:00
Dunccan de Weerdt
26ebe75aa1 Add Droid CLI provider
Discovers and parses sessions from ~/.factory/sessions/, reading JSONL
message logs and companion settings.json files for token usage tracking.

- Discovers sessions by scanning per-cwd subdirectories
- Skips internal .factory housekeeping sessions
- Extracts tools, bash commands, and user messages from JSONL
- Distributes session-level cumulative token counts across calls
- Normalizes Droid model wrappers before existing pricing lookup
- Derives clean project names from cwd paths
- Adds menubar provider filtering for Droid
2026-04-28 20:16:45 +02:00
AgentSeal
d043795855 Add Qwen provider and replace hardcoded pricing with LiteLLM snapshot
- Add Qwen CLI provider (discovers sessions from ~/.qwen/projects/)
- Replace FALLBACK_PRICING (40 hand-maintained entries) with auto-generated
  LiteLLM snapshot (3595 models including Azure, OpenRouter pricing)
- Build script fetches and bundles LiteLLM data before tsup
- Provider-prefixed lookups (azure/, openrouter/) resolve to correct pricing
- Add display names for all GPT-5.x model variants
- Add Qwen to menubar provider filter and tab strip
2026-04-28 19:49:14 +02:00
Resham Joshi
ec2de6a642
Add OpenClaw, Roo Code, and KiloCode providers (#175)
- OpenClaw: JSONL parser with multi-path discovery, tool extraction
  (toolCall + tool_use block types), model tracking via model_change
  and custom model-snapshot events
- Roo Code + KiloCode: shared Cline-family parser extracts model from
  <model> tags in api_conversation_history.json, strips provider
  prefixes from model names
- Add cline-auto and openclaw-auto aliases and display names
- Add menubar provider filters and tab colors for all three
- Show cached data instantly instead of blocking on CLI refresh
2026-04-28 09:24:14 -07:00
saipraneeth.konda
74c1c4b4c1 refactor(copilot): use auto model buckets for transcript inference 2026-04-28 19:32:03 +05:30
AgentSeal
ce78ac52c1 Add builtin aliases for Cursor model names
Some checks are pending
CI / semgrep (push) Waiting to run
Cursor emits dot-version tier-last names like claude-4.6-sonnet
that don't match our pricing keys. Map them so individual models
show costs instead of $0.
2026-04-28 15:09:54 +02:00
AgentSeal
64259c929c Fix Gemini provider for JSONL format (CLI 0.39+)
Gemini CLI 0.39 switched from single JSON to JSONL with one object
per line and $set metadata lines. Parser now handles both formats.
Also updated --provider help text to list all providers.
2026-04-28 15:03:39 +02:00
Resham Joshi
383df90c93
Fix Cursor provider dropping data older than 35 days (#169)
Increase lookback from 35 to 180 days so longer period views
(30-day, month, all) show accurate totals.

Fixes #159, fixes #163
2026-04-27 20:02:06 -07:00
Resham Joshi
6d15ea43a5
Add Gemini CLI provider for session tracking (#168)
Parse ~/.gemini/tmp/<project>/chats/session-*.json files from Gemini
CLI 0.38+. Uses real token counts (input, output, cached, thoughts)
embedded in each message instead of character estimation. Correctly
separates cached tokens from fresh input to avoid double-charging.

- Pricing for gemini-3.1-pro-preview, gemini-3-flash-preview,
  gemini-2.5-pro, gemini-2.5-flash from official Google API rates
- Tool name normalization (ReadFile->Read, SearchText->Grep, etc.)
- Menubar tab with Google Blue color (#4485F4)

Closes #166
2026-04-27 19:48:25 -07:00
Resham Joshi
f7f64a01ab
Add new providers, fix menubar tabs, accent color picker (#167)
* Add Kiro provider and transparent auto-model naming

- Add Kiro IDE provider: parses .chat JSON files, estimates tokens,
  normalizes dot-versioned model IDs for cost lookup
- Show "Cursor (auto)", "Copilot (auto)", "Kiro (auto)" in CLI
  dashboard instead of pretending to know which model was used
- Route auto model names through BUILTIN_ALIASES for cost estimation

* Fix menubar tabs: add missing providers, show period-scoped costs

- Add Kiro, OMP to ProviderFilter enum so installed providers appear as tabs
- Merge Cursor + Cursor Agent into single Cursor tab
- Tab costs now reflect the selected period (7d/30d/month/all) instead
  of always showing today
- Tab visibility still uses today's provider list so tabs don't
  disappear when switching to periods with no data

* Add accent color picker to menubar with Apple system presets

- 9 presets using Apple's exact macOS dark-mode accent colors
  (Ember, Blue, Purple, Pink, Red, Orange, Yellow, Green, Graphite)
- Color picker in header, persisted via UserDefaults
- "Burn" text stays fixed ember regardless of accent
- ThemeState is MainActor-isolated for thread safety
- Picker state lifted to AppStore so it survives .id() tree rebuild
- Accessibility labels on all color swatches
- Renamed brandAccentDark/brandEmberDeep/brandEmberGlow to match
  their actual light/deep/glow semantics

* Fix review findings: case-sensitive cost lookup, Kiro timestamp guard, cache versioning

- Normalize provider dictionary keys to lowercase in tab cost lookup
  so "Cursor Agent" (title-case from CLI) matches providerKeys
- Guard against missing/invalid/epoch startTime in Kiro parser to
  prevent RangeError crash or 1970-01-01 ghost entries
- Bump DAILY_CACHE_VERSION to 4 so upgraded users get a clean
  recompute with the new auto-model naming (cursor-auto vs default)
- Add version field to cursor-results.json cache to invalidate stale
  entries that still use the old 'default' model name
2026-04-27 19:46:30 -07:00
Resham Joshi
5d1b335c0a
Fix Copilot provider to read VS Code workspace transcripts (#165)
The Copilot provider only looked in ~/.copilot/session-state/ which is
from an older CLI tool. VS Code Copilot agent stores transcripts in
~/Library/Application Support/Code/User/workspaceStorage/*/GitHub.copilot-chat/transcripts/.

The new transcript format has no outputTokens or model_change events,
so tokens are estimated from content length and the model is inferred
from tool call ID prefixes. Both legacy and VS Code paths are now
scanned in parallel.

Fixes #161
2026-04-27 19:44:35 -07:00
AgentSeal
410fad9495 Fix Cursor provider reporting $0 for v3 bubble format and NULL createdAt rows
Cursor v3 stores zero token counts in bubbles, causing parseBubbles to
return empty results. The query also dropped rows with NULL createdAt
via the SQL comparison, hiding data from older Cursor versions too.

Changes:
- Remove inputTokens > 0 SQL filter, estimate tokens from text length
  when token counts are zero (same 4 chars/token ratio as agentKv)
- Include NULL createdAt rows with OR IS NULL, fall back to current
  timestamp when createdAt is missing
- Parse agentKv entries with plain string content instead of skipping
  them (not all content is a JSON array)
- Always parse both bubbles and agentKv instead of agentKv-only fallback
- Discover subagent transcripts in subagents/ subdirectories
- Fix timezone-dependent test in day-aggregator

Fixes #159, #163
2026-04-28 00:35:51 +02:00
AgentSeal
6eb9ba0a3b chore: clean up yield.ts - remove redundant comments and dead code 2026-04-25 14:04:16 +02:00
iamtoruk
1c5ca45bd8 Add experimental yield command to track productive vs wasted spend
New command: codeburn yield --period <period>

Correlates AI sessions with git commits to categorize spend:
- Productive: sessions with nearby commits that made it to main
- Reverted: sessions with commits that were reverted
- Abandoned: sessions with no commits or not in main

Uses timestamp proximity heuristic (session time + 1 hour window).
Works across branches, squash merges, and rebases by checking
if commits are in main branch ancestry.

Closes #152
2026-04-25 13:47:07 +02:00
iamtoruk
8ffbffcd7f Fix double-counting in menubar-json period data
PR #136 (2e5e449) changed parseAllSessions to use periodInfo.range
(full period) instead of just today's range. When combined with
cached historical data, this caused days to be counted twice:
- Once from getDaysInRange(cache, ...)
- Again from parseAllSessions(periodInfo.range, ...)

Result: 7-day cost showed ~$402 instead of correct ~$209.

Fix: Parse only today's sessions when using cache path. Historical
data comes exclusively from cache, today's data from fresh parse.
2026-04-25 01:44:02 +02:00
AgentSeal
ed7d76567b Release 0.9.0: Cursor Composer 2 support and provider fixes
- Fix cursor-agent provider to detect Composer 2 JSONL sessions (#142)
- Bump version to 0.9.0
- Update changelog with all 0.9.0 changes
2026-04-24 20:24:49 +02:00
AgentSeal
752aaf04c1 Fix compare chart bar colors to always match legend
Bars now always show their assigned colors (blue for model A, green
for model B) instead of graying out the non-winner. Only the winner's
percentage value is highlighted in green.
2026-04-24 20:18:33 +02:00
AgentSeal
396c8b542c Fix Codex provider to detect file edits from patch_apply_end events
Codex records file modifications as event_msg entries with type
patch_apply_end rather than function_call tool invocations. This fix
tracks those events and adds Edit to the tools list, enabling proper
calculation of edit turns, one-shot rates, and retry rates.

Fixes edit detection showing 0 edit turns despite actual file changes.
2026-04-24 20:07:13 +02:00
Resham Joshi
8c2fc4ffe8
Merge pull request #145 from getagentseal/fix-cursor-provider-agentKv
Fix Cursor provider for newer versions
2026-04-24 11:06:49 -07:00
AgentSeal
ec10ad1b67 Fix Codex model extraction from turn_context entries 2026-04-24 19:42:21 +02:00
AgentSeal
e1609e495c Add GPT-5.2 Low model display name for Cursor 2026-04-24 19:23:54 +02:00
AgentSeal
681afdbece Fix Cursor provider for newer versions that store data in agentKv
Newer Cursor versions store conversation data in agentKv entries
instead of populating tokenCount in bubbleId entries. This adds
a fallback parser that reads agentKv blobs and estimates tokens
from content length.

Closes #114
Closes #102
2026-04-24 18:11:25 +02:00
AgentSeal
25a71bf016 Add claude-max-5x preset for $100/month tier
Closes #141
2026-04-24 15:47:59 +02:00
AgentSeal
2e5e449ad1 fix: use consistent date range for menubar status queries
The menubar showed stale prices because provider all used end:now while provider specific queries used end:endOfDay. Sessions with timestamps after now was captured were excluded from all providers but included in specific provider queries.

Use periodInfo.range consistently across all parseAllSessions calls in menubar json status.
2026-04-22 21:28:12 +02:00
Łukasz Majcher
5e49f17e64 fix: switch scanJsonlFile and parseSessionFile to readSessionLines to prevent OOM
readViaStream (used for files ≥8 MB) reconstructs the full file as a
single string via chunks.join('\n'), giving the same peak allocation as
readFile. Callers then call content.split('\n'), creating a second copy.
With FILE_READ_CONCURRENCY=16 and files up to 128 MB this can exhaust
the V8 heap (~6 GB theoretical peak).

readSessionLines already exists as a proper async generator that yields
one line at a time. Switch both hot-path callers to iterate it directly
so the full file string is never held in memory.

Adds two tests: a spy test confirming readSessionLines is called (not
readSessionFile), and a 500-entry correctness test.

Fixes #131
2026-04-22 10:11:13 +00:00
iamtoruk
4f1138290e Merge main into feat/omp-support-model-aliases
Second merge of main since the PR was opened. Main moved 30+ commits
(0.8.5 bump, plan tracking feature, MiniMax pricing, menubar
prefetchAll walk-back, aicrowd cache rewrite revert) so the branch
needed another reconciliation before merging to main.

Two new conflicts resolved. Took main's text in both cases per the
policy of favoring main when the feature work is neutral:

  README.md             Kept main's Node 20+ / better-sqlite3
                        Requirements wording and main's shorter src/
                        tree listing. Added OMP to the Requirements
                        line.

  src/providers/pi.ts   Main dropped the discovery-cache snapshot and
                        the rich source-metadata fields as part of the
                        aicrowd revert. Took main's simpler structure
                        and only kept the providerName parameter so
                        OMP sources still report the correct provider
                        in the session source and dedup key.

Earlier fixups carried forward from the prior merge commit:
  - Object.hasOwn guards in resolveAlias against prototype-pollution
    via a model literally named '__proto__'.
  - source.provider in the dedup key prefix so OMP rows no longer
    stamp 'pi:'.
  - Combined pi.js imports in providers/index.ts.
  - Trailing newline on pi.ts.
  - Unknown-model fallback in cursor-agent.ts from yesterday's PR #117
    fixup (preserved via main).

353 tests pass (count dropped from 378 because main deleted the
parse-progress / parser-cache / provider-colors / source-cache test
files alongside the cache-rewrite revert).

Feature work by @cgrossde.
2026-04-21 11:51:20 -07:00
iamtoruk
81b5cda173 feat: add MiniMax-M2.7 and MiniMax-M2.7-highspeed model pricing
Adds FALLBACK_PRICING entries plus display names so MiniMax sessions
show up with the right cost and readable labels when users route MiniMax
through providers like OpenCode. Pricing verified against the live
MiniMax paygo page:

  MiniMax-M2.7           input $0.3/M  output $1.2/M  cache-read $0.06/M  cache-write $0.375/M
  MiniMax-M2.7-highspeed input $0.6/M  output $2.4/M  cache-read $0.06/M  cache-write $0.375/M
2026-04-21 05:50:52 -07:00
iamtoruk
b491a1f590 fix: bucket turns by assistant timestamp, filter at turn level
A turn that straddles midnight (user typed at 23:58, assistant responded
at 00:30) was bucketed and filtered inconsistently across call sites.
parseSessionFile filtered entries by timestamp, producing orphan assistant
calls that groupIntoTurns pushed as turns with empty timestamp. Some
downstream code counted those (buildPeriodData summing project totals)
and other code dropped them (renderStatusBar's empty-timestamp skip).

The menubar showed today = $32 while the terminal status showed today = $27
for the same dataset; each was internally consistent but used a different
turn-bucket rule.

Fix both: parseSessionFile now builds all turns first, then filters each
turn by its first assistant call timestamp (the moment cost was incurred).
renderStatusBar buckets the same way. day-aggregator.ts already bucketed
on assistant time, so it is now consistent too.

Net effect: a turn is counted in the day the API call actually ran in.
2026-04-21 04:40:44 -07:00