Commit graph

1646 commits

Author SHA1 Message Date
Aditya Vikram Singh
aa1e38016f fix(models): leave orcarouter/auto unpriced and bump daily cache to v28
Sonnet was a guess against a Qwen/Llama flash route. Fusion aliases keep
the 2026-08 live completion-model provenance. Warm days re-derive.
2026-08-23 20:54:21 +05:30
Aditya Vikram Singh
dcab9a451c Identify OrcaRouter route ids as the routed model, not a branded label 2026-08-23 17:59:27 +05:30
Aditya Vikram Singh
5b25641440 Price and label OrcaRouter gateway model ids as the model they route to
Add orcarouter/ as a routing wrapper like cmd/ and antigravity/, alias the
smart and fusion routes to their current upstream rows, and give each route
a branded display name. Unknown vendors nested inside the route still fail
closed.
2026-08-23 17:42:38 +05:30
Resham Joshi
c9dab7deb6
Merge pull request #1114 from getagentseal/feat/1112-quota-providers
feat: live quota for Gemini, Copilot, Antigravity + per-provider toggles
2026-08-23 04:29:16 -07:00
Resham Joshi
2a53b858a1
Merge pull request #1113 from getagentseal/feat/1111-default-today
feat: interactive dashboard opens on Today, falls back to 7 days when today is empty
2026-08-23 04:29:08 -07:00
Resham Joshi
796a504ece
Merge pull request #1109 from getagentseal/perf/1107-progressive-cold
perf: progressive cold start — paint the dated TUI first, index history in the background
2026-08-23 04:28:44 -07:00
Resham Joshi
46a7e1d818
Merge pull request #1108 from getagentseal/perf/1106-single-pass
perf: single-pass parse for dashboard ranges + correlation scan hoists
2026-08-23 04:28:19 -07:00
Resham Joshi
7ab076f1f5
Merge pull request #1105 from getagentseal/perf/1104-discovery-sweep
perf: fan out the discovery sweep instead of walking it one syscall at a time
2026-08-23 04:25:41 -07:00
iamtoruk
287791e2a9 feat: live quota for Gemini, Copilot, and Antigravity in the desktop app
Extends the existing borrowed-credential quota module (claude/codex) with
three providers, per-provider settings toggles, and a provider display map:

- gemini: Code Assist loadCodeAssist/retrieveUserQuota via the Gemini CLI's
  on-disk OAuth creds; optional env-gated token refresh; retired-tier
  responses degrade to terminalFailure with migration guidance
- copilot: copilot_internal/user with editor-plugin headers; token read-only
  from hosts.json/apps.json with one re-read on 401; marked as an internal
  API that may drift, failures degrade honestly
- antigravity: loopback-only Connect-RPC probe of the local language server
  (ps/lsof discovery via execFile, csrf token for the app variant, agy CLI
  accepted tokenless); TLS relaxation scoped to 127.0.0.1; no OAuth fallback
- settings: per-provider quota switches (default on) persisted renderer-side,
  honored in the main-process service so disabled fetchers never run; IPC
  disabled-list filtered against the provider allowlist
- sanitizeError extended for Google (ya29.) and GitHub (gh*_) token shapes

Kimi audited, not duplicated: the macOS menubar already fetches Kimi quota
live (KimiSubscriptionService.swift); the electron app not surfacing it is a
pre-existing gap, unchanged here.
2026-08-23 03:13:47 -07:00
iamtoruk
8f39cc5487 feat: open the interactive dashboard on today, falling back to 7 days when today is empty
The unset default opened on 7 days everywhere. It now opens on today and
only falls back to 7 days when today holds no sessions yet — the decision
is made from the today-scoped slice of the parse the first paint already
runs, so the probe costs a filter, not a second pass.

Explicit selections are untouched: -p/--period, --day, --from/--to, the
TUI period keys, the app's persisted default period, and every one-shot
(--format json, report/sessions/status, the piped non-TTY render) keep
the 7-day default they had.

Fixes #1111
2026-08-23 02:39:41 -07:00
iamtoruk
43bbb99a92 perf: paint the cold dashboard from the recent files, index the rest behind it
A cold start parsed the whole corpus before painting anything (~36s on a 21k
corpus) even though the default view only shows the last 7 days. A session log
is append-only, so its last event timestamp is <= its mtime: a file stamped
before the start of the displayed range provably holds nothing that range can
show. On a cold cache the interactive dashboard now parses only the files above
that floor, paints, and indexes the rest from the mounted UI.

- parser: `withColdFirstPaintFloor` scopes a parse to files that can hold
  in-range data (mtime >= rangeStart - 48h clock-skew margin), deferring only
  files with NO cache entry — nothing that has something to serve is held back,
  and network sources (synthetic now-stamped fingerprint) always load.
