Commit graph

323 commits

Author SHA1 Message Date
AgentSeal
d04350f8c5 prerelease: quota NaN guard, 10-year daily retention, test date bomb
Three review leftovers plus one inconsistency found while fixing them.

computePace returns undefined for a non-finite usedFraction instead of
letting NaN sail through the clamp into every pace field.

Daily-cache retention goes from 730 days to 3650: the cache is the only
durable record of carried days and lifetime now reads from it, so the
2-year prune would have replayed the lost-history bug in slow motion at
that horizon. Measured cost stays small (~2.3 MB / ~11 ms parse per 730
dense days).

The vercel-gateway dashboard-range test pins a relative day instead of
2026-06-01, which would have started failing once the rolling 6-month
window moved past it around December.
2026-07-20 16:37:13 +02:00
Resham Joshi
db8ef9b466
insights: close the post-merge review findings on workflow intelligence (#763)
Some checks are pending
CI / semgrep (push) Waiting to run
Five verified findings from the #756 review, each with a regression test:

Corrections now count only follow-up prompts. The session-opening prompt
cannot be correcting this assistant, however correction-shaped its task
text reads, and the revert pattern no longer matches ordinary 'revert
the <noun>' requests (undo was already guarded; the doc claimed both
were).

Time-to-first-edit returns null when the first edit's timestamp is
unparseable instead of silently measuring to the first parseable one.

File churn normalizes backslashed paths before keying, relativizing, and
basenaming. Previously the payload's basename-only privacy redaction was
a no-op on Windows and shipped the full absolute path, username
included; mixed separators also split one file across churn entries.

Pricing coverage excludes deliberately-free models (local-looking,
local-savings mapped, zero-rate overrides) from the denominator via the
same exclusions findUnpricedModels applies, so a mostly-ollama user no
longer reads high coverage while every cost-bearing call is unpriced.
An absent coverage value now reaches the payload as null, never as a
fabricated 100%.

