@vidoluco's round-6 re-validation: a cache generated by 0.9.20, copied and then
migrated by this branch, dropped exactly ONE call a virgin cache serves -
mai-code-1-flash-picker, 324 input / 91,136 cache read / $0.01, two minutes
after the labelled compaction row in its session. Every other day matched.
Root cause is the durable union merge, not the reconciliation. `compactedAt` is
a capture-only field this branch adds to the shutdown-ROLLUP call, and 0.9.20
already cached that call under the same (deliberately stable) dedup key. The
union appended only UNSEEN keys, so the bump could never reach it. The migrated
cache then ran the compaction-anchored residual math with no anchor - the
pre-anchor behaviour exactly: the interval opens at -Infinity, the
PRE-compaction rows are subtracted from a rollup that never counted them, and
the residual clamps to zero and disappears.
The union now REPLACES a cached call with the freshly-derived one wherever the
key matches. Nothing is deleted: keys the re-read did not produce are exactly
the rows the source has pruned, and those still carry forward untouched with
the old parser's fields. Keys it did produce were just derived from the live
source in this very pass, so the fresh call is by construction the better one.
This is also what makes the round-5 compaction-output fix reach an existing
cache - that row's key is stable too, so append-only would have pinned it at
output 0 forever. Both directions are pinned by (c6).
Two defects from @kelchm's two-machine validation of #946.
1. The daily-cache migration carried the copilot slice of every existing day
entry verbatim instead of re-deriving it, so `overview` on a migrated cache
kept serving pre-store numbers (2,980,804 tokens for a day whose fresh
derivation is 74,811,412; 21,608 calls vs 16,326) while export/models/audit
served the corrected ones off the same cache.
Root cause is the partial-survival guard (e9d922ca), not the migration: it
keeps a settled baseline slice whenever the fresh derivation reports FEWER
calls, reading that as aged-out sources. This change's whole point is that
copilot's supplementary accounting calls stop counting as api calls, so
every store-era day shrinks in calls and got pinned to its pre-store value.
The guard's own TRADE-OFF note predicted exactly this case.
A migration from an older cache version now grants copilot ONE
guarded-shrink-exempt re-derivation (`pendingRederive`), spent by the first
COMPLETE parse. It fires only where that parse actually produced a slice for
the (day, provider), so a day whose sources are gone still carries forward
whole - never-lose is unchanged in both directions - and no other provider
is exempt.
The daily cache moves 21 -> 25: 21-24 landed on main during validation, and
only a number the validators' own daily-cache.v21.json cannot claim gets
their stale carried slices re-derived.
2. The `initiator='compaction'` store row was ingested with outputTokens 0. Its
output has no assistant.message anywhere in events.jsonl, so that row is the
only place those tokens exist - dropping them was the sole token discrepancy
(-3,085) across a 30-session store-matched comparison. It is now read (on the
same optional-select rung as `initiator`, which is what identifies the row),
counted and priced; every other row keeps output 0 because a per-turn call
owns it. The copilot parse version moves to session-store-v3 so cached v2
rows re-parse; the dedup key is deliberately unchanged.
Settle window stays at 24 h - the three-machine evidence for tightening it is
recorded next to the constant, but shortening it is a product call.
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.
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.
Merge resolution
- daily-cache: main shipped v20 with #1040, so 21 is now the first FREE number
rather than one dodging an unmerged head. Both version notes kept, ours on
top. The re-derivation seed stays one below current (20), now the shipped
predecessor rather than a draft.
- upgrade-path: expected daily cache filename stays v21.
The rollup is cumulative across resume legs — already handled, now pinned
Measured on a real 3-leg session (CLI 1.0.80): every counter in a leg includes
the legs before it, and the last leg of a complete session equals its store-row
total to the nano. The parser has always emitted `cumulative - previous
cumulative` per model, so the interval arithmetic downstream already consumes
per-leg claims; it was the PR body and the provider doc that described the raw
journal as per-leg and misled the reader. Doc corrected, and (c3) pins it end
to end: a complete 3-leg session serves 900 (its last leg / store total), not
1,400 (the sum of its legs); an uncovered one serves 900 exactly once; and a
leg reporting LESS than its predecessor is taken as a fresh epoch rather than
clamped to a negative delta, so an older per-leg CLI never loses a leg.
initiator = 'compaction' replaces the timestamp heuristic where it exists
The summarization request does write its own assistant_usage_events row, and
newer stores label it. The label is now read — schema-adaptively, and the
enrichment select is a graduated chain so a store carrying the billing columns
but not `initiator` keeps its billing metadata instead of falling all the way
back to the base select. Two uses: the row is subtracted from the leg it
belongs to even though it commits before the compaction stamp, and it is kept
out of per-turn pairing since it has no assistant.message to pair with.
Optional twice over — absent on older stores, NULL on 1,504 of 2,509 rows on a
real one — so (c5) pins the labelled path at 350, the identical UNLABELLED
fixture at 400 (the documented one-request-per-compaction over-serve, which no
timestamp rule can close: the request that triggered the compaction completes
immediately before it too), and a compaction row never stealing a pairing
partner.
Attribution invariant, standing guard for the 1.8x report
(c4) runs `codeburn audit`'s own two numbers — attributed vs recomputed —
over all four combinations of the three representations a session can be
written in, at the magnitudes of a real reported day (gpt-5.6-terra, 146 rows,
input 17,792 / cache write 501,395 / cache read 12,097,364 / output 63,344 /
reasoning 24,831, billed $4.47). Every shape reconciles at 1.000, and the
covered case lands on $4.4687 = what the tokens price at = what GitHub billed,
with output 63,344 rather than the pre-fix 88,175 that re-priced reasoning on
top of itself.
upgrade-path: a day KEY is not the unit of never-lose
A parse change that re-dates a call to its true day legitimately empties one
day and fills its neighbour, token for token — observed on a real cache where
2026-08-08's single call moved to 08-07 exactly. The aging step now compares a
+/-1 day WINDOW rather than the day alone, so only a window that shrinks is a
loss, and an emptied day key is reported rather than failed.
RECONCILE_SETTLE_MS keeps 24h, with the measurement that argues against it
recorded at the constant and in the provider doc: across 91 real sessions zero
rows landed after shutdown (median -0.1s, max -0.0s). One machine, one CLI
version — the number to beat is seconds, not hours, once a second machine
agrees.
B-1 The freeze was one-directional and left the reverse hole wide open. Rows
sync; at the 90-day durable age-out the cached rows are pruned, the rollup
stops being dropped and serves again under a key that was never sent; the
session is long past settle, so it pushes an aggregate on top of the
per-request spans the receiver already holds.
Reframed around the thing that actually matters: a copilot session's
input/cache leaves this machine in one of two SHAPES — the raw rollup
(`copilot:<sid>:shutdown:`), or reconciled output (rows plus
`:shutdown-residual:`, which are disjoint by construction and together are
exactly the rollup re-expressed). The receiver must never hold both. Whichever
shape a session was first synced in, it stays in, and the other is frozen for
that session permanently — in both directions. Growth WITHIN the sent shape is
untouched, because same-shape output is additive, never substitutive: a
resumed session's new rows and residuals still push if rows were sent, a new
leg's rollup still pushes if rollups were.
This also corrects the previous commit, which classed a residual as aggregate
and so froze it for a session whose rows had gone out — the residual is
reconciled output and belongs with the rows.
B-2 A session with an unparseable timestamp was held forever while the CLI
promised it would "push once it settles". Nothing could ever settle it. Settle
now reads the newest moment a session can be SHOWN active; a session with
nothing datable at all is sent rather than held. Mixed sessions still settle on
their datable stamps.
B-3 A stamp implausibly far in the future is broken data, not evidence the
session is live, so it no longer counts toward that newest moment. One
year-ahead row can no longer hold a month-old session hostage. Ordinary clock
skew is absorbed by a one-hour grace in both directions and still reads as
live.
B-4 The residual dedup key carried the leg's POSITION. Legs sort across every
cached file for a session, so an earlier leg arriving later renumbers every
residual after it — and a renamed key is a span the receiver takes a second
time, since there is no retraction for a usage span. Keyed by the leg's own
instant instead: append-only files mean it never moves, and the equal-timestamp
coalescing above makes it unique per leg. Residuals are new in this change and
have never shipped, so no CACHE_VERSION concern.
B-5 The dry-run "already synced" count now subtracts frozen too, matching the
nothing-to-push line.
Tests: symmetric freeze from the rows side (rollup frozen, residual and new
rows still sent, per-turn untouched); all-unparseable sent, partly-unparseable
still held; a year-ahead stamp ignored while a one-minute skew still holds;
residual keys named after their leg instant with the positional names gone. The
key-prefix pin picks up the residual's new tail. (c2) pins the property rather
than one insertion scenario, because a second file's leg currently collides on
the rollup's own dedup key before it can reach the residual sweep — the
reachable repro would prove nothing about the next one, and that is stated at
the test.
Flakes: (f2) and (sc) re-bucket each run 10x isolated and 10x with four busy
loops pinning cores — 40/40 clean. Neither has clock or ordering dependence at
its margins: afterEach removes both tmpHome and the cache dir, and the two
relative timestamps in the re-bucket test are 35 days apart so they cannot
share a month. The likeliest cause of the transient failures is this branch's
own commits rewriting src/parser.ts under a concurrent vitest. (f2) gains a
self-check that its fingerprint sentinel really differs from the computed one,
which is the one way it could have passed while exercising nothing.
F1 — the upgrade-path guard was measuring the wrong layer. providerDetails
come from the menubar payload, and on a version bump adoptOlderDailyCaches
carries the superseded daily file forward as the baseline for exactly the days
this scenario creates, so a session-cache loss was mostly masked: reverting the
carry-forward showed a 10% dip where the real loss is 50%. It now sums
export.json's per-call copilot records (straight out of the parse), deletes the
baseline daily cache before the upgrade run so nothing else can answer, and
asserts EQUALITY rather than "did not shrink" — the failure mode on the other
side of the fix is a double. Proven: reverting the carry-forward now reports
80 calls -> 40, LOST history, and fails. (The double direction is asserted but
not demonstrated; a faithful re-keying simulation is collapsed by serve-time
dedup before it can be counted, which is noted at the step.)
F2 — sync was forward-only in the wrong direction. A receiver that took
`copilot:<sid>:shutdown:<model>:<n>` rollup spans from 0.9.20 keeps them
forever (append-once, no retraction for a usage span) and would now also
receive `copilot-store:` rows for the same sessions: permanent doubling on
every synced receiver, on the first push after upgrade. Sessions whose rollup
is already in the ledger are now FROZEN — their rows and residuals never go
out, and the receiver keeps the older, lossier number. A bounded under-count
in place of an unbounded over-count. Per-turn output spans are untouched
either way (the rollup never held output, reconciliation never touches them),
and sessions new to sync take the per-row path in full. `frozen` is reported
by --dry-run and the nothing-to-push line; docs/sync/README.md gains an FAQ
pointing at `codeburn sync reset --confirm` with the caveat that the receiver
must be cleared too.
F3 — docs/providers/copilot.md gains a Sharp edges section saying plainly that
a day sealed on a short store snapshot is a permanent UNDER-REPORT the
watermark advances past, not a stall, and that both realizable causes are now
closed (read ordering, compaction anchor).
F4 — the copilot dedup-key shapes are pinned by reading them back out of the
source, minted keys and discriminator prefixes alike, with the reason: the
durable union recognises history by key, so a changed prefix is a
CACHE_VERSION bump, not a parse-version bump.
F5 — the carry-forward comment now states its price: a bump no longer
re-derives a call it carried forward, so cost-shaped fixes land (cost is
recomputed from cached usage) but metadata and day-attribution fixes reach
only newly parsed calls.
F6 — the durable union filters at CALL level. A turn holding one cached call
beside a new one used to be dropped whole; nothing enforced the one-call-
per-turn assumption that made that safe.
F7 — the retain-source fence no longer skips a store it cannot fingerprint.
Present-but-unreadable defers, matching the classification path; only a
genuinely absent one stays a skip.
The CLI's session.shutdown rollup RESETS its counters at a successful
in-session compaction, so a leg containing one describes only its
post-compaction requests. Running the store-row subtraction from the previous
leg cancelled that leg's usage against the whole pre-compaction conversation
and left the residual short by exactly that much. With a complete store the
floor hid it; with a partial snapshot it was a permanent undercount, and a day
sealed on it never corrected. That is the half of the reviewer's blocker 2 the
read-ordering fence could not reach: no timing rule helps when the rows are
simply not there yet, and rollup > rows-in-interval is indistinguishable from
legitimate partial coverage.
Anchor at the compaction instead. Pre-compaction rows still SERVE; they just
stop cancelling usage the rollup never claimed. The maintainer's repro (row A
100 pre-compaction, sole rollup 200 for post-compaction request B, B's row
absent) now totals 300 instead of 200, and still totals 300 once B lands.
Event names taken from @github/copilot 1.0.80, not guessed:
session.compaction_start (nothing needed from it) and
session.compaction_complete, whose `success` is read and every other field
ignored — the payload also carries the summarization call's own usage, token
counts, model and trigger. Only success:true anchors; failed or absent falls
back to the previous leg; last compaction in a leg wins. The stamp rides on
the cached rollup call as `compactedAt` (add-only, validated optional).
Accepted, bounded over-serve, documented at the doc site: if the summarization
call writes its own store row it sits just before the compaction stamp, so it
falls outside the interval while the post-reset rollup counts it — one request
per compaction can serve twice. Not subtracted from compactionTokensUsed,
because the request that TRIGGERED the compaction also completes immediately
before it: any grace window wide enough to catch the summarization row also
catches a real request and turns an over-serve into a loss. The row writer is
native, so the bundle cannot settle it; a real store can.
(c1) pins all four shapes: the repro at 300 (and again through the cache), no
double once the row lands, a failed compaction ignored, and a leg with no
compaction unchanged. The existing 107 copilot tests stay green.
The sent-ledger is append-once and the OTLP span id derives from the same
deduplication key, so the pipeline assumes a served call is immutable: same
key, same value, forever. Copilot's serve-time reconciliation is the first
producer that breaks that (#988), three ways inside one session — a shutdown
residual shrinks as the store rows covering it land, a rollup is dropped once
rows cover its leg, and an unpaired row becomes supplementary when its journal
call appears. Sent at an intermediate state, the receiver keeps that state
forever AND receives what supersedes it. Local reports re-reconcile every pass
and were never affected; this is only about what leaves the machine.
Value-versioned keys were the other option, but usage spans have no upsert
contract — receivers sum them, so a superseding span adds rather than
replaces. (Attribution can do it because its spans are documented as upserts
by `ai.session_id`.) Giving usage spans one is a receiver protocol change.
So: hold, do not re-key. Every input to the reconciliation is written during
the session, so a session quiet for 24 hours cannot reconcile further and its
first send is also its last word. The whole session is held, not just its
residual — holding the residual alone still ships a row whose pairing can
flip. Nothing is dropped: `held` is reported by --dry-run and by the
nothing-to-push line, and the next push after the window sends it.
Scoped by provider because copilot is the only reconciling producer; every
other provider's calls go out unchanged and undelayed.
A session.shutdown rollup is reconciled against the store rows written up to
it, and the two live in different files. A session that shuts down mid-pass
appends its rollup to events.jsonl and its last row to the store, so whichever
we read first is the one that can be short — and the short direction
under-serves a compacted session, whose sole rollup covers only its
post-compaction requests while the missing row's usage has nowhere else to
come from. A day finalized on that pass keeps the undercount, because a sealed
day is only ever re-derived by a version bump.
Two halves, one rule: the durable record is read last.
- Changed stores parse after every other changed source of their provider, so
the row set is a superset of anything a rollup we read can claim. The short
direction becomes unreachable; only the harmless one is left (a row whose
journal partner has not landed, which serves its own tokens and pairs next
pass).
- A store served from cache is never re-read, so ordering cannot help it. If
it moves between its classification stat and the end of the pass, the rows
the journals were just reconciled against are stale — hold the daily
watermark instead of sealing. It lifts by itself on the next refresh, which
sees the store as changed and re-reads it last.
(f3) pins both: the flagged source parses last whatever the discovery order,
and a cache-served store mutated from inside another source's parse reports
partial hydration, then clears.
Not covered, deliberately: a store that is behind its journal for reasons no
local evidence can show (rows pruned, a backdated fixture). rollup > rows in a
leg's interval is indistinguishable from legitimate partial coverage — the
store adopted mid-session has exactly that shape permanently — so fencing on
it would hold the watermark forever. Anchoring a leg's interval at the
compaction that reset it is the real fix and needs the compaction event's name
from a machine that has Copilot data.
A parse-version or env-fingerprint change rebuilds the provider section, and
the durable carry-forward only kept entries whose source PATH had disappeared.
A durable SQLite source does not disappear: copilot's agent-traces.db and
session-store.db keep their file while the CLI prunes rows out of them. So
"the path exists" was being read as "the source can re-derive this", and the
bump deleted, permanently, exactly the history only the cache still held.
Carry every durable entry forward. For a source still on disk, park an
impossible fingerprint on it so the new parse version re-reads it in full; the
durable union merge then appends only turns whose dedup keys are not already
cached, which adds what the source still holds without duplicating it or
deleting what it has lost. The persisted `durable` flag is carried too, so the
orphan bootstrap does not have to wait for the section to be re-stamped.
The contract this rests on is stated at the site: a parse-version bump must not
re-key calls the source can still re-derive, or the union counts them twice.
(f2) pins both directions on the reviewer's shape — a pruned-but-extant DB
keeps its history across a bump, served AND persisted, and an intact DB
re-read under the same bump does not double.
Resolved against main's newer invariants:
- daily-cache: DAILY_CACHE_VERSION/MIN_SUPPORTED -> 21. Main shipped 19
(#1015 grok); 18 was burned by this branch's earlier public head and 20 is
claimed by the unmerged #1040, and isMigratableCache would adopt either as
finalized without re-deriving. Header keeps main's v19 note and adds v21's.
- session-cache: PROVIDER_PARSE_VERSIONS keeps main's grok/dsh entries and
appends this branch's `-session-store-v2` to main's copilot value.
- parser: durable age-out takes main's orphan-only rule (#992).
retainWhilePresent is now redundant under it rather than load-bearing.
- audit-report: both imports (isBehavioralCall + sanitizeModelForDisplay).
- tests/parser (e), (l), (sc): re-pinned to the orphan-only age-out. A
still-discovered >90d copilot events.jsonl now keeps serving its per-turn
output alongside the store rows; the (sc) age-out subject is now an orphan.
- daily-cache-version-rederivation: seed 20 so the adjacent-draft-version
case is what the test pins.
- scripts/upgrade-path: expected daily cache filename -> v21.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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
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.
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.
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.
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.