- A run that deferred anything is a partial hydration: it does not stamp the
  session cache complete and reports hydrationComplete false, so the daily
  backfill cannot finalize history off it and the next launch resumes cold.
  A floored run that deferred NOTHING keeps the normal stamps.
- The floor participates in the parse memo key, so a first-paint result can
  never be served to (or burst-reused by) an unfloored request.
- dashboard: cold + TTY + standard dated view only. The background fill is an
  ordinary unscoped background reload, so its cache writes and its refreshed
  panels are exactly what a full cold parse produces. While it runs, an
  "indexing history N/M files" banner states that the totals cover only what is
  indexed so far.

One-shot outputs (json/csv/markdown, report, sessions, models, serve/menubar/app
payloads) are non-TTY and never enter this path.

Fixes #1107
2026-08-23 01:37:15 -07:00
iamtoruk
a8b14a7916 perf: collapse the dashboard's repeated parses and hoist the PR-correlation loops
renderDashboard issued three parseAllSessions calls (scan range, plan window,
durable headline). They often share a START and differ only in where they end —
end-of-day vs each caller's own `new Date()` — which the exact-key memo cannot
match, so a warm render paid for the discovery sweep, the cache read and the
parse two or three times over.

withSinglePassParse lets a caller declare the widest range it will ask for. A
later request that is a PURE NARROWING of it is served by slicing that parse
instead of running the pipeline again. Pure narrowing is deliberately strict:
same start, an end that is inside, and the same month shard scope. Both extra
conditions are load-bearing, because a parse's file set is a function of its
range:
  - a changed file with `mtimeMs < range.start` is skipped without being parsed,
    so an earlier start reads files a later start never sees;
  - loadCache reads only the shards monthScopeForRange selects;
  - either way the extra files seed seenKeys/seenMsgIds BEFORE the range slice,
    and a seeded key suppresses the matching in-range turn in a provider parsed
    later — usage the narrower parse would have counted.
Holding start and month scope equal makes both parses see an identical file set,
leaving the range slice as the only difference — the same trade burstReuse
already makes for the mirror-image case (same start, later end).

Independently, correlateCrossProviderPrSessions ran two O(n*m) scans: a
`sessions.filter` per subagent child (now a one-shot agentId index) and a full
`launches.filter` per candidate (now a sorted array plus a windowed scan). Both
preserve the exact match set; launch order was never observable because the
matches collapse into a Map keyed by the sorted ref list.

Warm, 21k-file corpus, isolated cache, median of 5 interleaved:
  today  5.12s -> 3.81s (1.34x)   3 parses -> 2
  month  5.46s -> 3.41s (1.60x)   2 parses -> 1
  today --format json  4.11s -> 3.49s (1.18x, correlation hoist only)

vitest.config gains an explicit exclude so a .claude worktree's stale tests/
copy stops being swept into the run.

Refs #1106
2026-08-23 00:31:23 -07:00
iamtoruk
d5e485f415 perf: fan out the discovery sweep instead of walking it one syscall at a time
Every dated command re-walks and re-stats every provider tree before it can
decide what the cache already covers. That sweep was strictly serial at every
level -- one readdir, one stat, one state.json read at a time, and one provider
after another -- so on a 21k-file / 9-provider corpus it owned most of a warm
run's wall clock with the machine idle waiting on the kernel.

Measured per warm `today` pass on that corpus before this change: 859 ms in
discoverAllSessions (kimicode 467, codex 143, claude 135, grok 77) and 727 ms in
the Claude project walk + fingerprint pass.

- fs-utils: mapWithConcurrency + FS_SCAN_CONCURRENCY, one bounded, order-
  preserving helper for the whole sweep.
- providers/index: run provider discovery concurrently, concatenated in
  registry order.
- claude/codex/grok/kimicode: walk each level with the level fanned out,
  re-concatenated in readdir order before anything reconciles.
- parser: the Claude dir walk and both fingerprint passes (scanProjectDirs and
  parseProviderSources) fan out, then reconcile serially in discovery order,
  which is what changedFiles ordering and the seenMsgIds pre-seed depend on.
- parser: collectJsonlFiles reads entries with their types, so a plain file no
  longer costs a wasted subagents/ probe.
- codex-cache: share one in-flight load between concurrent readers. The memo is
  only populated after the read + parse resolves, so concurrent discovery had
  every caller re-reading and re-parsing the same (here 59 MB) file.

No reconciliation logic changed: the same fingerprints reach the same cache
comparison in the same order. Warm `today` 5.30s -> 2.99s, cold 34.7s -> 31.1s
(medians of 5 / 2, isolated HOME + cache). today/report -p month/models/sessions
JSON, warm and cold, per-provider and combined, are byte-identical apart from
the run's own `generated` timestamp.

