687 Commits

Author SHA1 Message Date
iamtoruk 195bb9128e chore: release 0.9.21
Version 0.9.21 across CLI, desktop app, and Windows menubar; refreshed
LiteLLM pricing snapshot and fallback (catches the 2026-08-24 OpenAI
GPT-5.6 cut and DeepSeek v4 repricing mid-rollout, #1134 tracks
re-tightening); CHANGELOG stamped for 0.9.21; README release links to
0.9.21. Snapshot-pinned model tests updated to current catalog truth.
2026-08-24 11:16:55 -07:00
Resham Joshi 485b2c5721 Merge pull request #1128 from avs-io/codex/post-1126-project-privacy
fix(sync): trust only proven outbound project metadata
2026-08-24 07:50:06 -07:00
iamtoruk 4e7149d2d5 test(sync): the unproven-provenance fixture now asserts ai.project omission
The #1126 test pinned basename presence; under #1128's fail-closed
provenance the same fixture (no trusted provider-recorded cwd) correctly
omits the field entirely — stronger privacy, same zero-paths guarantee.
The positive basename-on-the-wire case is covered by
sync-project-provenance.
2026-08-24 07:45:35 -07:00
iamtoruk d098628b93 Merge remote-tracking branch 'origin/main' into codex/post-1126-project-privacy 2026-08-24 07:44:10 -07:00
Aditya Vikram Singh cc721f00a7 fix(sync): address project privacy review 2026-08-24 20:09:27 +05:30
iamtoruk 99d9ec89d2 fix(daily-cache): ranged parses never write days outside their range
A turn anchored before the range start survives range slicing whole, so
the aggregator can emit a residue day the parse never covered (#1130,
found in #1127's root-cause work). The merge guards already defuse the
overwrite; this stops the residue from reaching durable history at all.
Applied at both ranged call sites (re-derive and gap); the aggregator
and slicer semantics are untouched — whole-corpus callers still
aggregate undated parses freely.
2026-08-24 07:28:43 -07:00
iamtoruk 04cd6d0bf3 Merge remote-tracking branch 'origin/main' into fix/ft002-harness-launcher
# Conflicts:
#	src/doctor.ts
2026-08-24 07:15:08 -07:00
Resham Joshi 1936f6997e Merge pull request #1123 from avs-io/local/943-copilot-credits
feat: Copilot AI Credit plan math from total_nano_aiu
2026-08-24 07:14:04 -07:00
Resham Joshi fe25d52215 Merge pull request #1118 from avs-io/local/1058-orcarouter
Price and label OrcaRouter gateway model ids as the model they route to
2026-08-24 07:13:56 -07:00
Resham Joshi 9476623c77 Merge pull request #1126 from getagentseal/fix/sync-project-basename
fix(sync): wire truth — no filesystem paths in ai.project, billable output tokens
2026-08-24 07:12:23 -07:00
iamtoruk b0a42172fb fix(daily-cache): never-lose guards for degraded parses and residue-only days
Diagnosed via #1127. Three guards:

- The gap-fill path no longer overwrites cached days wholesale: a
  complete parse wins per (date, provider) through the same guarded
  merge the re-derive path uses; a partial parse only fills days and
  slices the baseline lacks. Previously a parse degraded by the
  refresh lock (menubar running) could replace a good day with its
  undercount permanently. The gap path also stops dropping
  pendingRederive.
- A day whose only content is turn-anchored residue (a midnight-
  straddling turn's category counts with zero cost/calls/tokens) is
  never sealed: the watermark pulls back so the next launch re-derives
  it — self-healing for every broken cache in the wild, no version
  bump. Bounded to the settle window and never for the oldest cached
  day.
- codeburn doctor gains a read-only Cache health section listing
  residue-only dates and failed/empty session-cache entries, so the
  next report like #1127 is diagnosable in one command.

Tests: residue-shape pin (day-aggregator), partial-gap cannot shrink a
populated day (fails on the old code), complete-gap parity, watermark
pull-back re-derive.
2026-08-24 06:41:47 -07:00
Aditya Vikram Singh ada8ce2ac4 fix(codex): read a complete session_meta first line
rolloutFileSessionId sliced 8192 bytes then JSON.parse. Production
session_meta first lines are 22-27 KB, so both trees returned
undefined ids and the overlapping nest source was kept. Read up to
the existing 1 MiB first-line cap and parse payload.session_id from
the complete line. Pin billed-A / nest-A / nest-B at 30 KB on the
exported no-arg singleton.
2026-08-24 18:36:25 +05:30
Aditya Vikram Singh 57e897c74a fix(plan): close Copilot nanoAiu twins once per session
foldCopilotSupplementaryTurns will not fold a supplementary twin across
a local-day boundary, so the pair can occupy separate turns. Credit
spend now groups at session scope.
2026-08-24 18:35:59 +05:30
Aditya Vikram Singh 2c87ee3eaa fix(codex): walk billed+nest trees on the no-arg singleton
filterOverlap required an explicit nest path, so export const codex =
createCodexProvider() never deduped. When the resolved dir is a launcher
nest and ~/.codex is a distinct existing tree, discover both and drop nest
sources whose session id is already billed. Unique nest and billed sessions
stay. Soften the Buzz doctor note to a presence heuristic.
2026-08-24 18:15:34 +05:30
Aditya Vikram Singh 33dc05bb90 fix(sync): trust only proven outbound project metadata 2026-08-24 16:05:35 +05:30
iamtoruk 619c17ec2e fix(sync): export billable output tokens, not raw output
Providers that bill reasoning separately from output (the exclusive set)
were under-reported on the wire by exactly their reasoning volume —
measured on a real pilot push: opencode short by 37,848 tokens, the
summed reasoningTokens of the window. Cost was already correct (priced
locally before export). Route ai.output_tokens through
billableOutputTokens, the same correction the display layer took in
PR #1115. Inclusive providers (claude, codex, copilot) are byte-identical.
Span ids derive from dedup keys that carry no token counts, so nothing
re-sends and nothing duplicates.
2026-08-24 02:25:38 -07:00
iamtoruk fcbc28540f fix(sync): never put a filesystem path on the wire as ai.project
Several providers key a project by its sanitized absolute path
("-Users-me-Projects-app"), and sync shipped that verbatim as ai.project
on usage and attribution spans — contradicting docs/sync/README.md's
promise that file paths stay local. Confirmed on real exports.

One choke point after parseAllSessions maps project to the leaf directory
name before either wire path reads it; codeburn yield's local output is
untouched. Never-lose: usage span ids and commit attribution keys carry
no project (unchanged, no re-send); session attribution keys hash the
record, so each already-synced session re-emits once and the receiver
upserts by (org, trace) — replaced, not duplicated. Two directories with
the same leaf name now collapse to one wire project identity; that is
the documented contract, and git.repo still disambiguates.
2026-08-24 02:19:34 -07:00
iamtoruk 424d71ec02 Merge remote-tracking branch 'origin/main' into feat/1110-surface-hydration
# Conflicts:
#	CHANGELOG.md
2026-08-24 01:30:27 -07:00
Resham Joshi 84c4a973a0 Merge pull request #1120 from getagentseal/fix/1117-menubar-livelock
fix(menubar): no-output watchdog, lock signal cleanup, dead-pid takeover, serve orphan reaping
2026-08-24 01:29:52 -07:00
Aditya Vikram Singh ee6d614557 merge: rematch #1123 onto main after #1121
Keep both cachedCallToApiCall spreads: nanoAiu from this PR and
supplementaryAccounting / Hermes observation keys from #1121.
2026-08-24 00:01:18 +05:30
Aditya Vikram Singh 066e10a3b5 merge(main): rematch #1121 after #1116/#1122
Keep-both CHANGELOG: #916 ledger + #899 probeRoots.
No cache bump.
2026-08-23 21:18:30 +05:30
Aditya Vikram Singh af073bccdd merge(main): rematch #1118 after #1116/#1122 — daily cache v29
#1115 took v28 on main. OrcaRouter pricing takes the next number.
Keep-both CHANGELOG. MIN_SUPPORTED stays 28.
2026-08-23 21:18:07 +05:30
Aditya Vikram Singh 7042048608 fix(codex): dedup nest rollouts by session id on the default factory
Wire defaultLauncherRoots into createCodexProvider(nest) so overlap
filtering is not opts-only. Compare payload.session_id, not basename.
2026-08-23 20:54:21 +05:30
Aditya Vikram Singh 55155c10ed fix(plan): do not double Copilot credits when both twins have nanoAiu
Store-row nanoAiu is still the bill when the JSONL twin has none.
USD plans keep the cost-or-calls retain predicate.
2026-08-23 20:54:21 +05:30
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
Resham Joshi 7e23ed8083 Merge pull request #1122 from avs-io/local/899-proberoots
feat(doctor): probeRoots for five fixed-location providers (#899 Tier 2, batch 2)
2026-08-23 08:10:18 -07:00
Aditya Vikram Singh 80fbae3725 fix(report): join billable output onto existing modelBreakdown keys
Parser sessions key modelBreakdown by getShortModelName. Leftover
raw-id buckets (overview fixtures, cached summaries) were minting a
second $0 / 0-call short-name row that findUnpricedModels flagged as
Unpriced and failed CI on #1116.
2026-08-23 20:06:31 +05:30
Aditya Vikram Singh 0c7d92a312 feat: Copilot AI Credit plan math from total_nano_aiu
Spent credits are finite nanoAiu / 1e9. Token-priced costUSD does not
fill the Copilot bar. Thread nanoAiu through cachedCallToApiCall with
no cache-version bump.
2026-08-23 19:12:04 +05:30
Aditya Vikram Singh caba98817b fix(doctor): changelog and isolate probeRoots env in #899 tests
Restore CODEBUFF_DATA_DIR / VIBE_HOME after the batch-2 cases so later
suites cannot inherit a leftover override.
2026-08-23 18:53:45 +05:30
Aditya Vikram Singh 57709c69c6 feat(doctor): probeRoots for five fixed-location providers (#899 Tier 2, batch 2)
Share one discovery-root resolver per provider so doctor reports the exact
paths discoverSessions reads: all three Codebuff CHANNELS, Devin's
transcripts + sessions.db children, Gemini's tmp parent, Kiro's pre-filter
candidates with empty CLI/v2 skipped, and Mistral Vibe's joined sessions dir.
2026-08-23 18:52:13 +05:30
Aditya Vikram Singh 24ee9b9a64 fix(hermes): keep calculated-cost observation deltas estimated
Later observations with costBasis calculated were emitted as measured.
Derive the estimate marker from the stored basis so LiteLLM-priced
growth stays estimated, persist it through the session cache, and
lock the path with a discover/parse/ledger fresh+warm regression.
2026-08-23 18:46:42 +05:30
Aditya Vikram Singh ece5548126 fix(report): join model output on modelBreakdown keys
Per-call billable output was written under raw call.model while
parser buckets use getShortModelName. Display aliases split into a
zero-output named row and a phantom raw-id row. Aggregate-only
sessions fall back to each existing bucket.
2026-08-23 18:43:37 +05:30
Aditya Vikram Singh 8446d3f81e fix(hermes): keep post-finalization session growth on the observation day
Hermes rows are lifetime totals stamped at started_at. After a day is
sealed, later growth was dropped. A sidecar ledger records last-seen
totals per (profile, sessionId) and emits weight-0 observation-time
deltas so sealed days stay put while today's growth still lands.

Closes #916
2026-08-23 18:26:51 +05:30
Aditya Vikram Singh f816c5e3de fix(report): bill remaining displayed output per call
Compare, export, report JSON, overview Top-model/By-tool/day, and
menubar localModelSavings still summed raw output. Session totals
stay defined on aggregate-only and stub calls. Upgrade verifier
expects daily-cache.v28.json.
2026-08-23 18:18:57 +05:30
iamtoruk 272da806e4 fix(menubar): no-output watchdog, lock signal cleanup, dead-pid takeover, serve orphan reaping
The 0.9.20 menubar never completed a fetch on a large corpus: the cache
version bump forced a full rehydration, DataClient's fixed 45s kill ended
it mid-transaction, cache-refresh-lock had no signal cleanup so the dead
holder's lock survived, and DEFAULT_WAIT_MS (30s) < DEFAULT_STALE_MS (90s)
meant no waiter could ever recover it.

A. Swift port of #1096's watchdog. CLIWatchdog holds the constants and the
   pure verdict; spawns and the resident serve child set CODEBURN_PROGRESS=1;
   the window restarts on any stdout/stderr byte; 45s silence, 10min cold
   floor until the first payload, 15min ceiling, SIGTERM then SIGKILL after
   5s. ServeConnection's fixed 60s warm cap becomes the same silence window,
   re-armed by each progress frame, and a spent death budget is now a
   5-minute cooldown instead of disabling the resident for the app run.

B. cache-refresh-lock arms SIGINT/SIGTERM cleanup the way session-cache does
   for hydrating.lock, so a SIGTERMed holder unlinks its own lock.

C. Staleness also opens on a dead holder pid, and the waiter budget derives
   from staleMs so it can never expire before the gate it waits for. A live
   holder - fresh heartbeat, pid answers signal 0 - is never taken from.
   parser.ts heartbeats through the lock wait, the one silent stretch left.

D. The app closes its end of a retired child's stdin (dropping the handle
   left the pipe alive inside the Process), reaps every serve child
   synchronously at quit, and records pid+argv so a crash-orphaned child is
   reaped next launch. serve's final exit no longer runs through a
   monkeypatched process.exit, and its post-drain cleanup is bounded.

Closes #1117
2026-08-23 05:35:17 -07:00
iamtoruk 7af989c94a fix: emit hydration only while incomplete so warm serve stays byte-identical to one-shots
The upgrade-path gate asserts serve menubar-json == the spawned one-shot
payload; an always-present hydration block on serve broke that on every
platform. Absence already means complete (the rule one-shot consumers live
by), so the explicit complete:true block bought nothing the invariant did
not already guarantee. Consumers were written for the optional field and
need no change.
2026-08-23 05:33:37 -07:00
Aditya Vikram Singh dcab9a451c Identify OrcaRouter route ids as the routed model, not a branded label 2026-08-23 17:59:27 +05:30
iamtoruk d03b92f4ff feat: gate the partial answer on the client rendering the indicator
Each surface holds its OWN serve child: the Swift menubar (mac/), GNOME and
Windows clients spawn `codeburn serve --stdio` too, and their payload
decoders ignore an unknown key — so they would have shown a partial total
with no way to say so. The paint is now opt-in via
CODEBURN_SERVE_PROGRESSIVE, which the Electron app sets on its own serve
child because its renderer shows the indexing banner. Unset means the child
answers only with complete data, so no surface can present partial totals
unlabelled.
2026-08-23 05:18:29 -07:00
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
Aditya Vikram Singh cfdaa685a0 fix(report): route displayed output through billableOutputTokens
Cost already used the helper. Report, sessions, overview and optimize
did not, so exclusive providers dropped reasoning and optimize
double-counted it for claude/codex/copilot. Daily cache v28 re-derives.

Closes #1115
2026-08-23 17:31:12 +05:30
Aditya Vikram Singh e8ab5086fe fix(codex): empty a realpath-alias nest factory
A second factory whose path is a distinct alias of the primary home
is complete overlap, not a distinct nest. The primary factory itself
still discovers.
2026-08-23 17:30:11 +05:30
iamtoruk be1b212a3f feat: surface-wide progressive hydration via an explicit payload marker
The TTY got the fast cold start in #1109; every JSON surface still waited
for the full parse because a payload has no way to say "partial". Add one.

- `hydration: { complete, indexedFiles, totalFiles }` on the menubar payload,
  add-only and emitted ONLY by the resident serve child (which polls, and
  therefore converges). Absence keeps meaning "complete", so every one-shot
  output is byte-identical and no script can be handed partial data.
- serve: a cold child answers `status --format menubar-json` from the files
  the requested period can show (same mtime floor as the TUI) and schedules
  the unfloored fill behind it. Partial answers are never memoized; requests
  waiting behind the fill are heartbeated by id so the client's no-output
  watchdog stays armed.
- `stale` keeps its own meaning: a first paint is fresh but partial, so it
  reports through `hydration` and never sets `stale`.
- desktop app and web dash render an honest "indexing history · N/M files"
  notice while `complete` is false.
2026-08-23 04:49:58 -07:00
Aditya Vikram Singh 17bb4bfdda fix(codex): drop only overlapping nest rollouts
A second createCodexProvider(nest, {primaryDir, launcherRoots})
filters shared rollout-*.jsonl basenames. The production singleton
honors CODEX_HOME and never hides unique nest sessions because
~/.codex exists.
2026-08-23 17:13:07 +05:30
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 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
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
Aditya Vikram Singh 4296db737b fix(doctor): keep Codex probeRoots on the tree actually scanned
A Buzz CODEX_HOME now redirects discovery and probeRoots to ~/.codex
together. An explicit nest factory still has no sessions and no roots.
2026-08-23 00:13:46 +05:30