Commit graph

572 commits

Author SHA1 Message Date
iamtoruk
f92949c081 docs(pricing): bound the codex repricing drift in verify:upgrade, fix stale comments
The COST_CHANGED_BY_DESIGN carve-out in compare.mjs left codex cost entirely
unasserted after #1075/#1078. It now requires the upgraded cost to be strictly
lower than baseline and within 25% of it, and the row verdict says "repriced"
instead of the misleading "identical (cost N% drift)".

grok.ts's comment on the reasoning/output split still claimed provider-side
splitting was the repo's only mechanism; it now also names
billableOutputTokens/REASONING_INCLUDED_IN_OUTPUT (models.ts), which is the
other half since #1078. usage-aggregator.ts's "folds reasoning into output"
comment was true pre-#1078 but is backwards for codex now (reasoning is
already inside output, not added to it).

Test exemplars for the "reasoning is additive" case used hermes, whose
upstream is OpenAI-shaped and may not stay a safe example; swapped to gemini,
which documents "thoughts" as genuinely separate output.

CHANGELOG's #1075 entry gets one line noting days whose codex transcripts
have aged out keep their pre-fix totals via the daily-cache never-lose guard,
matching the disclosure already given for #1040.
2026-08-21 12:49:05 -07:00
iamtoruk
7876c8e9d7 fix(pricing): version the pricing cache and drop codex-credits' dead reasoning param
The pricing cache written to disk had no schema version, so a cache written
by a pre-#1078 binary lacked cacheWriteCostIsExplicit on every entry. Reading
it back resolved the missing key to undefined (falsy), silently reintroducing
the surcharge-fabrication bug #1078 killed for up to CACHE_TTL_MS after an
upgrade. loadCachedPricing now rejects any cache whose version doesn't match
the current schema instead of reading it verbatim.

codexCredits() still accepted an optional reasoningTokens param that added it
to output - the exact double-count #1078 removed from every real caller. The
only caller never passed it; deleted it so it can't be reintroduced by
accident.

parser.ts's activeGeneratedTokens fallback went through billableOutputTokens
in #1078, but codex is the only caller of activeDurationMs/activeGeneratedTokens
and always sets both together, so the fallback branch is unreachable for it.
Reverted to reduce diff noise.
2026-08-21 12:48:55 -07:00
iamtoruk
fda7e8024d fix(codex): stop double-billing reasoning output, price cache writes at the explicit rate only
Reasoning tokens are a subset of output_tokens for OpenAI models, not an
extra bucket: on a 1,396-rollout corpus all 134,316 token_count events
carrying a total satisfy input + output == total. CodeBurn added
reasoning_output_tokens on top when pricing a codex call, in the
cache-rehydration re-price, and in the models/audit display sums. That
overstated codex cost by $166.03 (3.5%) and displayed output tokens by
34.6% on that corpus. Both cost sites and the display sums now go through
one shared billableOutputTokens() so a cold parse and a warm read cannot
drift apart.