Closes #1104
2026-08-22 15:52:47 -07:00
Resham Joshi
554d32eba6
Merge pull request #1098 from avs-io/fix/pr-tab-empty-period
Some checks failed
CI / semgrep (push) Waiting to run
Tests / test (22) (push) Waiting to run
Tests / test (22.13.0) (push) Waiting to run
macOS Menubar CI / test (push) Has been cancelled
fix(desktop): say why the PR tab is empty
2026-08-22 13:08:51 -07:00
iamtoruk
21382bb8e6 copy: drop the em dash from the wider-period hint (repo copy rule) 2026-08-22 12:46:42 -07:00
Resham Joshi
ed8fa3af3d
Merge pull request #1100 from getagentseal/fix/menubar-freshness-marker
payload: add-only stale marker for read-only stale menubar serves
2026-08-22 12:43:06 -07:00
Resham Joshi
3599b2d40a
Merge pull request #1101 from getagentseal/fix/copilot-haiku-pricing
fix(models): price claude-haiku-4.5 copilot session-store rows
2026-08-22 12:43:01 -07:00
iamtoruk
e19c099b05 fix: capture hydration flag right after the primary parse, not at return
isSessionHydrationComplete() (parser.ts) reads a module-level global that is
only safe immediately after the parse it describes, with no intervening
awaits. The stale-marker wiring landed in the prior commit read it at the
bottom of buildMenubarPayloadForRange, after several awaits -- including,
on the claude-config-scoped branch, a second parseAllSessions call for the
365-day history block that runs after the parse producing the headline
data. That let the history backfill's hydration outcome silently overwrite
the headline's.

Capture the flag into a local right after each branch's primary parse
resolves (the claude-config-scoped parse, or buildDurablePeriod) and thread
that captured value to buildMenubarPayload instead of re-reading the global
at the end. This also closes a cross-request race: once captured
synchronously, no other in-flight request's parse can flip it under us.

Regression test simulates a complete primary parse followed by an
incomplete bystander parse and asserts the payload reflects the primary
outcome, not the bystander's.