Also shares the one-shot minimum-edit-turns threshold between
worstOneShotCategory and the coaching gate so they cannot drift.
2026-07-20 07:26:08 -07:00
iamtoruk
dea61bad4e fix(report): CLI totals read the durable daily cache, matching the menubar exactly 2026-07-20 06:33:56 -07:00
Resham Joshi
ba56ad72f5
Merge pull request #758 from getagentseal/feat/rich-session-capture
feat(capture): parse-time capture of branch, LOC deltas, interruptions, titles, PR links
2026-07-20 06:03:03 -07:00
iamtoruk
8ced2ed47f feat(capture): parse-time capture of branch, LOC deltas, interruptions, titles, PR links 2026-07-20 05:56:38 -07:00
Resham Joshi
f32c00d2b1
feat(insights): workflow intelligence (corrections, time-to-first-edit, file churn, coverage) + dash wire-up (#756)
* feat(insights): workflow intelligence (corrections, time-to-first-edit, file churn, coverage) + dash wire-up

* menubar: propagate workflow-intelligence fields onto the cache-backed headline

The carry-forward headline (#755) serves totals from the durable day set
and merges only session-derived fields from the scan. The new workflow,
topReworkedFiles, and pricingCoverage fields were computed by the scan
but never propagated, so they silently defaulted on the all-provider
path (pricingCoverage rendered a dishonest 1.0). Propagate them like the
other scan-only fields and pin it with a carried-headline regression
test.
2026-07-20 05:47:46 -07:00
Resham Joshi
e99ee4bf37
doctor: surface Claude Code transcript retention (#757)
Claude Code deletes session transcripts after cleanupPeriodDays (default
30) at startup. Daily totals survive in the daily cache, but per-session
detail older than the cutoff is unrecoverable, and most users learn this
only after losing months of history. Doctor now reports the effective
retention and, below one year, warns with the exact settings.json edit
that preserves transcripts.
2026-07-20 05:25:12 -07:00
Monish Khandelwal
380be66b4f
feat(cli): add lifetime period for CLI and dashboard (#753)
Re-apply the CLI/dashboard Lifetime split from #391/#416 on current main.

- add lifetime as a first-class period (all stays last 6 months)
- wire period through cli-date, dashboard key 6, and CLI commands
- refresh cli-date and cli-json-daily regression coverage

Part of #389. Supersedes #416.
2026-07-20 05:24:33 -07:00
Aditya Vikram Singh
b8435073ee
quota: provider-agnostic window model with provenance, merge rule, and linear pace (#725 Part 1) (#740)
Co-authored-by: Aditya Vikram Singh <247195684+avs-io@users.noreply.github.com>
2026-07-20 05:21:39 -07:00
ihearttokyo
92b2b5ef39
fix(dashboard): Bound Daily Activity scans (#727)
Reuse one concrete six-month scan for interactive history and selected-period totals. This restores network-backed providers, removes concurrent refresh scans, and keeps non-interactive reports scoped to their requested period.
2026-07-20 05:21:35 -07:00
Resham Joshi
7f2739e130
daily-cache: never lose history the sources can no longer re-derive (v14) (#755)
* daily-cache: never lose history the sources can no longer re-derive (v14)

Session files are ephemeral (Claude Code deletes transcripts after ~30
days), so a cached day whose sources are gone exists nowhere else. Every
invalidation path (schema bump, savings-config change, timezone change,
incomplete-hydration retry) used to discard all cached days and re-derive
from surviving sources, silently truncating history to the source
retention window. Five bumps between June 22 and July 16 erased
everything before April 24 on a machine with usage since March.

Invalidations now re-derive what they can and carry forward every
(day, provider) slice they cannot, marked 'carried'. Loading a missing or
corrupt cache file adopts days from every older daily-cache file in the
cache dir (legacy, versioned, .bak copies, orphaned .tmp) as a union per
(day, provider), higher schema version winning per pair. Provider slices
now store the full per-provider breakdown (tokens, models, categories,
sessions) so carry-forwards stay exact across future rebuilds.

Merge rules hardened by adversarial review: opaque pre-v5 days (totals
without provider slices) merge all-or-nothing so partial parses cannot
double-count into them; zero-data placeholder slices neither block nor
lose carried data (session counts deduplicated by max); a partial parse
never overwrites finalized baseline slices, only fills gaps; adoption
purges today/future entries, applies retention, and clamps a stale
lastComputedDate so a purged day cannot be skipped forever.

Verified end-to-end against a copy of a real cache dir: the rebuilt v14
cache holds every (day, provider) pair present in any older cache file,
including Claude days from early April that the July rebuilds had
dropped.

* daily-cache: per-project daily rollups in the durable record (v15)

Project history previously lived only in the session layer, so it faded
with the source files even though day totals now survive. Days and
provider slices carry a projects breakdown (cost/calls/savings/sessions
per project) filled by the day aggregator and folded through carry
merges, making the By Project dimension as durable as models and
categories. Days recorded before v15 keep their totals with no project
split; nothing can reconstruct one once sources are gone.

This is the first bump to ride the v14 carry-forward: the v14 cache is
adopted losslessly and only source-backed days re-derive (verified on a
real cache dir: identical totals, zero lost pairs, project splits on
every derivable day).

Hardening from adversarial review: placeholder-aware project session
dedup so totals reconcile; migrateDays sanitizes provider slices and
nested projects from foreign caches; all foreign-keyed map access in the
merge path uses hasOwn reads and defineProperty writes, closing a real
prototype-pollution path a regression test caught when a cache key is
named __proto__.

* menubar: serve headline totals and projects from the durable day set

Review finding on this PR: the all-provider headline built cache-backed
totals and then replaced them wholesale with a rebuild from the
surviving-session parse, so current.cost/calls, the models table, and
topProjects stayed truncated to the source-retention window even though
history.daily carried the full record. The replacement existed only to
keep the estimated-cost markers alive.

The cache-backed period data is now the authority; the scan contributes
exactly what day entries lack: estimated-cost markers, unpriced-model
detection, per-session drill-downs, and a fresher project path. Project
totals come from the same day set as the headline, with ProjectDayStats
gaining a path so carried-only projects still display a friendly name.
Sessions merge by max: the cache buckets a session on its start day, the
scan counts it on any active day, and both undercount differently.

End-to-end regression test seeds a cache whose only day is carried (no
session files exist) and asserts the headline, models, and topProjects
all reflect it. Verified on a real cache dir: the 6-month headline now
equals the history sum to the cent.

* daily-cache: close residual corrupt-input gaps in v15 ingestion sanitization

v15 sanitizes provider slices and projects at ingestion and guards merge
lookups with Object.hasOwn/setOwn. Three residual gaps versus the
invariant (no JSON-parseable cache content may throw or produce
NaN/garbage), plus one found in review:

- Day-level models/categories were not sanitized: a day with
  models:'bad' survived load and buildPeriodDataFromDays iterated the
  string per-character, yielding NaN in period model totals. Day-level
  maps now reuse the same sanitizers as slice-level maps.
- Map keys shadowing Object.prototype (constructor, toString, ...) still
  entered the cache from foreign files; modelTotals[name] ?? init in
  buildPeriodDataFromDays resolves such a key to the inherited prototype
  member and produces NaN. All sanitized maps now drop these keys at
  ingestion (deliberate tradeoff: such names are reclassified invalid).
- Top-level lastComputedDate was unvalidated; a non-string later hit
  .slice() in the gap-start parse and threw. Kept only when it is a
  YYYY-MM-DD string, else null (forces a plain re-backfill).
- Stale savingsConfigHash comment still described pre-v14 discard
  behavior; reworded to re-derive + carry forward.

3 regression tests, each verified to fail on the pre-fix code. Relevant
suites 71/71 green, tsc clean. Valid-input behavior unchanged except the
prototype-key reclassification above.

---------

Co-authored-by: ozymandiashh <234437643+ozymandiashh@users.noreply.github.com>
2026-07-20 05:21:22 -07:00
Resham Joshi
2312fd2eb0
Merge pull request #751 from ozymandiashh/pr/714-delta
Some checks are pending
CI / semgrep (push) Waiting to run
fix(yield): case-insensitive canonical repo grouping via realpathSync.native
2026-07-19 17:25:39 -07:00
Resham Joshi
49b377616b
Merge pull request #750 from ozymandiashh/pr/747-kimicode
feat(kimicode): Kimi Code CLI provider, parse ~/.kimi-code wire sessions
2026-07-19 16:27:14 -07:00
iamtoruk
56ea38917f perf(parser): single-pass field extraction in the large-line JSONL scanner 2026-07-18 17:23:34 -07:00
ozymandiashh
2c9871b2f2 fix(yield): case-insensitive canonical repo grouping via realpathSync.native
Follow-up to #719, closing the two deltas flagged in the #714 discussion:

- fs.realpathSync preserves the caller's path casing on case-insensitive
  filesystems (macOS APFS, Windows), so the same repo reached through
  differently cased paths produced distinct canonical keys and split into
  separate groups. fs.realpathSync.native returns the on-disk casing, making
  the canonical key truly canonical. Regression test creates a real repo,
  references it through altered casing (guarded to case-insensitive
  filesystems), and asserts one group.

- Documents the accepted residual: a main repo moved on disk after
  'git worktree add' leaves git's linked-worktree metadata stale, so its
  worktrees may not unify; that is git-state staleness, not a grouping bug.
2026-07-19 03:12:03 +03:00
ozymandiashh
2b95aef7e5 feat(kimicode): Kimi Code CLI provider, parse ~/.kimi-code wire sessions (#747)
Adds a provider for Kimi Code CLI (MoonshotAI/kimi-code, the successor of
kimi-cli), reading ~/.kimi-code/sessions/wd_*/session_*/: state.json for
session metadata and per-agent agents/<id>/wire.jsonl event streams.

- Usage from usage.record events: inputOther->input, output->output,
  inputCacheRead->cacheRead, inputCacheCreation->cacheWrite. The store has
  no cost fields, so cost is computed from tokens and flagged estimated.
- Real model attribution: usage.record.model carries the config ALIAS; the
  real model id lives on llm.request events. Rows resolve the alias through
  the alias->model map first, with the nearest preceding request as
  fallback, so aliases never leak into reports.
- Subagent wire files parse as separate sources under one session without
  double counting; multi-turn continuations in one wire.jsonl are one
  session with multiple turns; retry-only failed sessions parse to zero
  usage; malformed JSONL lines are skipped.
- Tool calls feed the tool breakdown; tool state resets at turn boundaries
  so a failed turn cannot bleed its tools into the next row.
- KIMI_CODE_HOME override, eager registration, PROVIDER_ENV_VARS and
  PROVIDER_PARSE_VERSIONS entries, probeRoots for doctor, docs page.
2026-07-19 02:52:37 +03:00
iamtoruk
8326aee4a6 perf(parser): parse appended session files from the cached offset instead of byte 0 2026-07-18 16:26:12 -07:00
Resham Joshi
5a8a1d0fa4
Merge pull request #745 from ozymandiashh/pr/741-skills
fix(classifier): attribute skills to skillBreakdown regardless of turn category
2026-07-18 14:32:52 -07:00
Resham Joshi
906cb2d550
Merge pull request #735 from ozymandiashh/feat/quickdesk-provider
feat(quickdesk): Amazon Quick Desktop provider (~/.quickwork)
2026-07-18 12:57:08 -07:00
ozymandiashh
56e836fa9e fix(classifier): attribute skills to skillBreakdown regardless of turn category
Skill attribution was gated on the turn classifying as 'general', but
classifyByToolPattern checks hasSkill last, so any turn invoking the Skill
tool alongside Read/Bash/Edit/MCP got a real category and its skill was
silently dropped. Skill turns virtually always carry other tools, which is
why skills came back empty in all 25 usage snapshots (#741) while mcpServers
populated.

subCategory is now set for any turn with Skill tool_use; category assignment
is untouched. The claude parse version is bumped so cached session summaries
(which lack skill attribution) re-parse on upgrade.
2026-07-18 22:54:49 +03:00
iamtoruk
b71ec34a21 fix(usage): per-provider snapshot capture, placeholder guard, path-id display fallback 2026-07-18 03:37:42 -07:00
ozymandiashh
bdb1475251 feat(quickdesk): Amazon Quick Desktop provider, parse ~/.quickwork sessions and metrics (#707)
Adds an eager provider for Amazon Quick Desktop (aws.amazon.com/quick/desktop),
which stores local data under ~/.quickwork. Usage source of truth is the
per-day EMF metrics JSONL (Model, InputTokens, OutputTokens, CostUSD,
session_id); real CostUSD passes through the cache like kiro/devin/hermes.
sessions.db (SQLite, read-only) enriches calls with titles, first user
message, and tool names, and provides estimated usage (quickdesk-auto,
costIsEstimated) for sessions that predate metrics coverage. Multi-profile
layout via profiles.json entries plus the migrated legacy root; honors
QUICKWORK_HOME; schema-tolerant (sqlite_master and PRAGMA introspection,
per-line JSONL error isolation). The on-disk schema is reverse-engineered and
documented as such in docs/providers/quickdesk.md.
2026-07-18 00:02:01 +03:00
AVSR Pavan Kumar
b6bf233f46
feat(act): defer-* plan kinds for native deferral config (part 2 of #614) (#631)
* feat(optimize): mcp-deferral-gaps finding family

Add three read-only detectors for MCP tool-def deferral coverage gaps:

- mcp-deferral-off: sessions carrying MCP tool-def overhead with zero
  ToolSearch invocations and no deferred_tools_delta inventory across
  the window. Cause attribution, each with its own message and fix:
  stale ENABLE_TOOL_SEARCH=false (settings env, any scope, or shell
  profile), non-first-party ANTHROPIC_BASE_URL (reported as unknown
  proxy — never assumes capability), Vertex AI config, all observed
  Claude Code versions predating default-on tool search (v2.1.7), or
  a generic deferral-appears-inactive fallback.
- mcp-alwaysload-hygiene: alwaysLoad-pinned servers whose observed
  call rate is below 1 call per 5 sessions (named constant). Notes the
  5s startup-blocking cost of alwaysLoad in the explanation.
- mcp-defer-threshold: ENABLE_TOOL_SEARCH=auto/auto:N overrides whose
  threshold the estimated def volume never reaches, so tools load
  upfront; recommends the tightest auto:N that would defer.

Detection only: no FindingApply payloads, no act-layer changes, no
file mutation. Users without deferral gaps see zero new output —
deferral-active evidence (ToolSearch calls or inventory) suppresses
the findings, and each detector gates on conservative named
thresholds. Config readers take an injectable homeDir (PlanContext
style) for hermetic tests.

Discrepancies vs the design notes, codebase/live-docs win:
- The Claude Code changelog records default-on (2.1.7), not first
  ship, so the version cause uses the default-on boundary.
- Per-tool "anthropic/alwaysLoad" lives in server-served tool _meta,
  not static config; server-level only, limitation documented.
- Live docs confirm auto default threshold 10% of context window and
  the alwaysLoad v2.1.121+ requirement.

Refs getagentseal/codeburn#614

* feat(act): defer-* plan kinds for native deferral config

Wire the mcp-deferral-gaps findings to the act machinery with three
new plan kinds, all through the existing ConfigDocs/runAction path
(journaled, backed up, stale-guarded, dry-run previewable, undo
restores byte-identical files):

- defer-enable: removes a stale ENABLE_TOOL_SEARCH=false from the
  settings env of the scope where the finding recorded it. Refusal
  paths render as manual notes instead of plans: shell-profile lines
  (codeburn only appends marker blocks to shell files, never edits
  user lines), unknown proxies (setting the override blind makes
  requests fail outright on proxies that don't forward tool_reference
  blocks — the note says to verify first), Vertex, and old versions.
  A proxy-verified cause (set by the part-3 verifier) produces a real
  ENABLE_TOOL_SEARCH=true plan in user settings.
- defer-alwaysload: strips alwaysLoad: true from the named servers in
  the exact config files the finding recorded. Gated on an injectable
  installed-version probe (default: claude --version); below v2.1.121,
  unparseable, or probe failure all refuse with a note naming the
  required version. Preview notes the removed up-to-5s startup block.
- defer-threshold: rewrites the auto override to the recommended
  auto:N, or deletes it when the finding says the default already
  defers (removeOverride).

Findings now carry FindingApply payloads (path, scope, cause, servers,
recommended N); detector text is unchanged, so plain optimize renders
byte-identically to before. Every plan links its findingId into the
ActionRecord and states that changes take effect on the next session
(the config is read at Claude Code start).

Discrepancy vs the design notes: no existing version-check helper was
found in guard/ or act/ to reuse, so the detector's parseVersion/
versionPredates are exported and shared instead of adding a parallel
comparator.

Refs getagentseal/codeburn#614

---------

Co-authored-by: AgentSeal <hello@agentseal.org>
2026-07-16 19:01:54 -07:00
iamtoruk
b95cb95ce4 fix(yield): group commit attribution by canonical repo, not launch path
Sessions launched from monorepo subdirectories or separate worktrees
of one repository formed independent attribution groups, each awarding
the same commit (double-counted productive sessions; the duplicate
owner was not even flagged ambiguous). Groups now key on the realpath
of git-common-dir, so all subdirs and worktrees of a repo collapse
into one group while distinct repos stay separate; ambiguity semantics
unchanged, now correctly spanning the whole repo. rev-parse results
cached per directory.

Fixes #713. Regression tests fail on the unfixed code.
2026-07-16 18:58:49 -07:00
ozymandiashh
a55251ce8f
feat(export): subagentType and model on per-record exports; --by-agent follow-ups (#712)
Follow-ups to #710, completing issue #708's second ask plus the two review
notes:

- export: new records.csv with one row per call incl. subagentType and
  model; sessions.csv gains both fields APPENDED after all legacy columns
  so by-index consumers are unaffected; JSON fields additive; formula-
  injection escaping preserved
- models --by-agent: the main-session bucket sentinel becomes '(main)' -
  agentType is free-form text, so a real subagent literally named 'main'
  no longer merges into the main bucket (test-pinned across all formats)
- README: note that the --min-cost 0.01 default can hide sub-cent agent
  buckets; --min-cost 0 shows the complete agent inventory
2026-07-16 16:35:33 -07:00
Resham Joshi
6e66d34582
feat(models): agent x model cross-breakdown (--by-agent) (#710)
Orchestrator setups pin different agents to different models (planner on
Opus, implementers on Sonnet, scouts on Haiku). The dashboard shows
"Skills & Agents" and "By Model" as separate panels, so there was no way
to see which agent drove which model's spend, or catch an agent that
silently switched models. `codeburn models --by-agent` closes that gap:
one row per (provider, model, agent), mirroring the existing --by-task
pattern across the table, markdown, CSV, and JSON renderers.

The agent label comes from each Claude subagent transcript session's own
agentType crossed with that session's model calls, aggregated through the
same assistantCalls path --by-task uses (not modelBreakdown), so cache-tier
dedup, savings, and Codex-credit logic all carry over unchanged. This is
the authoritative pairing; main-session subagentTypes spawn markers are
deliberately not joined to models, since a spawn marker names an agent but
not the model that agent actually ran on.

Ordinary main sessions (no agentType) and every non-Claude provider have
no agent, so their spend buckets under the label "main" rather than being
dropped or fabricated into a false agent. --by-agent is mutually exclusive
with --by-task; passing both errors politely.

Exposing subagentType + per-record model on `codeburn export` (so users
can build custom crosses over the export) is a separate follow-up and is
intentionally out of scope here.
2026-07-16 15:43:58 -07:00
iamtoruk
ae18b1fd91 fix: audit batch — prefetch one-shot, turn-anchored day bucketing, warm-path collapse
- prefetch fires exactly once per provider per session; memo sized to
  detected providers so base keys never evict (was: 12 full-history
  parses every 30s forever from LRU thrash re-triggering the effect)
- day-aggregator buckets whole turns by user-message timestamp,
  matching the report/headline path: trend bars and provider breakdown
  now reconcile exactly (midnight-straddling turns were split);
  DAILY_CACHE_VERSION 13 with an IANA tz guard that rehydrates when
  the cache crosses timezones
- overview's redundant second all-provider scan collapsed (~11% faster
  warm); numbers parity-guarded
- lock takeover cleans stale hydrating.lock; SIGINT/SIGTERM release it
- scanner skips EPERM/EACCES provider dirs (progress shows skipped)
  instead of aborting; onboarding hints Full Disk Access
- rateLimited quota copy render tests; export -f json emits valid
  empty JSON when no data

Root 2023 (+4), app 327 (+3), typechecks clean.
2026-07-16 15:37:03 -07:00
Resham Joshi
426d80806a
Merge pull request #705 from getagentseal/fix/perf-deep
P0: cache completeness + cold-only splash + honest instant switching
2026-07-16 14:05:55 -07:00
iamtoruk
e9655fa03c fix: hydration completeness markers, cold-only splash, honest switching, prefetch
P0: an interrupted cold hydration left partial caches that the
emptiness check read as warm — the daily backfill froze (missing older
days) while session parsing healed gradually (drifting totals),
poisoning every surface sharing the cache. Both caches now carry an
explicit complete marker: partial resumes cold under the hydration
lock; unmarked caches self-heal with one re-hydration. Regression test
seeds the exact frozen artifact and asserts bit-for-bit convergence
with a never-interrupted run.

- splash indexing reveals only on genuinely cold scans (explicit cold
  flag in the progress protocol), never on warm incremental re-parses
- uncached filter switches clear to skeleton instead of showing the
  previous filter's numbers; cached switches paint same-commit;
  background prefetch warms every detected provider after idle
- build stamp (git sha + date) in splash and About ends build ambiguity
- payload parity test: identical payloads on identical cache, and
  payload totals equal the CLI report path
- Sessions empty state keeps the provider filter row; zero savings
  line hidden; TCC usage-description strings + Full Disk Access docs
- warm profile documented: optimize scan ~1.47s dominant (safe
  refactor deferred), parse ~1s, aggregation ~30ms

Root 1848, app 320, typechecks clean.
2026-07-16 14:05:11 -07:00
Aditya Vikram Singh
f2864ff964
yield: single-owner commit attribution, ambiguous bucket, heuristic caveat (#692)
Some checks are pending
CI / semgrep (push) Waiting to run
* yield: single-owner commit attribution, ambiguous bucket, heuristic caveat (#641)

* yield: repo-level single-owner attribution, sharper ambiguous rule, determinism pins

---------

Co-authored-by: Aditya Vikram Singh <247195684+avs-io@users.noreply.github.com>
2026-07-16 13:09:26 -07:00
Aditya Vikram Singh
1b69c83165
parser: dedupe the twin string/Buffer large-JSONL scanners (#698)
* parser: dedupe string/Buffer JSONL scanners into one implementation (#643)

* Remove dead findJsonValueBounds wrapper

---------

Co-authored-by: Aditya Vikram Singh <247195684+avs-io@users.noreply.github.com>
2026-07-16 12:23:06 -07:00
Aditya Vikram Singh
594bc994d0
copilot: extract skill invocations so Skills & Agents populates (#654) (#697)
Co-authored-by: Aditya Vikram Singh <247195684+avs-io@users.noreply.github.com>
2026-07-16 12:02:34 -07:00
Aditya Vikram Singh
35b8f0519e
Extract shared token estimation helper, migrate all CHARS_PER_TOKEN sites (#644) (#677)
Co-authored-by: Aditya Vikram Singh <247195684+avs-io@users.noreply.github.com>
2026-07-16 11:58:22 -07:00
Resham Joshi
4d8d81a714
Merge pull request #699 from getagentseal/feat/cadence-and-coherence
Desktop: refresh cadence, instant switching, cache coherence, telemetry v1
2026-07-16 11:47:48 -07:00
iamtoruk
300e949951 feat(app): refresh cadence, instant switching, cache coherence, telemetry v1
Performance and coherence:
- Settings > General 'Refresh every' (Manual/30s/1m/3m/5m/10m), live via
  RefreshCadenceContext; Manual polls only on demand
- usePolled memoKey LRU: provider/period switches paint the last-good
  result instantly with a switching hairline while refreshing quietly
- quota TTL 5min + honest rate-limited copy on 429 backoff
- version-suffixed cache files (session-cache.v5.json, daily-cache.v12,
  auto-minted on future bumps); legacy files never written or deleted,
  adopted once when versions match: old and new binaries coexist
  without clobbering (field-observed menubar-vs-desktop ping-pong)
- advisory hydration lock: concurrent cold starts share one scan
  (wait-then-read-warm), stale/dead locks self-heal, never a
  correctness gate

Telemetry v1 + onboarding (desktop only, per owner decisions):
- first-launch onboarding (3 feature screens + consent); region-split
  default (EU/EEA/UK/CH off, elsewhere on, unknown off); nothing sends
  before consent completion or while off; dev builds never send
- anonymous install UUID, rotated on opt-out; day-granularity events,
  cost buckets only; whitelisted names; 200-event queue, 5min flush
- Settings > Privacy live toggle replaces the static claim

Zero computed-number changes. App 316/316, root 1805.

Wire contract targets api.codeburn.app/v1/telemetry (Worker follows).
2026-07-16 11:47:05 -07:00
ozymandiashh
057de97360
feat(budget): spend budgets with overview display and scriptable --check (#562)
Ported onto current main after the locale-grouping change in overview.ts;
review follow-ups included:
- explicit note: budgets count the full would-be API cost including
  subscription-proxied spend, consistent with the overview Cost total
- percent display floors the raw value on both surfaces, so 99.6 percent
  renders as 99 percent in warn state and 100 percent appears only when the
  state is over; regression tests cover the boundary on overview and --check
- overview keeps main's en-US locale-grouped formatting and the
  unpriced-model warning block; the budget line renders after it
2026-07-16 11:10:16 -07:00
ozymandiashh
491c9cbfc8
feat: implement act apply-model and report baseline tripwire (#607) (#616)
* feat: implement act apply-model and report baseline tripwire (#607)

* feat: implement act apply-model and report baseline tripwire (#607)

* fix(act): scope model-default tripwire to the applied project

Review fixes for #616:
- modelDefaultRow and the under-20-edit-turns gate now aggregate only the
  target project's sessions (derived from changes[0].path, separators
  normalized before dirname), matching the per-project baseline captured at
  apply time instead of comparing against all projects.
- baseline.candidateModel labels the candidate explicitly, with a
  backward-compatible fallback to metrics key order for existing journals.
- measured model-default rows render a correlation marker instead of a
  formatTokens(0) token claim.
- zero-matching-projects now reports an honest project-not-found note; clean
  rows route through confidenceFor like every other kind.
- tests: tripwire fires on a same-project regression that global aggregation
  would mask (fails on pre-fix code), clean and not-measurable cases,
  Windows-separator journal paths with an excluded masking project.
2026-07-16 11:10:08 -07:00
Aditya Vikram Singh
6cdbf560dc
optimize: bound zero-edit low-worth savings estimate to a recovery fraction (#640) (#691)
Co-authored-by: Aditya Vikram Singh <247195684+avs-io@users.noreply.github.com>
2026-07-16 11:09:44 -07:00
iamtoruk
ded4e4e844 feat(cli): scan progress protocol, incremental cache saves, --no-timeline
- CODEBURN_PROGRESS=1 emits newline-JSON progress on stderr (provider
  list, per-provider start/done, per-file ticks) for GUI first-run
  loaders; plain CLI output untouched
- parseAllSessions saves the session cache atomically every 5s during
  hydration and after each provider group, so an interrupted cold scan
  leaves a warm partial cache instead of nothing
- status --no-timeline skips the granular timeline computation
  (~300-450ms per call) for clients that never render it; default
  unchanged for the menubar
2026-07-16 10:51:18 -07:00
Resham Joshi
3163ccb5c4
fix(report): surface estimated costs distinctly (#688)
* fix(report): surface estimated costs distinctly

Providers that estimate tokens or price (kiro, cursor, warp, copilot,
grok, hermes, codewhale, and the codex proxy path) set costIsEstimated
on their parsed calls, but the flag died at the parser boundary: it was
never carried onto ParsedApiCall or into the session aggregates, so a
figure whose tokens were synthesized from content length rendered with
the same authority as a metered one.

Plumb the truth through, mirroring the savingsUSD/isLocalSavings pattern:
a call-level boolean (ParsedApiCall.isEstimated, CachedCall.isEstimated,
persisted so it survives the session-cache round trip) and an additive
aggregate amount (estimatedCostUSD on the model breakdown, session, and
project totals, plus PeriodData and the menubar payload). The amount is
carried rather than a bare boolean so a row that is mostly metered with a
small estimated slice is not indistinguishable from a fully guessed one.

Display: report (TUI) and overview per-model rows prefix the cost with a
tilde and print one legend line; the MCP tables carry the same marker and
legend, and the machine surfaces (report --json, MCP get_usage, menubar /
web payload) expose estimatedCostUSD. Totals math is unchanged; the flag
is display/metadata only.

Bump PROVIDER_PARSE_VERSIONS for every provider that sets the flag so
already-cached sessions reparse once and pick it up. Copilot is excluded:
it is a durable provider, so changing its env fingerprint would discard
OTel cache entries whose source rows may already be pruned.

Also fix the cross-provider project merge, which summed totalCostUSD but
dropped merged-in projects' totalEstimatedCostUSD, undercounting the
project/period estimated total (the same latent gap still affects
totalSavingsUSD, left untouched here).

* test(parser): pin estimated dollars through the cross-provider merge

The merge fix for dropped totalEstimatedCostUSD was not covered: deleting
the summing line left every test green. Extract the merge into an exported
mergeProjectsByCrossProviderKey (no behavior change) and pin both the
measured-plus-estimated and both-estimated merge cases.

* docs(parser): honest merge-comment scope and load-bearing overwrite note

Re-review nits: the merge doc claimed all additive totals are summed there
while totalSavingsUSD still is not (pre-existing gap, tracked separately)
and totalProxiedCostUSD is re-derived post-merge; say so. Mark the
buildPeriodData overwrite in usage-aggregator as load-bearing for the
estimated marker so nobody optimizes it away trusting the daily cache.
2026-07-16 10:46:21 -07:00
Resham Joshi
3dd1d1c793
feat(doctor): per-provider detection diagnostics (#685)
* feat(doctor): add per-provider detection diagnostics command

When a provider reports zero (or a number that looks wrong) there was no way
to see why: whether the tool is not installed, an env override points at the
wrong directory, the data dir is empty, or parsing failed. Users had to file
issues like "OpenCode does not work" with nothing to go on.

`codeburn doctor` closes that gap. For every provider (or one via --provider)
it shows the exact directories/dbs probed with any env override and whether the
path exists, how many session files were discovered, how many of a bounded
sample parsed cleanly, the cached file count and parser version, and a one-line
verdict: OK (n sessions), NOTHING FOUND with a concrete likely cause, or
ERRORS (n parse failures). Output is a human table by default or --json.

The collect logic is a pure function separated from rendering so tests exercise
it without a TTY. It runs fully offline and read-only (never writes caches or
config, and skips the one network provider's parse), and isolates each provider
in its own try/catch so a single thrower becomes an error row instead of
crashing the report. Providers expose their scan roots via an optional
probeRoots() so the exact paths are shown even when zero sessions are found,
reusing each provider's own path resolution rather than duplicating it.

* fix(doctor): make the inert promise actually true

Adversarial re-review reproduced two false guarantees. Cursor's parser
writes its results cache to disk before its first yield, so a doctor run
created ~/.cache/codeburn/cursor-results.json on a clean machine; the
collect pass now sets CODEBURN_SUPPRESS_CACHE_WRITES (restored after) and
the cursor writer honors it. Antigravity's parse probes for a live
language server (spawns ps and lsof, RPCs the IDE when found); doctor now
skips its parse sample the way network providers are skipped, keeping
discovery counts meaningful.

Also stop blaming CODEBURN_CACHE_DIR in NOTHING FOUND hints (it is our
cache location, not a discovery path) and correct the sample-cap comment:
the cap truncates yields, eager parsers still do whole-file work.
2026-07-16 10:38:08 -07:00
Resham Joshi
98051b4263
fix(copilot): bill CLI input and cache tokens from session.shutdown (#684)
* fix(copilot): bill CLI input and cache tokens from session.shutdown

Copilot CLI sessions (~/.copilot/session-state/*/events.jsonl) recorded
only outputTokens, because the parser read assistant.message events and
ignored the session.shutdown rollup. That rollup is the sole on-disk
source of a CLI session's input, cache-read and cache-write tokens, so
cost was underreported (in the reported case 35k+ cache-read tokens were
dropped entirely).

Handle session.shutdown in the CLI (non-transcript) JSONL path and emit
one supplementary call per model from modelMetrics.<model>.usage. The
per-turn assistant.message events keep owning output tokens, tools and
turn structure; the shutdown call contributes only the input and cache
tokens they lack, with output excluded, so no dimension double-counts and
the combined cost equals the full CLI-measured cost. usage.inputTokens is
cache-inclusive (input + cache_read + cache_write), so the cache
components are subtracted to recover the uncached input calculateCost
expects. These are real counts written by the CLI, so costIsEstimated is
false. Sessions without session.shutdown (crashed or still running) keep
the exact previous output-only behavior, and the VS Code, JetBrains and
OTel paths are untouched.

Bump the copilot entry in PROVIDER_PARSE_VERSIONS so already-cached CLI
sessions re-parse and pick up the corrected tokens and cost.

* fix(copilot): never emit an empty-timestamp shutdown call

A session.shutdown without its own timestamp and without sessionStartTime
produced a call with an empty timestamp, which the date-range filters in
parser.ts drop silently, erasing exactly the tokens this feature bills.
Fall back to the last stamped event in the session.

* fix(cache): durable orphans survive provider fingerprint changes

Bumping a durable provider's PROVIDER_PARSE_VERSIONS changed its env
fingerprint, and getOrCreateProviderSection replaced the whole section on
mismatch. For copilot OTel orphans the discarded cache entry is the only
remaining record of pruned spans, so the first post-upgrade run silently
and permanently erased month-to-date history. Carry forward exactly the
entries whose source no longer exists; present sources still drop and
re-parse under the new fingerprint, which is the point of a bump.

Regression test forges a pre-upgrade fingerprint on a pruned-source
orphan and asserts it stays counted and cached across the transition.
2026-07-16 10:37:58 -07:00
Resham Joshi
b7e3bac263
Merge pull request #679 from getagentseal/feat/desktop-app
CodeBurn Desktop: standalone Electron app
2026-07-16 07:54:05 -07:00
iamtoruk
1b600d1c82 Merge origin/main into feat/desktop-app
Resolves menubar-json conflicts: main's granular-history timeline
param unions cleanly with this branch's providerDetails and currency
payload additions; both new tests kept.
2026-07-16 07:52:28 -07:00
kronos
68ad0be18b
fix(antigravity): stamp mtime fallback at emission, tighten path classification (#612)
* feat(antigravity): add support for Antigravity IDE storage on Windows

CodeBurn previously only detected Antigravity CLI usage (.pb files under
.gemini/antigravity/). Antigravity IDE on Windows stores session state in
VSCode-style storage at %APPDATA%\Antigravity IDE\User\globalStorage\state.vscdb,
which was not detected.

Add support for reading Antigravity IDE sessions from the VSCode-style storage:
- Extend CONVERSATION_ROOTS to include APPDATA Antigravity IDE path
- Refine path classification to properly identify IDE vs CLI sessions
- Handle missing per-call timestamps by stamping file mtime as fallback
- Bump CACHE_VERSION to 4 for cache invalidation

Fixes: CodeBurn reports zero usage when Antigravity IDE is actively used.

* fix(antigravity): stamp mtime fallback at emission, tighten path classification

- Apply the mtime fallback to a copy at emission instead of mutating and
  persisting it into the cache, so a later file rewrite can't retro-date a
  session's history to the new mtime. SQLite gen_metadata rows have no real
  per-call time; the RPC path still uses chatStartMetadata.createdAt.
- Drop the unreachable APPDATA classifier branch (discovery only walks the
  ~/.gemini roots; APPDATA state.vscdb holds no token usage). This also removes
  the misroute of base-antigravity paths under an "Antigravity IDE" profile dir.
- Bump CACHE_VERSION to invalidate caches that persisted a synthesized mtime.

* fix(antigravity): stabilize untimestamped call times across DB rewrites

Extract ChatStartMetadata.created_at from proto-encoded data and decode multiple timestamp formats (ISO string, Timestamp submessage, unix varint). Implement assignStableTimestamps() to preserve first-seen timestamps across file rewrites, preventing retro-dating of sessions when .db files are modified. Make conversation roots dynamic (computed per call) to honor environment overrides in tests. Add comprehensive timestamp stability tests verifying that timestamps remain fixed across file mtime changes while respecting date-range filters.

* test(antigravity): assert today range excludes first-seen timestamps

Adds a test case to verify that the 'today' date range filter correctly excludes sessions based on their first-seen timestamp, not file modification time. This ensures that even if a database file is rewritten with a later mtime, sessions with earlier first-seen dates are properly filtered out.

Refs #411
2026-07-16 07:49:54 -07:00
Resham Joshi
4307542b62
feat(claude): account for /advisor escalations under the advisor model (#659)
* feat(claude): account for /advisor escalations under the advisor model

Claude Code's advisor tool (/advisor) escalates hard decisions mid-turn
to a stronger model. Those tokens are recorded inside
message.usage.iterations as advisor_message entries with their own model
and are excluded from the top-level message.usage totals. The parser read
only the top-level totals, so advisor spend was silently dropped and never
attributed to the advisor model.

Emit a separate call per advisor_message iteration priced under the
advisor model, preserve those iterations through entry compaction and the
large-line (>32KB) byte-scanner path, and fold advisor cost into the guard
budget. Bump the claude parser and guard cache versions so existing caches
re-parse and pick up the advisor spend.

* test(guard): pin advisor cost fold in the live budget

The guard's advisor fold in computeSessionUsage had no test; dropping
advisorCost from the line cost left all 30 guard tests green. Pins the
fold (advisor line costs more than the same line without iterations)
and the per-message replace semantics under streaming rewrites.
2026-07-16 07:42:49 -07:00
ihearttokyo
ccd8764e5b
feat(dashboard): make Daily Activity history directly scrollable (#672)
Some checks are pending
CI / semgrep (push) Waiting to run
* feat(dashboard): add inline daily history paging

* refactor(dashboard): make daily activity directly scrollable

* fix(dashboard): keep the empty state for users with no history at all

Scrollable mode kept the dashboard shell up whenever full history exists,
but a truly-new user with zero data anywhere saw a zeroed skeleton instead
of the No usage data message. Extract the decision into showEmptyState so
the boundary is test-pinned: empty period with history renders the shell,
empty everything renders the empty state, and the loading pass never
flashes it.

---------

Co-authored-by: AgentSeal <hello@agentseal.org>
2026-07-16 04:10:08 -07:00
iamtoruk
699ca53979 feat(cli): price-override --list --format json 2026-07-16 03:45:36 -07:00
ozymandiashh
df78e50052
feat: add CodeWhale provider support (#674)
Co-authored-by: AgentSeal <hello@agentseal.org>
2026-07-16 03:12:28 -07:00
ozymandiashh
5d366fb03b
feat(dashboard): add granular session and model timelines (#673)
* feat(dashboard): add granular usage timelines

* fix(dashboard): readable timeline legend and month-scale buckets

Session legend labels now use the real projectPath's last two segments
(app/web) instead of the sanitized project dir, which rendered as an
unreadable -Users-name-... dump and truncated in the legend.

Hourly buckets now cap at 8 days; longer ranges bucket daily. A 30-day
window rendered 720 overlapping hourly spikes, unreadable as a chart.

The browser no longer gives the backend session_other/model_other
aggregate a top-N slot, which rendered two identical Other legend
entries whenever the backend fold out-ranked a real series.

---------

Co-authored-by: AgentSeal <hello@agentseal.org>
2026-07-16 03:04:54 -07:00