cache_write_input_tokens (codex PR #33454) was never read and
cacheCreationInputTokens was hardcoded to 0. It is now carved out of the
uncached-input bucket and clamped to it, but routed to the cache-write
bucket ONLY when the pricing source publishes an explicit cache-write rate.
buildCosts fabricates 1.25x input when a source omits one, which is correct
for Anthropic and would have invented a surcharge OpenAI never charged on
gpt-5.5 / 5.4 / 5.3-codex / gpt-5. ModelCosts now carries
cacheWriteCostIsExplicit so that distinction survives getModelCosts.

A cost change invalidates persisted output: codex-results.json v10 -> v11
(stores costUSD verbatim), the codex parse version moves (the token-bucket
change does not self-heal on read), and the daily cache goes 20 -> 23 (21 is
claimed by the #946 landing branch and 22 by PR #1056). The upgrade-path
corpus asserts codex tokens and calls exactly and reports the repricing.

Closes #1075
2026-08-21 11:50:23 -07:00
Aditya Vikram Singh
094f9f1d43 test: consume runtime CLEARED/REDIRECTED arrays in the isolation guard
Extra High MERGE AFTER FIX: source-scraping the setup file treated a
comment containing 'HERMES_HOME' as isolation. The lists now live in a
side-effect-free module that applyIsolation and the declaration test
both import. Comment-only sabotage fails with hermes:HERMES_HOME.
2026-08-21 18:58:21 +05:30
Aditya Vikram Singh
f55f98726d test: isolate provider-home env vars so developer shells cannot leak sessions
HERMES_HOME and eight sibling PROVIDER_ENV_VARS data-dir overrides were
fingerprinted for cache invalidation but never CLEARED by the vitest
setup file, so a Hermes-shell laptop parsed real sessions in fixtures.
A static guard fails closed when the map grows another undeclared home.
2026-08-21 18:45:15 +05:30
Resham Joshi
f0c6e58008
Merge pull request #1049 from avs-io/fix/optimize-provider-remediation-copy
fix(optimize): scope remediation copy to --provider
2026-08-21 05:23:03 -07:00
Aditya Vikram Singh
c12c1c6267 fix(optimize): do not claim a session scan for non-Claude providers
Maintainer review on #1049: empty-state copy under --provider codex
named detectors that scanSessions never ran. Say the session-scan
detectors do not cover that provider yet. Drop the dead TUI provider
threading; optimize view is still gated to all|claude.
2026-08-21 15:58:05 +05:30
Resham Joshi
ed02748063
Merge pull request #1055 from avs-io/fix/1045-session-meta-depth
fix(codex): read session_meta string fields at payload depth 1
2026-08-21 02:53:14 -07:00
iamtoruk
577ff0b9ee test: skip the live Frankfurter fetch so FX moves can't shift assertions
Mirrors the pricing guard: CODEBURN_FX_NO_FETCH short-circuits getExchangeRate
after the cache read and before the fetch, returning the same USD-equivalent
rate an unreachable network already yields. Tests that seed their own
exchange-rate.json (serve-stdio's EUR case) keep working unchanged.
2026-08-21 02:23:41 -07:00
iamtoruk
4866332b91 test: price off the bundled snapshot so upstream reprices can't turn tests red
loadPricing() fetched the live LiteLLM table during tests and live data wins
over the bundled snapshot, so DeepSeek v4 assertions went red when upstream
dropped the off-peak discount. CODEBURN_PRICING_SNAPSHOT_ONLY skips the fetch;
env-isolation sets it for the whole suite.
2026-08-21 02:21:31 -07:00
Aditya Vikram Singh
497f655623 fix(codex): read session_meta string fields at payload depth 1
#1040 fixed nested provenance.model. The compact Buffer path still
took the first cwd/session_id/originator/name anywhere in the payload.
Same depth-1 window for every session_meta string field. Bump parse
fingerprint and CODEX_CACHE_VERSION so present sources re-parse.
2026-08-20 08:52:30 +05:30
iamtoruk
f9f79b0660 Merge remote-tracking branch 'origin/main' into pr1040-rebase
# Conflicts:
#	CHANGELOG.md
2026-08-19 13:18:09 -07:00
Resham Joshi
7862aabd47
Merge pull request #1039 from avs-io/fix/hermes-glm53-pr-links
fix(hermes): unwrap known routers; fail closed on unknown models
2026-08-19 12:29:21 -07:00
iamtoruk
fec3519ff1 Merge remote-tracking branch 'origin/main' into pr1041-rebase
# Conflicts:
#	CHANGELOG.md
2026-08-19 12:08:25 -07:00
iamtoruk
8d93a6b40a Merge remote-tracking branch 'origin/main' into pr1040-rebase
# Conflicts:
#	CHANGELOG.md
2026-08-19 12:07:57 -07:00
Aditya Vikram Singh
8b79927d29 fix(optimize): preserve Claude copy and use provider display names
Extra High on #1049: keep the shipped Claude Code empty-state and
TUI header strings; resolve agent nouns from Provider.displayName;
cover all five cross-provider detector labels.
2026-08-20 00:37:41 +05:30
iamtoruk
02059c961c Merge remote-tracking branch 'origin/main' into pr1039-rebase
# Conflicts:
#	src/models.ts
2026-08-19 12:07:02 -07:00
Resham Joshi
cab937c2c7
Merge pull request #1038 from avs-io/fix/tui-resize-coalesce
fix(tui): coalesce resize bursts without dropping updates
2026-08-19 12:05:03 -07:00
Aditya Vikram Singh
fb477f9916 fix(optimize): scope remediation copy to --provider
Cross-provider optimize findings and destination headers were
Claude-hardcoded after #1002 already scoped the detectors.
2026-08-20 00:20:27 +05:30
iamtoruk
bb5b71dff1 feat(models): name the MiMo v2 Flash row
It was the only MiMo row still rendering as its raw slug next to
"MiMo v2.5" and "MiMo v2.5 Pro". SORTED_SHORT_NAMES is longest-first, so
the two v2.5 entries keep their own labels.
2026-08-19 11:33:13 -07:00
iamtoruk
9752f02636 fix(hermes): memoize the origin lookup; cover PR links end to end
githubOwnerRepoFromRoot re-read .git/config once per session, so every
session in the same repo paid for the same two syscalls. Memoize it per repo
root for the life of the process.

The prLinks plumbing from a provider call through the session cache into the
session summary had no test above the provider boundary; add one that runs
the real parseAllSessions pipeline against a temp HERMES_HOME. It fails
against the pre-change parser.
2026-08-19 11:31:40 -07:00
iamtoruk
e6cac54661 fix(models): derive the vendor-namespace allowlist from the pricing catalog
The hand-written KNOWN_NAMESPACES set dropped pricing for vendor prefixes
LiteLLM itself indexes: x-ai/, nousresearch/, zhipu/, litellm_proxy/ and
openai_like/ all priced on main and went unpriced here. Derive the set from
the loaded pricing keys instead, so a vendor the catalog knows is never lost
to a stale list, and keep only the spellings no catalog carries as explicit
extras: the routing wrappers, the client-side kimi/ and mimo/ prefixes, and
the litellm_proxy/ + openai_like/ routes. Local runners are excluded on
purpose, so an unlisted ollama tag cannot strip down to a priced cloud row.

xiaomi/ stops being a routing wrapper: it is the namespace LiteLLM prices
MiMo under, and BUILTIN_ALIASES maps the bare MiMo ids INTO it, so peeling
pulled against the alias. It stays known via the derived set.

Also: a user price override for a bare id now wins over the catalog row a
routed spelling of it would otherwise hit, and a namespaced GLM-5.3 is no
longer LABELLED GLM-5.2 by the sibling alias it prices through.

Tests assert the allowlist through a price override on a synthetic id, so
they cannot rot with the snapshot; the glm-5.4 assertion that pinned on the
snapshot NOT carrying a model is dropped.
2026-08-19 11:31:34 -07:00
iamtoruk
911bd3f486 fix(plans): say monthly budget, not calendar month, and fit 80 columns
The budget window comes from computePeriodFromResetDay, which builds an
anniversary period from plan.resetDay (1-28, settable per plan with
`codeburn plan set --reset-day`). "Calendar-month budget" and "Next
calendar reset" are therefore wrong for anyone who moved the reset day,
which is the same class of inaccuracy this change set exists to remove.
Say "budget" and "Next budget reset" instead, and use one wording across
the TUI and the desktop cards.

Both TUI lines truncate end-first at the terminal width. The headline had
grown past the point where an 80-column terminal still showed the
percentage, so it drops "vs ... /mo" for "/ $300.00 budget", and the
status line drops the clause repeating "budget" from the headline. At 80
columns the longest label (custom plans carry their provider) now fits
the percentage, and the status line still shows the projection.
2026-08-19 11:30:07 -07:00
iamtoruk
52ceceb211 cache: re-derive the daily rollup for the codex session_meta model fix
The codex parse version and CODEX_CACHE_VERSION bumps in #1040 make codex
sessions re-parse, but the daily cache has no per-provider invalidation, so
every day already finalized keeps its old per-model rows - and usage-aggregator
serves every day before today from that cache, with ten-year retention. Raise
DAILY_CACHE_VERSION and MIN_SUPPORTED_VERSION to 20 so history re-derives once
off the warm session cache.

The re-derivation test now seeds v19, the last shipped version, so it models the
real 19 -> 20 path, and the upgrade-path check expects daily-cache.v20.json.

Measured on a real 110-day cache: no day lost value, none disappeared, 100 came
back identical, and 9 grok days rose by $19.80 in total from rollups an earlier
parse change had left stale. Every codex model row was unchanged - that corpus
predates the provenance field the fix corrects.
2026-08-19 11:28:40 -07:00
iamtoruk
ef040a4c4d test(models): pin the shipped MiMo v2 Flash crash; name the base 2.5 row
The `mimo-v2-flash -> xiaomi/mimo-v2-flash` alias shipped before this
branch and already cycled through display-name resolution, so
getShortModelName threw RangeError on every real MiMo v2 Flash session.
The new cycle-safe resolver fixes it, but nothing pinned the ids that
actually crashed in production: cover the four spellings found in a real
session cache, including the unnamespaced `mimo/mimo-v2-flash`.

Add the base `mimo-v2.5` display name so the row reads next to
"MiMo v2.5 Pro" instead of showing a raw slug; SORTED_SHORT_NAMES is
longest-first, so the Pro tier still wins its own entry.
2026-08-19 11:26:22 -07:00
Aditya Vikram Singh
827a41241c fix(models): keep getShortModelName unary for Array.map
CI typecheck failed: sessions-report maps getShortModelName, and
the Extra High cycle Set was a second parameter. Array.map fed
the index as `seen`.

Cycle tracking stays on an internal helper. Display and alias
behavior unchanged. No second Extra High.
2026-08-19 21:27:13 +05:30
Aditya Vikram Singh
d5ced78595 fix(models): cycle-safe short names; keep user-alias display
Extra High MERGE AFTER FIX on d3f86f5. mimo-v2.5 aliased to
xiaomi/mimo-v2.5 then last-segment recursed forever. Looking up
SHORT_NAMES before resolveAlias also froze user remaps of known
ids (gpt-4o still displayed as GPT-4o).

Follow user aliases first. Break strip→alias→leaf cycles.
Do not invent a Kimi rate. Do not paper over this with a
mimo-v2.5 SHORT_NAMES row.
2026-08-19 21:10:33 +05:30
Aditya Vikram Singh
3e93741f6f fix(hermes): require origin identity and fail-closed fences 2026-08-19 20:50:19 +05:30
Aditya Vikram Singh
d3f86f5d16 fix(models): alias bare MiMo 2.5 ids to the LiteLLM Xiaomi rows
Hermes and token-plan sessions store mimo-v2.5-pro. The snapshot
row is xiaomi/mimo-v2.5-pro. Same class as the existing
mimo-v2-flash alias. No invented rate.

Looking up the display name on the stripped leaf before following a
pricing alias, so cline-pass/mimo-v2.5-pro cannot recurse
strip → alias → last-segment forever.
2026-08-19 20:44:18 +05:30
Aditya Vikram Singh
fae4db0008 fix(hermes): restore z-ai pricing; require owner/repo; reject UNC
Extra High held 91754c5. z-ai/glm-5.2 went unpriced (Cline's real
vendor spelling). Forward-slash UNC became a workspace on POSIX.
PR matching used basename, so evil/codeburn collided. Tilde fences
were still scanned.

Add z-ai to known namespaces. Reject // UNC on POSIX. Attribute
PRs only when origin yields owner/repo. Strip ``` and ~~~ fences.
Bump Hermes parse version to v4.
2026-08-19 20:32:51 +05:30
Aditya Vikram Singh
91754c5f54 fix(hermes): fail closed on unknown namespaces, relative cwd, fenced PRs
Extra High held #1039 again. An unknown provider/model still became a
bare-model price via getCanonicalName's first-segment strip. Relative
cwd values like '.' could inherit the invoking repo. PR scrape treated
fenced dumps and other repos as attribution.

Peel only known vendor/router namespaces. Require an absolute
platform path before a Hermes cwd is a workspace. Ignore fenced URLs
and, when a git root exists, keep only that repo's pull links.
Bump the Hermes parse version so old cache rows reparse.
2026-08-19 20:19:29 +05:30
Aditya Vikram Singh
9646890638 fix(plans): call sticker-price bars a budget, not live quota
SuperGrok Heavy is a $300/mo preset compared to parsed API-equivalent
spend on a calendar month. CodexBar's live weekly window is a different
class. TUI and desktop copy now say budget / calendar month and deny
a live provider window. No Grok Connect is invented.
2026-08-19 20:07:02 +05:30
Aditya Vikram Singh
10fdec6037 fix(hermes): fail closed on unknown GLM, namespaces, and ACP identity
Extra High review held #1039. Stop pricing every future glm-5.x as 5.2,
stop collapsing unknown provider/org/model trees onto a priced leaf, and
stop calling ACP Buzz — source=acp is a transport and the DB has no
client field. Strip trailing punctuation on PR URLs and bump the Hermes
parse version so old cache rows reparse.
2026-08-19 19:42:42 +05:30
Aditya Vikram Singh
66a776acfa fix(hermes): treat routing prefixes and session surfaces as classes
Price any OmniRoute/Cline/cmd/antigravity wrapper by peeling prefixes
and, for GLM 5.x, falling back to the newest priced sibling. Do not
require a new alias per model id.

Classify Hermes sessions by surface and workspace: ACP is the Buzz
app; project comes from git root or a real cwd, never $HOME or a
profile name.
2026-08-19 18:46:11 +05:30
Aditya Vikram Singh
0abbbd1b4a fix(hermes): only attribute PRs mentioned by the user or assistant
Tool dumps (gh pr list, changelog grep) were smearing one session
across dozens of pull URLs. Capture links from user and assistant
text only.
2026-08-19 18:28:07 +05:30
Aditya Vikram Singh
f97d084109 fix(models): price gateway-prefixed glm-5.3 ids
Hermes stores Cline Pass as cp/cline-pass/glm-5.3. One prefix strip
leaves cline-pass/glm-5.3, which missed the bare glm-5.3 alias and
stayed $0. Price the last path segment through the same aliases.
2026-08-19 18:20:01 +05:30
Tim De Pauw
1d36f444d7
Fix Codex model attribution after session metadata
Limit session_meta model extraction to direct payload fields so nested provenance metadata cannot overwrite turn_context. Invalidate cached parses and cover model switches with a regression test.
2026-08-19 14:49:04 +02:00
Aditya Vikram Singh
0658308448 fix(hermes): price glm-5.3, label default as hermes, capture PR links
GLM-5.3 is missing from LiteLLM, so Hermes/Cline sessions priced at $0.
Alias it to the glm-5p2 sibling and keep the display name GLM-5.3.

The root Hermes profile was shown as project "default". Use "hermes"
for that profile only; named profiles and cwd inference are unchanged.

Scan Hermes transcripts for https GitHub pull URLs and attach them so
the Pull requests page can attribute those sessions.
2026-08-19 18:06:51 +05:30
Aditya Vikram Singh
ad406cf10f fix(tui): coalesce resize bursts without dropping updates
Hold Ink stdout columns/rows frozen during a SIGWINCH burst and emit
one settled resize, then rerender. Do not intercept writes, so a
mid-burst state update still paints even when net size is unchanged.

Fixes #977.
2026-08-19 13:48:50 +05:30
iamtoruk
3093887a14 Merge remote-tracking branch 'origin/main' into fix/scoped-save-republish
# Conflicts:
#	CHANGELOG.md
2026-08-18 12:48:02 -07:00
iamtoruk
ed9057f230 Merge remote-tracking branch 'origin/main' into fix/scoped-save-republish
# Conflicts:
#	CHANGELOG.md
2026-08-18 12:43:52 -07:00
iamtoruk
10ceb467a8 Merge remote-tracking branch 'origin/main' into fix/pre-release-hardening
# Conflicts:
#	CHANGELOG.md
2026-08-18 12:43:39 -07:00
iamtoruk
ab98a04c51 fix(cache): stop republishing month shards a scoped run never read
A date-ranged query loads only the months its range can report on, so a
file in an out-of-range month has no visible cache entry: the reconcile
re-parses it and writes the identical entry back, which marks a bucket
this run never loaded dirty. The save then merged and republished that
month under a fresh nonce name on every run, with byte-identical
content.

A merge into an unloaded month that neither adds, changes nor removes an
entry now keeps the published shard.

Fixes #1032
2026-08-18 12:29:07 -07:00
iamtoruk
e9d922ca2d fix(daily-cache): keep history for days whose transcripts partly aged out
The never-lose contract carried a (day, provider) slice forward only when
the re-derivation found nothing for it. Transcripts expire per file, so a
mostly-aged-out day still gets a few turns from surviving later files: the
fresh slice came back non-empty but truncated and replaced the full cached
one (a real cache lost $2,765.75 / 19,209 calls / 520 sessions over 13 days
on the 17 -> 19 upgrade).

A fresh slice now replaces a settled baseline slice only when it carries at
least as many calls. Comparison is on calls alone - cost and tokens are
re-priced accounting on the same evidence, and session counts drift down on
healthy days. Days inside a 7-day settle window stay authoritative. The
tz-change re-derive gets the exact form of the rule: the subtraction residual
is added on top of a data-carrying fresh slice instead of being dropped. The
cross-file adoption union is unchanged.
2026-08-18 12:23:29 -07:00
iamtoruk
37796a568e models: distinguish grok-4.5-build from grok-4.5 in reports
Reports bucket rows by model id and label them afterwards, so the two ids
collapsing onto one display name printed what looked like the same row twice
with different numbers. Give the variant its own SHORT_NAMES entry, which the
longest-first match picks over the grok-4.5 prefix. Display only: ids are
untouched, so nothing re-parses and no cost moves.

Fixed in the shared table rather than the grok provider so the menubar and
model-breakdown, which call getShortModelName directly, get it too.

Fixes #1029.
2026-08-18 12:01:12 -07:00
iamtoruk
39075edd50 parser: keep promptSource on lines over 32 KB
parseLargeJsonl dropped promptSource for exactly the lines SDK-generated
prompts live on, so the recurring-context detector regex-scanned the ends of
the raw line for it. Add the field to LARGE_ROOT_FIELDS (tiny scalar,
add-only, isSidechain already there) and delete the workaround: it read only
2 KB from each end, so a flag further in was missed.

No cache change: optimize scans the raw JSONL each run, so promptSource never
has to persist on CachedFile.

Fixes #1030. With #994 this closes #1023.
2026-08-18 12:00:29 -07:00
iamtoruk
087656bacc cache: CODEBURN_CACHE_SCOPE=all forces a full shard read
The month-scoped load a ranged query takes is a behaviour change on a warm
cache with no way back except deleting it. Drop the scope in loadCache, the
one place every caller (including the resident serve) routes through, so a
suspect scoped read can be compared against a full one in place.

Read policy only: deliberately not in PROVIDER_ENV_VARS, so setting or
unsetting it invalidates nothing.
2026-08-18 11:59:21 -07:00
Resham Joshi
564c618829
Merge pull request #1014 from ozymandiashh/fix/997-session-legend-titles
fix(dash): lead the session legend with the session title
2026-08-18 11:49:20 -07:00
iamtoruk
ca3b692acb Merge remote-tracking branch 'origin/main' into pr1014-rebase
# Conflicts:
#	CHANGELOG.md
2026-08-18 11:42:05 -07:00
iamtoruk
cfc0abb50f Merge remote-tracking branch 'origin/main' into pr1017-rebase
# Conflicts:
#	CHANGELOG.md
2026-08-18 11:41:52 -07:00