Drafted with minimax/MiniMax-M3 via local gateway.
2026-08-22 11:56:03 -07:00
iamtoruk
fa89015947 fix(models): price claude-haiku-4.5 copilot session-store rows (#1093)
The Copilot session-store.db writes the model as claude-haiku-4.5
(tier-first, dot), but no pricing alias existed for that raw id, so
calculateCost returned $0 while GitHub billed the real Anthropic rate
(kelchm's #946 validation: $0.0063536 for a sampled request). Aliases
it to the existing, correctly-priced claude-haiku-4-5 snapshot row --
no new rate invented. Unlike #1090's gpt-5.6-codex case, this id has
no prefix-fallback self-heal, so the daily cache bumps v26 -> v27 to
force re-derivation of already-finalized days.

Drafted with cline-pass/deepseek-v4-pro via local gateway.
2026-08-22 11:41:08 -07:00
iamtoruk
affd40e230 payload: add-only stale marker for read-only stale menubar serves (#771)
isSessionHydrationComplete() (parser.ts, PR #937) already reports when a
read-only serve skipped or staled real on-disk changes; thread it through
buildMenubarPayload as an optional stale field, present and true only on a
stale serve, always absent otherwise, so older/newer CLI-app pairs stay
compatible. Mirrors the field into the desktop renderer types and the macOS
menubar's Codable payload model (data layer only, no view change).

Drafted with minimax/MiniMax-M3 via local gateway.
2026-08-22 11:38:46 -07:00
Aditya Vikram Singh
0a43427924 fix(desktop): name a custom range on the empty PR tab
The empty note still used the dormant 30D label after a calendar
selection. Use the same rangeLabel as the TopBar caption.
2026-08-22 23:58:50 +05:30
Aditya Vikram Singh
b095091aad fix(desktop): thread PR-tab period through App
Empty copy was defaulting to Today because App never passed the selected
period. Labels now share PERIOD_LABELS; the wider hint queries Lifetime.
2026-08-22 23:46:46 +05:30
Aditya Vikram Singh
7b802f4dd0 fix(desktop): say why the PR tab is empty
Today/week/month often have no session-linked PRs, so the tab
looked broken. Name the selected period and, when All has rows,
point at that count. Do not invent gh links. Do not change
attribution or the default period.
2026-08-22 23:24:59 +05:30
Resham Joshi
9cfa957e3e
Merge pull request #1096 from getagentseal/fix/app-timeout-watchdog
fix(app): no-output watchdog replaces fixed timeouts; graceful kills; cold floor everywhere; orphan serve reaping
2026-08-22 10:47:15 -07:00
iamtoruk
4eabda17d7 fix: bound the serve drain, reap orphans on Windows, and de-flake the grace test
G1a - the stdin-EOF drain is now bounded (Promise.race against 45s, overridable
via CODEBURN_SERVE_DRAIN_MS for the test). Generous enough that no legitimate
request answered at EOF comes near it, and an async-wedged one releases the
child instead of turning it into the orphan the drain was added to prevent. The
comment names the ceiling: a SYNCHRONOUSLY wedged parse never yields to this
timer or to any other JS path, and only a signal can end that process.

G1b - Windows is the majority of the desktop fleet and had no orphan escape at
all: no ps, and no stdin-close recourse once the app has crashed. The reap now
works there. tasklist cannot report argv (image name and window title only) and
wmic is gone from current Windows, so the command line comes from
Get-CimInstance Win32_Process. Match discipline is unchanged - exact argv - with
quoting normalized on both sides, because we record a plain space-joined argv
while Windows reports the real quoted command line; a path with spaces still
round-trips. serveCommandMatches is extracted and tested directly.

G2 - the grace test raced node's cold boot (a 300ms window). The child now emits
one byte the moment it is ready, so the watchdog window is measured from
READINESS, not from spawn: boot cost cannot eat it at any machine speed. Timeout
raised on top of that. 6/6 green under 8 busy loops.

G3 - a served request heartbeats for its whole duration now, not just its parse:
the wrap moved to the one seam (runCaptured) that also covers aggregation and
payload serialization, whose ~8s tail lands back-to-back with the parse's own
quiet stretches. The one-shot path keeps the parse-only wrap, where an 8s tail
is comfortably inside the window on its own, and the CHANGELOG now says exactly
that instead of claiming every phase.

G4 - stillCold() anchors to coldStartBegan ?? bootedAt.

Text: the cold-timeout bound no longer claims to restore the "locate the CLI"
recovery (that is a not-found state, never cold-flagged), and the CHANGELOG
states the >15min case honestly - the ceiling still ends it, but partial cache
saves mean successive polls converge rather than each rescanning from nothing.
2026-08-22 10:36:58 -07:00
Resham Joshi
b4a53b5443
Merge pull request #1097 from getagentseal/fix/provider-phase-partial-saves
fix: thread partial-progress saves into non-Claude provider phases
2026-08-22 10:18:20 -07:00
iamtoruk
3317189dd6 fix: address adversarial review of the timeout watchdog (F1-F7)
F1 - `codeburn serve` lost an in-flight request's response when stdin closed
mid-flight: runStdioServe returned before its queue drained, and the explicit
process.exit(0) then hit runCaptured's monkeypatched exit, throwing ExitSignal
and exiting 1 with the frame never written. The finally now awaits the queue.

F2 - the SIGTERM rationale was false. armSignalCleanup unlinks the refresh lock
and re-raises; it publishes no partial cache, and a lock left by SIGKILL already
self-heals through the stale-pid takeover. SIGTERM-first is kept for the real
(smaller) benefit - a clean lock release instead of a takeover - and every
comment plus the CHANGELOG now says only that.

F3 - the cold gate had no exit. overviewWarmed only flips on success, so an
install that can never hydrate sat behind an indexing splash forever with no
error and no route to the CLI recovery. The cold claim now expires with the cold
window itself.

F4 - the real CLI does not heartbeat the way the demo did: a cold parse's
inter-provider cache save measured 31.6s of total silence, which a 45s window
survives only until the machine is 1.5x slower. Under CODEBURN_PROGRESS a
running parse now emits a keepalive every 10s regardless of phase, so silence
genuinely means stopped. Consumers that do not know the event ignore it.

F5 - the orphan-reap identity check matched any `cli.js` running `serve`. The
pidfile now records the exact argv and `ps -ww` must match it exactly.

F6 - bump() re-armed the watchdog after settle, leaving a timer finish() never
clears when a killed child's buffered output landed.

F7 - kill paths dropped the child from activeChildren before SIGTERM and the
SIGKILL backstop was unref'd, so a quit inside the 5s grace orphaned a child
that ignores SIGTERM. It now stays registered until it actually dies.

N8 - the silence test wrote its only byte at t~0, so it passed without the
re-arm. The byte now lands mid-window and the kill is asserted from it.
N9 - documented why mutations keep a plain total cap.
2026-08-22 09:58:59 -07:00
iamtoruk
9d2235ff0e docs: correct the stale per-file save comment 2026-08-22 09:48:09 -07:00
Resham Joshi
5042139903
Merge pull request #946 from kelchm/feat/copilot-session-store
feat(copilot): read per-request input/cache from session-store.db
2026-08-22 09:41:20 -07:00
iamtoruk
c5f03cd373 fix: thread partial-progress saves into non-Claude provider phases
scanProjectDirs (Claude) has long taken a throttled saveProgress
callback so a killed cold parse resumes from a warm cache;
parseProviderSources (every other provider) only persisted at the
whole-provider boundary. A run killed mid-way through a large single-
provider phase (a multi-GB codex corpus is the common case) discarded
everything parsed since the last provider finished, restarting that
whole phase from zero.

parseProviderSources now accepts the same onFileParsed callback,
invoked once per source right after that source's cache entry lands,
outside the per-file try/catch, mirroring scanProjectDirs' placement
and throttle. A file's fingerprint is only ever set after it fully
parses, so a mid-file kill can never leave a half-parsed file looking
complete on resume.

Verified against a real ~2GB Claude + ~4GB codex corpus (frozen
snapshot): kill -9 mid-codex-phase leaves a genuine partial cache
(no codex-results flush, complete: false), a resume only reparses the
unfinished remainder, and the final report is byte-identical to an
uninterrupted cold parse.
2026-08-22 09:39:26 -07:00
iamtoruk
c30ebecff4 Merge origin/main into feat/copilot-session-store
Resolves the conflicts #946 accumulated while it was in validation. Eight
files conflicted; the session-store accounting is unchanged.

src/daily-cache.ts — version collision. This PR minted 25 when main was at
24; #1056 (`codex-auto-review` pricing) then spent 25 on main. The bump moves
to 26/MIN 26 and daily-cache.v26.json, with main's full comment ladder kept as
the foundation and this PR's paragraph rewritten to name 26 and record the
collision. PENDING_REDERIVE_PROVIDERS and the B1 migration semantics from
b6481c19 carry over intact, retargeted at 26.

src/models.ts, src/parser.ts, src/audit-report.ts, src/models-report.ts —
#1075/#1078 replaced the per-site "reasoning is already inside output" tests
with billableOutputTokens() and REASONING_INCLUDED_IN_OUTPUT. This PR had
added copilot to that case at three sites independently. Union: all three
sites take main's helper call verbatim, and copilot joins claude and codex in
the set — same accounting this PR shipped, now through main's single source of
truth. It also reaches parser.ts activeGeneratedTokens (a fourth site, from
#1079), which is the same correction: a copilot supplementary call carries
reasoning with output 0, so counting it as generated repeats the per-turn
output. The audit legend already said so on this side.

src/providers/copilot.ts — comment-only. #1054's lastEventTimestamp-first
shutdown fallback was derived from this branch, so the code was already
identical on both sides: the `shutdownTimestamp` expression and the
`copilot:<sid>:shutdown:<model>:<n>` key are byte-for-byte main's. Both
rationales are kept (leg-collapse on date, and residual anchoring).

src/session-cache.ts — PROVIDER_PARSE_VERSIONS.copilot takes this PR's
`-session-store-v3` suffix; main's #1051 note about why a fingerprint change
is expensive is kept above it. Codex keeps main's #1092 suffix chain untouched.

src/main.ts — #1067 deleted the unreachable live dailyMap fallback that this
PR had taught behavioral weight. Main's deletion wins; the now-unused
isBehavioralTurn import goes with it.

tests/parser.test.ts — import union.

Also: scripts/upgrade-path/run.mjs NEW_DAILY_CACHE -> daily-cache.v26.json,
CODEBURN_COPILOT_SESSION_STORE_DB added to the #1064 env-isolation CLEARED
list, and the CHANGELOG entry's stale "v21" corrected to v26.

Verified: tsc clean; 3132 tests pass across 223 files; test:locks 26/26;
verify:upgrade PASSED, re-deriving daily-cache.v26.json and holding durable
copilot history across the bump. #1054's regression ("keeps three stampless
shutdown legs as :n keys with lastEventTimestamp") passes on the merged tree.
Real-corpus A/B against origin/main over 2026-07-01..2026-08-22: codex, grok,
kimicode and opencode byte-identical in export, audit and models; claude drifts
only monotonically with run order (a live session writing transcripts, confirmed
by interleaving four runs). This machine has no copilot data, so the copilot
recovery semantics rest on the suites and the upgrade-path corpus.
2026-08-22 09:36:51 -07:00
iamtoruk
d8bd428054 fix(app): give the mutation-restart and destroy paths the same SIGTERM grace
A settings mutation replaces the resident serve child, which may be mid-write:
the same lock hazard a timed-out read has, so it gets the same treatment.
restartAfterMutation and destroy now go through killGracefully instead of
SIGKILL. reapAll's one-shot loop stays a hard kill and now says why - quit has a
1.5s flush budget, shorter than the grace, so waiting one out would wedge the
quit, and a one-shot read holds no lock worth releasing.
2026-08-22 09:26:29 -07:00
iamtoruk
d4b7b48dc8 fix(app): no-output watchdog replaces fixed timeouts; graceful kills; cold floor everywhere; orphan serve reaping
The desktop app capped every read at 45s of TOTAL runtime, so a slow machine's
warm `optimize` (52.5s on 0.9.20, 39.6s on main) was SIGKILLed mid-parse and the
panel painted a red "timed out" that a 60s poll then reproduced forever.

- cli.ts: the read timeout is now a no-output watchdog. The window restarts on
  every byte the child writes (serve: every frame carrying that request's id),
  so only a genuinely silent child times out. A 15-minute absolute ceiling still
  reaps a livelocked child. Every read spawn sets CODEBURN_PROGRESS=1 so a long
  parse heartbeats through it; progress lines are stripped from any surfaced
  stderr so they can never become the error message.
- cli.ts: a timed-out child gets SIGTERM first and SIGKILL only after a 5s
  grace, on both the spawn and the serve branch. That lets the CLI's signal
  cleanup publish its partial parse and release the cross-process refresh lock
  instead of leaving both stale.
- main.ts: the 10-minute cold floor now covers EVERY read while the first
  hydration is still running, not just the overview. Sections start polling the
  moment `ready` flips (which an overview error also does), and a 45s section
  spawn queued behind the cold parse was killed on arrival.
- main.ts/renderer: a timeout raised while the hydration is still running is
  flagged `cold`, and the renderer keeps its indexing splash (and keeps the
  sections gated) instead of latching ready and painting CliErrorPanel. Only
  timeouts are flagged; a permission or nonzero failure still surfaces.
- cli.ts/main.ts: the resident serve child's pid is recorded under userData and
  a serve orphaned by a previous crash is reaped at launch, after `ps` confirms
  the pid still belongs to a codeburn serve.
- src/main.ts: `codeburn serve` exits explicitly once its stdin closes, so a
  handle outliving the transport cannot keep the child alive as an orphan.
2026-08-22 09:22:38 -07:00
Resham Joshi
68f3ed89e1
Merge pull request #1050 from avs-io/fix/968-subscription-flat-rate
Some checks are pending
CI / semgrep (push) Waiting to run
Tests / test (22) (push) Waiting to run
Tests / test (22.13.0) (push) Waiting to run
fix(models): treat subscription SKUs as honestly $0
2026-08-22 04:31:04 -07:00
iamtoruk
99c72f7ccc Update stale flat-rate test expectation for codex-auto-review
The merge with main pulled in #1056's codex-auto-review -> gpt-5.5
alias, which findUnpricedModels' pre-existing hasBillableRate check
now resolves for the raw id, so a synthetic $0 row for it is no
longer reported as unpriced (it correctly has a billable rate).

Removed it from the expected findUnpricedModels() output in the
"skips subscription / flat-rate product SKUs" test; the display-name
variant ('Codex Auto Review') is unaffected since getModelCosts does
not resolve display names, so it stays in the expected list. The
codex-auto-review / #1056 pricing interaction itself is already
covered by the "Codex activity ids (#1047)" describe block.
2026-08-22 04:25:47 -07:00
iamtoruk
f9c99a0651 Merge origin/main into fix/968-subscription-flat-rate
Resolves conflicts against main after #1078/#1084/#1088/#1090/#1092/
#1053/#1056 landed since this branch's last upstream merge.

- CHANGELOG.md: kept both entries (this PR's #968/#1050 note plus
  main's #1079/#1088, #1082, #1075 notes that had moved into the same
  "### Fixed" slot).
- Everything else (src/models.ts, main.ts, usage-aggregator.ts,
  daily-cache.ts, tests/*) merged cleanly with no conflict markers;
  git's recursive merge combined #1050's flat-rate classifier changes
  with #1056's codex-auto-review -> gpt-5.5 alias without overlap.

Verified the codex-auto-review / #1056 interaction post-merge:
isBuiltInFlatRateModel no longer matches codex-auto-review (dropped
per #1050), while MODEL_ALIASES still aliases it to gpt-5.5 (#1056),
so it prices at GPT-5.5 rates rather than $0. Covered by the existing
tests/models.test.ts "Codex activity ids (#1047)" describe block and
the "does not treat a priced sibling as expected-free" case.

getFlatRateModelsConfigHash's output is folded into
getDailyCacheConfigHash's template literal unconditionally
(flatRateModels=<hash>), so the flat-rate section always participates
in the daily-cache invalidation hash even when empty -- no
DAILY_CACHE_VERSION bump needed for this change.
2026-08-22 04:17:13 -07:00
Resham Joshi
13c1785df7
Merge pull request #1056 from avs-io/fix/1047-codex-activity-ids
fix(models): price Codex activity ids via the official underlying model
2026-08-22 04:14:32 -07:00
Resham Joshi
a7b9041bdd
Merge pull request #1053 from avs-io/fix/967-models-display-merge
fix(models): resolve and merge raw ids in models report
2026-08-22 03:50:25 -07:00
iamtoruk
00377968a0 Merge origin/main into fix/967-models-display-merge
Resolves conflicts from main's #1078 (billableOutputTokens for the
output bucket) and #1084 (models-report test exemplar swap) against
this branch's canonical-id row merging.

- src/audit-report.ts: import-line collision only. Union both sides'
  imports (billableOutputTokens + fallbackRawModelDisplayName/
  getShortModelName); both are used elsewhere in the file and neither
  side's logic needed further changes.
- src/models-report.ts: import-line collision resolved the same way.
  The row-construction conflict was structural, not a data conflict:
  main's side pushed one row per raw bucket (a simpler variant this
  branch's canonical-id folding already made obsolete downstream --
  rowsByKey/foldedCategoryCost/foldKey are used unconditionally past
  this point). Kept this branch's rowsByKey merge-by-canonical-id
  structure, which already computes credits from bucket.outputTokens
  (already billable-output-summed at the accumulation stage, main's
  change, untouched by this conflict) using the identical formula
  main used; folded main's explanatory comment about billable output
  into the kept credits block.
2026-08-22 03:43:24 -07:00
iamtoruk
b6481c1933 fix(copilot): let a parse-version bump re-derive the durable calls it re-reads
@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).
2026-08-22 03:30:53 -07:00
iamtoruk
12c8cb7790 fix(copilot): re-derive migrated copilot day slices, count the compaction row's output
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.
2026-08-22 02:26:35 -07:00
Aditya Vikram Singh
9e24d093af merge: rebase #1056 onto main with Codex v15 / daily v25
Main already spent Codex results v14 (#1092). Daily stays 25
(#1090 still the last daily bump). Keep all four parse-version
tokens (codex-pricing-v1 + codex-tps-v1 + codex-mcp-skills-v1 +
activity-price-v1) so a take-ours merge cannot drop the pricing,
throughput, or MCP/skills invalidation. Do not Extra High again.
2026-08-22 14:53:59 +05:30
Resham Joshi
e4b05471b4
Merge pull request #1092 from getagentseal/fix/codex-mcp-skills-attribution
fix(codex): attribute MCP + skills from the exec custom tool and the item model (#478)
2026-08-22 02:10:22 -07:00
iamtoruk
1c2ac6e8a4 fix(codex): attribute MCP + skills from the exec custom tool and item model
The `mcp-cli call <server> <tool>` matcher from #656 only ran on
`function_call` arguments. Codex records the same exec two other ways:
the custom-tool transport carries the shell tool as a `custom_tool_call`
whose payload is an `input` program (no `arguments` at all), and the item
model repeats a finished command as `event_msg`/`item_completed` with a
`CommandExecution` item holding an argv `command`. Both reach the Bash
counter and neither reached the matcher, so a CLI-wrapped MCP call stayed
missing from the MCP breakdown (#478).

All three shapes now feed one classification pipeline. The same pipeline
learns skills: Codex has no skill tool, so a skill load is a shell read of
the skill's SKILL.md, which landed entirely under Bash. A read counts only
when the command segment starts with a file-reading binary and the path
ends in `<name>/SKILL.md`; the skill is `<name>`, matching pi's key (#588)
and the Claude parser's vocabulary. A grep near a SKILL.md stays Bash.

Attribution only: the item-model path adds no tool of its own, and a
command carried by both shapes is attributed once (per-name claim ledger).
Cost, tokens, calls and daily rollups cannot move.

CODEX_CACHE_VERSION 13 -> 14 and the codex parse version both move: both
layers store tools/toolSequence/skills verbatim, and on a warm cache the
fix is otherwise invisible (verified by reverting the bumps: skills stay
empty). DAILY_CACHE_VERSION is deliberately unchanged - the daily cache
holds no tool/mcp/skill breakdown, and turn category cannot change because
every added attribution rides on a call that already has Bash.
2026-08-22 01:51:33 -07:00
Aditya Vikram Singh
090499ef7e merge: rebase #1056 onto main with Codex v14 / daily v25
Main already spent Codex results v13 (#1079) and daily v24 (#1090).
Keep all three parse-version tokens (codex-pricing-v1 +
codex-tps-v1 + activity-price-v1) so a take-ours merge cannot
drop the pricing or throughput invalidation. Do not Extra High again.
2026-08-22 05:26:00 +05:30
Resham Joshi
a9b7670f2f
Merge pull request #1090 from getagentseal/fix/gpt56-codex-pricing
Some checks are pending
CI / semgrep (push) Waiting to run
Tests / test (22) (push) Waiting to run
Tests / test (22.13.0) (push) Waiting to run
fix(models): price gpt-5.6-codex and gpt-5.6-codex-max
2026-08-21 16:32:15 -07:00
Resham Joshi
d3b971c21f
Merge pull request #1088 from getagentseal/fix/codex-tps-reasoning
fix(codex): exclude harness startup and fix partial-dedup timing in Tok/s
2026-08-21 16:31:45 -07:00
iamtoruk
fa8c008f71 revert(codex): retract the BUG-2 partial-dedup timing fix
Re-instrumenting this exact head over the full corpus traced the
original 12.5%/46% figures to a replication gap: the earlier
prevCumulativeTotal guard (codex.ts) already discards any token_count
event whose running total exactly repeats the previous kept one, so a
drop at the seenKeys dedup site is always a byte-identical replay of
already-counted tokens -- never a real loss. The condition BUG-2's fix
guarded against does not occur in real Codex output.

Removes taskDedupedTokens, its increment at the dedup site, the
active-time scaling at task_complete, and the now-unexercisable unit
test (its fixture forces a dedup collision that is not also a
cumulative-total repeat, a state the real writer never produces). The
dedup site keeps a short comment recording why no rescaling is needed,
so the next investigator doesn't retrace this.

Keeps: the reasoning double-count fix, the harness-startup exclusion
(BUG-1, confirmed to the decimal), the shared mergeToolIntervals
helper and permissive duration parsing (BUG-8), and the legend rename.
The real-corpus table is unchanged -- BUG-2 measured zero effect on it
before this revert too.
2026-08-21 15:37:57 -07:00
iamtoruk
c7e754d3c0 fix(codex): exclude harness startup and fix partial-dedup timing in Tok/s
Extends #1079 (reasoning double-count) with three more findings from an
exactness pass over the same throughput path:

- BUG-1: task_started fires before Codex assembles the request, so the
  gap to the first request-context event (turn_context, world_state,
  event_msg/user_message, or response_item/message) was counted as
  active model time. The active window now starts at that event instead.

- BUG-2: a token_count event dropped by fork-replay dedup lost its
  tokens from the numerator while the task's real duration still spanned
  it in the denominator, understating Tok/s for a partially (not fully)
  deduped task. The active window is now scaled down by the dropped
  tokens' proportional share.

- BUG-8: the tool-interval clip/merge/cap logic was duplicated between
  providers/codex.ts and codex-throughput.ts and had already drifted
  (task_complete only read a plain-number duration, unlike
  mcp_tool_call_end). Now one shared mergeToolIntervals, and
  task_complete's duration parses the same permissive forms.

Cost and every token count remain byte-identical. activeGeneratedTokens/
activeDurationMs/toolWaitMs are stored verbatim in both Codex caches, so
none of this self-heals -- but the CODEX_CACHE_VERSION 13 bump already
shipped for #1079 covers the same fields, so no further bump is needed.

Dashboard's per-model column keeps the "Tok/s" header (zero width slack
at the standard layout, verified against a real test); the legend now
spells out "Effective Tok/s" with a decode-speed disclaimer.
2026-08-21 15:29:14 -07:00
iamtoruk
98348d973b fix(models): price gpt-5.6-codex and gpt-5.6-codex-max
Neither id is in LiteLLM yet, so litellm-snapshot.json had no row for
them (285 sessions / 5,446 calls priced at $0 on one real corpus,
flagged during #1075 verification). Add explicit snapshot rows sourced
from the pattern every prior Codex-suffixed id LiteLLM does carry
follows: it bills identically to its bare-model sibling of the same
generation (gpt-5-codex == gpt-5, gpt-5.1-codex == gpt-5.1-codex-max ==
gpt-5.1, gpt-5.2-codex == gpt-5.2, gpt-5.3-codex == gpt-5.3, verified
against the live model_prices_and_context_window.json) - both new ids
get the exact gpt-5.6 tuple rather than an invented rate.

getModelCosts already resolved both ids to the correct rate through
the `gpt-5.6` prefix fallback before this, so a fresh parse or a warm
session-cache read (codeburn models included) was already pricing
these calls correctly. The daily cache is the one place that does not
self-heal: it has no per-provider invalidation, so a day finalized
before a `gpt-5.6` snapshot row existed at all keeps that $0 forever.
Raising MIN_SUPPORTED_VERSION (v23 -> v24) forces the one-time
re-derivation, a lossless no-op for days already correct - the
mechanism itself is generic and already covered by
daily-cache-version-rederivation.test.ts. #1056 also claims 24 on its
own branch; whichever lands second takes the next number.

Closes #1077
2026-08-21 14:53:00 -07:00