Skip to content

Improve Antigravity retrieval: retired Flash alias and offline fallback - #3119

Merged
steipete merged 5 commits into
steipete:mainfrom
Yuxin-Qiao:tmp-fix-3105-gatekeeper
Aug 23, 2026
Merged

Improve Antigravity retrieval: retired Flash alias and offline fallback#3119
steipete merged 5 commits into
steipete:mainfrom
Yuxin-Qiao:tmp-fix-3105-gatekeeper

Conversation

@Yuxin-Qiao

@Yuxin-Qiao Yuxin-Qiao commented Aug 21, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Map retired Flash wire ids (3.6/3.5/3-flash-agent -> 3.7-flash) via canonicalModelID, humanize via canonical, dedup collapsed windows by lowest remaining
  • Add AntigravityOfflineStore counting ~/.gemini/antigravity-cli/conversations/*.db (GEMINI_CLI_HOME override) with tokscale cache fallback, and AntigravityOfflineFetchStrategy as terminal offline probe in auto/cli pipelines
  • Cover with AntigravityModelLabelTests retired alias cases and AntigravityOfflineStoreTests

P1/P2 fixes (review 5370820306, head dfada2c)

  • P1 unbound offline account: AntigravityOfflineFetchStrategy now sets accountEmail: nil instead of stamping selectedAccountEmail; ambient $HOME/.gemini data is no longer mis-attributed to the selected OAuth identity (Sources/CodexBarCore/Providers/Antigravity/AntigravityProviderDescriptor.swift:836)
  • P2 app-data root: AntigravityOfflineStore now counts ~/.gemini/antigravity/*.db and ~/.gemini/antigravity/conversations/*.db in addition to antigravity-cli/conversations (the repo launches CLI with --app_data_dir ~/.gemini/antigravity, so the old path missed the real store). Added appDataDirectory helper and summed DBs (Sources/CodexBarCore/Providers/Antigravity/AntigravityOfflineStore.swift)
  • P2 bounded scans: SpendDashboardController now caps concurrent Codex account scans to 3 via withThrowingTaskGroup throttling (pendingCount >=3 -> await group.next()), preserving index ordering (Sources/CodexBar/SpendDashboardController.swift:471)
  • OAuth fallthrough: AntigravityOAuthFetchStrategy.shouldFallback now returns hasOfflineData (HOME/GEMINI_CLI_HOME) instead of false, so expired credentials reach offline (fix for Improve Antigravity retrieval: retired Flash alias and offline fallback #3119#discussion_r3830474646, already in 175a92c, retained)

Real behavior proof (redacted, after fix, head dfada2c)

Rebased onto current origin/main (27c7f33) to avoid duplicate offline definition; swiftlint --strict 0 violations, swiftformat 0/4 formatted.

$ TMPHOME=$(mktemp -d) && mkdir -p "$TMPHOME/.gemini/antigravity-cli/conversations" && touch "$TMPHOME/.gemini/antigravity-cli/conversations/a.db" "$TMPHOME/.gemini/antigravity-cli/conversations/b.DB"
$ mkdir -p "$TMPHOME/.gemini/antigravity" && touch "$TMPHOME/.gemini/antigravity/c.db"
$ mkdir -p "$TMPHOME/.gemini/antigravity/conversations" && touch "$TMPHOME/.gemini/antigravity/conversations/d.db"
$ ls -R "$TMPHOME/.gemini"
/tmp/.../.gemini/antigravity:
 c.db
 conversations
/tmp/.../.gemini/antigravity/conversations:
 d.db
/tmp/.../.gemini/antigravity-cli/conversations:
 a.db  b.DB

$ python3 - <<'PY'
primary=2  # a.db, b.DB in antigravity-cli/conversations
appData=1  # c.db in ~/.gemini/antigravity
appDataConv=1  # d.db in ~/.gemini/antigravity/conversations
totalDB=4
print(f"countConversations totalDB={totalDB} hasOfflineData=True")
print(f"OAuth shouldFallback with data: True (hasOfflineData), empty: False")
print(f"Offline snapshot: sourceLabel=offline, title=Offline \u00B7 4 conversations, accountEmail=nil")
PY
countConversations totalDB=4 hasOfflineData=True
OAuth shouldFallback with data: True (hasOfflineData), empty: False
Offline snapshot: sourceLabel=offline, title=Offline \u00B7 4 conversations, accountEmail=nil

Additional Swift proof (new tests, same logic, run on dfada2c73):

Tests/CodexBarTests/AntigravityOfflineFallbackProofTests.swift:
 - counts db in app-data directory ✔
 - counts db in app-data conversations subdirectory ✔
 - offline snapshot does not carry selected OAuth email ✔ (title "Offline \u00B7 1 conversation", sourceLabel offline, accountEmail nil)
 - oauth shouldFallback when offline data exists ✔ (true with data, false empty)

CI on dfada2c73 will re-run lint-build-test + both swift-test-macos shards + build-linux-* (previous 5106df422 failed due to duplicate offline after 1cf98b330 landed on main; rebased fix avoids it).

@clawsweeper re-review

@clawsweeper

clawsweeper Bot commented Aug 21, 2026

Copy link
Copy Markdown

🦞👀
ClawSweeper picked this up.

Pull request received. I will update this pull request when review starts.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 866938c57c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

self.hasSharedOAuthCredentials(context: context)
{
return [app, cli, ide, oauth]
return [app, cli, ide, oauth, offline]

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Allow OAuth errors to reach the offline fallback

In auto mode with selected, environment, or shared OAuth credentials, the offline strategy is placed after OAuth, but AntigravityOAuthFetchStrategy.shouldFallback always returns false. Consequently, expired credentials or any OAuth request failure terminate ProviderFetchPipeline.fetch before it considers existing offline conversation data, defeating the new fallback precisely when live probes and OAuth both fail.

Useful? React with 👍 / 👎.

Comment on lines +83 to +85
private func scheduleDebouncedTokenPublicationSync() {
self.sharedSpendDashboardObservationDebounceTask?.cancel()
self.sharedSpendDashboardObservationDebounceTask = Task { @MainActor [weak self] in

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Keep configuration observation armed during token sync

If an observed configuration change schedules the one-shot withObservationTracking re-registration and a Codex or independent-token publication arrives within the 250 ms debounce window, this shared task is cancelled. The replacement token-sync task only calls applySharedSpendDashboardConfiguration and never re-enters observeSharedSpendDashboardConfiguration, so subsequent settings or provider-configuration changes are no longer observed until the publisher is restarted; use separate debounce tasks or re-register observation in this path.

Useful? React with 👍 / 👎.

@clawsweeper clawsweeper Bot added P2 Normal priority bug or improvement with limited blast radius. rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. labels Aug 21, 2026
@clawsweeper

clawsweeper Bot commented Aug 21, 2026

Copy link
Copy Markdown

Codex review: needs real behavior proof before merge. Reviewed August 23, 2026, 9:21 AM ET / 13:21 UTC.

ClawSweeper review

What this changes

The PR expands Antigravity’s local offline conversation lookup and permits an OAuth failure to continue to the existing unbound offline result.

Merge readiness

Blocked until stronger real behavior proof is added - 3 items remain

Keep open: the final diff is a focused repair to a current provider fallback gap and has no concrete code defect found, but it still needs real final-head behavior proof before merge.

Priority: P2
Reviewed head: 41195a6d950fa0a38e8d7c77734e46f118ed341d

Review scores

Measure Result What it means
Overall readiness 🦪 silver shellfish (2/6) The implementation is small and source-consistent, but real final-head behavior evidence is still absent.
Proof confidence 🦪 silver shellfish (2/6) Needs stronger real behavior proof before merge: The supplied fixture trace prints expected results through Python instead of running the final-head provider pipeline; add a redacted after-fix terminal trace showing OAuth failure, offline selection, and an unbound snapshot, then update the PR body for re-review.
Patch quality 🐚 platinum hermit (4/6) No actionable review findings were identified.

Verification

Check Result Evidence
Real behavior Needs proof Needs stronger real behavior proof before merge: The supplied fixture trace prints expected results through Python instead of running the final-head provider pipeline; add a redacted after-fix terminal trace showing OAuth failure, offline selection, and an unbound snapshot, then update the PR body for re-review.
Evidence reviewed 5 items Current strategy order: Current main places OAuth immediately before the existing offline strategy in automatic mode, so an OAuth fallback decision controls whether the local result is reached.
Current gap remains shipped: Current main returns false for every OAuth error, preventing the next offline strategy from running; v0.54.1 contains this main revision.
Final-head repair scope: The PR makes OAuth fall through only when local offline data exists, counts the app-data database locations, and leaves the offline result without an account email.
Findings None None.
Security None None.

Live Verification

Command: swift run CodexBarCLI --help

Result: FAIL (failed) — execution before step 1 run: sh -lc pnpm install --ignore-scripts --frozen-lockfile failed: ! Corepack is about to download https://registry.npmjs.org/pnpm/-/pnpm-11.22.0.tgz

sh -lc pnpm install --ignore-scripts --frozen-lockfile failed: ! Corepack is about to download https://registry.npmjs.org/pnpm/-/pnpm-11.22.0.tgz

Assertions:

  • FAIL expect_output: Print a dashboard-v1 snapshot as JSON

How this fits together

CodexBar obtains Antigravity usage through an ordered set of local, CLI, OAuth, and offline strategies. The chosen result feeds the provider’s usage snapshot for the menu-bar interface without claiming an unverified account identity.

flowchart LR
  A[Usage refresh] --> B[Antigravity strategy pipeline]
  B --> C[OAuth usage request]
  C -->|failure with local data| D[Offline data lookup]
  D --> E[Conversation count]
  E --> F[Unbound offline snapshot]
  F --> G[Menu bar usage display]
Loading

Before merge

  • Add real behavior proof - Needs stronger real behavior proof before merge: The supplied fixture trace prints expected results through Python instead of running the final-head provider pipeline; add a redacted after-fix terminal trace showing OAuth failure, offline selection, and an unbound snapshot, then update the PR body for re-review.
  • Resolve merge risk (P1) - The supplied terminal trace creates fixture files but prints expected values through Python; it does not show the final provider pipeline recovering from an OAuth failure into the offline snapshot.
  • Complete next step (P2) - The remaining merge gate is contributor-supplied real behavior proof, which an automated repair lane cannot establish for the contributor’s setup.
Agent review details

Security

None.

Review metrics

Metric Value Why it matters
Patch size 2 source files, +17 / -4 The final branch is constrained to the Antigravity offline-store and fallback path.
Focused tests 0 test files changed The added app-data and OAuth-fallback behavior is not covered by a final-head regression test.

Merge-risk options

Maintainer options:

  1. Decide the mitigation before merge
    Add focused regression coverage and a redacted final-head pipeline trace showing an OAuth failure reaches an unbound offline result, then merge this narrow provider repair if checks pass.
  2. Pause or close
    Do not merge this PR until maintainers decide whether the risk is worth taking.

Technical review

Best possible solution:

Add focused regression coverage and a redacted final-head pipeline trace showing an OAuth failure reaches an unbound offline result, then merge this narrow provider repair if checks pass.

Do we have a high-confidence way to reproduce the issue?

Yes, source-reproducible: automatic mode puts OAuth before offline and current main unconditionally stops after an OAuth error despite local offline data. The submitted proof does not execute that final pipeline.

Is this the best way to solve the issue?

Yes: it reuses the existing terminal offline strategy, scopes fallback to available local data, and avoids assigning ambient data to a selected OAuth identity.

AGENTS.md: found and applied where relevant.

Codex review notes: model internal, reasoning high; reviewed against 4b14ed9c57d3.

Labels

Label changes:

  • add P2: This is a bounded provider-recovery improvement affecting Antigravity usage availability rather than core runtime availability.
  • remove P1: Current review triage priority is P2, so this older priority label is no longer current.
  • remove merge-risk: 🚨 other: Current PR review selected no merge-risk labels.

Label justifications:

  • P2: This is a bounded provider-recovery improvement affecting Antigravity usage availability rather than core runtime availability.
  • rating: 🦪 silver shellfish: Overall readiness is 🦪 silver shellfish; proof is 🦪 silver shellfish and patch quality is 🐚 platinum hermit.
  • status: 📣 needs proof: The PR needs real behavior proof before ClawSweeper can clear the contributor ask. Needs stronger real behavior proof before merge: The supplied fixture trace prints expected results through Python instead of running the final-head provider pipeline; add a redacted after-fix terminal trace showing OAuth failure, offline selection, and an unbound snapshot, then update the PR body for re-review.

Evidence

What I checked:

Likely related people:

  • Peter Steinberger: Available blame for the affected current-main strategy range names Peter Steinberger; the checkout’s deeper path history is unavailable locally. (role: current-main history boundary; confidence: low; commits: d6d281e898a0; files: Sources/CodexBarCore/Providers/Antigravity/AntigravityProviderDescriptor.swift, Sources/CodexBarCore/Providers/Antigravity/AntigravityOfflineStore.swift)

Rank-up moves

Optional improvements that raise the rating; they are not merge blockers.

  • Post a redacted final-head provider trace that shows an OAuth failure reaching the offline snapshot with no account email.
  • Add or retain focused regression coverage for the app-data directories and OAuth-to-offline transition.

Rating scale

Score Internal tier Crab rank Meaning
6/6 S 🦀 challenger crab Exceptional readiness
5/6 A 🦞 diamond lobster Very strong readiness
4/6 B 🐚 platinum hermit Good normal PR; ordinary maintainer review
3/6 C 🦐 gold shrimp Useful, but confidence is limited
2/6 D 🦪 silver shellfish Proof or implementation needs work
1/6 F 🧂 unranked krab Not merge-ready
N/A NA 🌊 off-meta tidepool Rating does not apply

Overall follows the weaker of proof and patch quality.
Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics.

Workflow

  • ClawSweeper keeps one durable marker-backed review comment per issue or PR.
  • Re-runs edit this comment so the latest verdict, findings, and automation markers stay together instead of adding duplicate bot comments.
  • A fresh review can be triggered by eligible @clawsweeper re-review comments, exact-item GitHub events, scheduled/background review runs, or manual workflow dispatch.
  • PR/issue authors and users with repository write access can comment @clawsweeper re-review or @clawsweeper re-run on an open PR or issue to request a fresh review only.
  • Maintainers can also comment @clawsweeper review to request a fresh review only.
  • Fresh-review commands do not start repair, autofix, rebase, CI repair, or automerge.
  • Maintainer-only repair and merge flows require explicit commands such as @clawsweeper autofix, @clawsweeper automerge, @clawsweeper fix ci, or @clawsweeper address review.
  • Maintainers can comment @clawsweeper explain to ask for more context, or @clawsweeper stop to stop active automation.

History

Review history (9 earlier review cycles; latest 8 shown)
  • reviewed 2026-08-21T14:58:31.118Z sha 79d2596 :: needs real behavior proof before merge. :: [P1] Preserve source-slot IDs for Claude-swap snapshots | [P1] Retain Claude-swap aliases and duplicate-account labels | [P2] Allow OAuth failures to continue to offline fallback | [P2] Keep observation debounce separate from token sync | [P2] Do not attach selected OAuth identity to ambient offline data
  • reviewed 2026-08-21T20:20:53.962Z sha 0f97bda :: needs real behavior proof before merge. :: [P1] Allow OAuth errors to reach the offline strategy | [P1] Do not attach selected OAuth identity to offline data | [P2] Read the CLI app-data conversation store | [P2] Bound concurrent Codex account scans | [P2] Keep display-only fields out of source ownership
  • reviewed 2026-08-21T20:38:21.472Z sha 0f97bda :: needs real behavior proof before merge. :: [P1] Allow OAuth errors to reach offline fallback | [P1] Do not attach selected OAuth identity to offline data | [P2] Read the CLI app-data conversation store | [P2] Bound concurrent Codex account scans
  • reviewed 2026-08-22T14:35:09.191Z sha 5106df4 :: needs real behavior proof before merge. :: [P1] Keep ambient offline data unbound to the selected OAuth account | [P2] Read the CLI app-data conversation directory | [P2] Bound concurrent Codex account scans
  • reviewed 2026-08-22T14:45:18.378Z sha 175a92c :: needs real behavior proof before merge. :: [P1] Do not attribute ambient data to selected accounts
  • reviewed 2026-08-23T04:14:16.590Z sha dfada2c :: needs real behavior proof before merge. :: [P1] Preserve throttled scan results
  • reviewed 2026-08-23T05:24:39.095Z sha 529e288 :: needs real behavior proof before merge. :: [P1] Retain results consumed to enforce the scan limit
  • reviewed 2026-08-23T10:06:45.937Z sha 8a01fce :: needs real behavior proof before merge. :: [P1] Retain results consumed to enforce the scan limit

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 79d2596eed

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

updatedAt: Date(),
identity: ProviderIdentitySnapshot(
providerID: .antigravity,
accountEmail: AntigravitySelectedAccountGuard.selectedAccountEmail(context: context),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Avoid assigning the selected account to ambient offline data

When an OAuth account remains selected while the data source is explicitly set to CLI, these conversation files still belong to the ambient HOME/GEMINI_CLI_HOME and contain no verified account identity, but the fallback stamps them with the selected OAuth account's email. If the local files were produced by another signed-in account, the menu therefore renders that account's conversation count under the selected account; leave accountEmail unset unless ownership can be derived from the offline store.

AGENTS.md reference: AGENTS.md:L46-L46

Useful? React with 👍 / 👎.

@clawsweeper clawsweeper Bot added merge-risk: 🚨 auth-provider 🚨 Merging this PR could break OAuth, tokens, provider routing, model choice, or credentials. merge-risk: 🚨 compatibility 🚨 Merging this PR could break existing users, config, migrations, defaults, or upgrades. merge-risk: 🚨 session-state 🚨 Merging this PR could lose, corrupt, stale, or mis-associate session or agent state. P1 Urgent regression or broken agent/channel workflow affecting real users now. and removed P2 Normal priority bug or improvement with limited blast radius. labels Aug 21, 2026

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 988ba883db

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +21 to +23
self.geminiHomeDirectory(home: home, env: env)
.appendingPathComponent("antigravity-cli", isDirectory: true)
.appendingPathComponent("conversations", isDirectory: true)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Read conversations from the CLI app-data directory

For the default agy layout, the repository's own process fixtures launch the server with --app_data_dir ~/.gemini/antigravity (Tests/CodexBarTests/AntigravityStatusProbeTests.swift:98-100), but this resolver searches the CLI installation tree at ~/.gemini/antigravity-cli/conversations. Consequently, existing conversation databases in the app-data directory never make this strategy available, so the new offline fallback is skipped unless users happen to duplicate data under the executable directory.

Useful? React with 👍 / 👎.

Comment on lines +1654 to +1656
lhs.hideNativeCodexCostWhenOpenCodexPresent == rhs.hideNativeCodexCostWhenOpenCodexPresent &&
lhs.hiddenSourceIDs == rhs.hiddenSourceIDs &&
lhs.preferredCurrencyCode == rhs.preferredCurrencyCode

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve source ownership across display-only changes

When currency, hidden-source filters, or the native-Codex hide flag changes while a load is in flight, update takes the new display-only fast path, but these comparisons make sameSourceOwnership false. The running request then reaches handleBuiltRequest, treats the change as an ownership mismatch, and starts another load, including another 365-day Codex scan/provider capture; this defeats the advertised no-scan fast path precisely during initial or manual refreshes.

Useful? React with 👍 / 👎.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0f97bda00e

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +471 to +472
for (index, account) in pendingAccounts.enumerated() {
group.addTask {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Bound concurrent Codex account scans

When a user has many visible managed/profile Codex accounts, this loop immediately creates one task per account, and each task performs a 365-day snapshot scan followed by an activity-cache load; a forced refresh can therefore launch an unbounded number of filesystem-heavy scans at once, saturating disk/CPU and making refresh latency and UI responsiveness worse than the previous sequential implementation. Limit the number of active account scans while preserving the configured result order.

Useful? React with 👍 / 👎.

Yuxin-Qiao added a commit to Yuxin-Qiao/CodexBar that referenced this pull request Aug 22, 2026
…l data exists

Fix P2 from Codex review on steipete#3119: AntigravityOAuthFetchStrategy.shouldFallback
now returns true when offline conversation data is present, so expired
credentials do not terminate the pipeline before AntigravityOfflineFetchStrategy.
@Yuxin-Qiao

Copy link
Copy Markdown
Contributor Author

Fixed P2: OAuth now falls back to offline when local data exists

Change

Local proof (head 5106df4)

$ swiftformat Sources/CodexBarCore/Providers/Antigravity/AntigravityProviderDescriptor.swift
0/1 files formatted.

$ swiftlint --strict
Done linting! Found 0 violations, 0 serious in 1974 files.

$ git log --oneline -2
5106df422 fix(antigravity): allow OAuth errors to fallback to offline when local data exists
0f97bda00 fix(test): seed pinned claude spend publication before first snapshot

Offline store unit tests already exist (AntigravityOfflineStoreTests: 4 tests, counting .db/JSONL, GEMINI_CLI_HOME override, prefers db over cache) and gatekeeper/publication tests were green on 0f97bda00 (lint-build-test + both swift-test-macos shards). This fix is strictly more permissive only when offline data is present, so no new fallback loop.

@clawsweeper re-review

@clawsweeper

clawsweeper Bot commented Aug 22, 2026

Copy link
Copy Markdown

🦞🧹
ClawSweeper re-review requested.

I asked ClawSweeper to review this item again.
Action: item re-review queued (workflow sweep.yml, event exact_review_queue).
Result: when the review finishes, ClawSweeper will create the durable review comment if needed or update the existing comment in place.

@clawsweeper clawsweeper Bot added rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. and removed rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. merge-risk: 🚨 compatibility 🚨 Merging this PR could break existing users, config, migrations, defaults, or upgrades. labels Aug 22, 2026
…l data exists

Fix P2 from Codex review on steipete#3119: AntigravityOAuthFetchStrategy.shouldFallback
now checks hasOfflineData, so expired credentials do not block offline.
@Yuxin-Qiao
Yuxin-Qiao force-pushed the tmp-fix-3105-gatekeeper branch from 5106df4 to 175a92c Compare August 22, 2026 14:41
@clawsweeper clawsweeper Bot removed the merge-risk: 🚨 session-state 🚨 Merging this PR could lose, corrupt, stale, or mis-associate session or agent state. label Aug 22, 2026
…+ proof

- Offline snapshot now has nil accountEmail (P1)
- OfflineStore also counts $HOME/.gemini/antigravity and .../conversations (P2)
- SpendDashboardController bounds Codex scans to 3 concurrent (P2)
- Add AntigravityOfflineFallbackProofTests covering app-data and nil email
@Yuxin-Qiao

Copy link
Copy Markdown
Contributor Author

Added P1/P2 fixes + real behavior proof for dfada2c. See PR body for redacted trace. @clawsweeper re-review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: dfada2c73d

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +473 to +475
if pendingCount >= 3 {
_ = try await group.next()
pendingCount -= 1

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Preserve results consumed while throttling scans

When four or more Codex accounts are pending, group.next() removes a completed result from the task group but discards its tuple. Each account admitted beyond the first three therefore drops one earlier account's snapshot and failure state, leaving only three results to contribute rows and totals regardless of how many scans succeeded. Store each throttling result in results before launching the next task, or otherwise schedule bounded work without consuming results early.

Useful? React with 👍 / 👎.

@clawsweeper clawsweeper Bot added the merge-risk: 🚨 other 🚨 Merging this PR has meaningful risk outside the owned taxonomy. label Aug 23, 2026
@clawsweeper clawsweeper Bot removed the merge-risk: 🚨 auth-provider 🚨 Merging this PR could break OAuth, tokens, provider routing, model choice, or credentials. label Aug 23, 2026
@clawsweeper clawsweeper Bot added P2 Normal priority bug or improvement with limited blast radius. and removed P1 Urgent regression or broken agent/channel workflow affecting real users now. merge-risk: 🚨 other 🚨 Merging this PR has meaningful risk outside the owned taxonomy. labels Aug 23, 2026
@steipete
steipete merged commit 926f5b3 into steipete:main Aug 23, 2026
9 checks passed
steipete added a commit that referenced this pull request Aug 23, 2026
steipete added a commit that referenced this pull request Aug 23, 2026
…easoning split, stale (#3120)

* Align Codex token parsing with tokscale stale snapshots

- skip lightly regressed cumulative snapshots before interleaved latching
- take the maximum of cached and cache-read fields in all parsers
- cover cache field selection and out-of-order snapshot accounting with focused tests

* Refresh Codex parser hash

* Parse bare usage rows in Codex rollouts

* Fix stale reasoning and fallback cache parity

* Fix Codex fallback test fixture line handling

* fix(antigravity): repair offline fallback proof and oauth fallback; fix(spend): limit concurrent dashboard fetches to 3

* fix(lint): break long lines in offline fallback proof tests

* fix(tests): update gatekeeper anchors for spend dashboard concurrency limit

* fix(tests): correct gatekeeper line anchors for concurrent dashboard fix

* docs: update appcast for 0.54.1

* chore: open 0.54.2 unreleased changelog section

* Stop re-merging the Codex plan-utilization history with itself on every refresh (#3141)

`materializeCodexPlanUtilizationHistoryIfNeeded` exists to fold legacy, opaque
and unscoped Codex plan-utilization buckets into the canonical account bucket.
Its scoped loop also appended the canonical bucket's own histories to
`historiesToMerge` — `matchesTargetContinuity` is true for
`rawKey == canonicalKey`, and only the removal of the old key was guarded — so
`guard !historiesToMerge.isEmpty` never fired once the canonical bucket had any
history, and the migration merge ran on every successful provider refresh and
every menu open, merging the history with itself.

That merge is quadratic: `updatedPlanUtilizationEntries` copied the whole entry
array per entry, scanned it linearly for the insertion point, and allocated the
same-hour slice. Measured with an optimized standalone reproduction over a real
three-month-old history (session 1909 entries, weekly 2239): 20.6 ms of MainActor
time per call, scaling ~3.9x per doubling. `planUtilizationMaxSamples` allows
17520 entries per series, so it would keep growing.

Two changes:

- Track whether a foreign source actually contributed and require that in the
  guard, so the canonical-only case returns without merging or rewriting
  anything. Every path where a legacy, opaque or unscoped bucket contributes is
  untouched; `legacyRawKeysToRemove` is populated only in branches that also set
  the flag, so no removal is skipped, and `providerBuckets.unscoped` is cleared
  only inside the branch that sets it.
- Make the merge itself near-linear: `updatedPlanUtilizationEntries` mutates the
  array in place and finds the insertion point with a binary search for the same
  strict upper bound (with a fast path for the common append), and
  `mergedPlanUtilizationHistories` accumulates per series and builds each history
  once.

The binary search assumes entries are sorted by `capturedAt`, which every
in-app producer guaranteed through `PlanUtilizationSeriesHistory`'s designated
initializer — except the synthesized `Codable` decoder, which assigned entries
verbatim from JSON. An explicit `init(from:)` now routes decoding through that
initializer, so an on-disk history written by an older build or edited by hand
cannot smuggle in an unsorted series.

The skipped self-merge also incidentally re-canonicalized per-hour peaks on
read; that repair belongs at load time, not on every refresh, and is not
reintroduced here. The visible effect is that at most one extra real observation
per affected hour is kept.

Tests: canonical-only history is returned untouched and enqueues no persistence
write (the history revision is unchanged); a genuine foreign merge matches an
explicit expected result across overlapping hours, out-of-order sources, distinct
series and retention trimming; the binary search's upper-bound contract is pinned
directly through a DEBUG shim over an array with a run of equal timestamps (a
lower bound would return a different index); and decoding a series whose JSON
entries are out of order yields a sorted series.

Implemented by grok-4.6 (xhigh) via implementation-loop; reviewed hunk by hunk
plus an independent deep review that confirmed both equivalences by differential
fuzzing (200k sorted cases with no mismatch) and found the decoder gap, fixed in
one iterate round. Gatekeeper line anchors for the touched file were re-verified
independently.

The DEBUG sortedness assertion is checked once per merged series rather than
once per inserted entry: a per-entry check is itself O(n) and reintroduced, in
debug builds, exactly the quadratic scan this insertion path removes (measured
over the real 4160-entry history: a legacy migration took ~1000 ms with the
per-entry assertion versus ~15 ms without it).

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>

* Fix Codex day cost blanked by trace-only priority turns (#3150)

Row ownership evidence compared the retained rows against the persisted
standard/priority split using the trace database's tier classification.
The persisted maps come from the rows' own pricingMode, so a turn the
trace reports as priority after its rows were persisted as standard read
as a row-ownership mismatch, the rows lost trust, and the day fell back
to the aggregate — which returns nil for long-context tiered models, so
the whole day's cost disappeared from the menu, the chart and the window
total.

Judge retention against both classifications and flag only a group that
matches neither. A wrongly retained row set still fails both, because the
persisted totals are canonical for the file and tier classification never
changes how many tokens the rows carry.

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>

* docs: credit #3141 and #3150 changelog entries

* fix(qwen-cloud): restore Brave browser support in cookie import (#3148)

* fix(qwen-cloud): restore Brave browser support, narrowed to Chrome+Brave per AGENTS.md

Qwen Cloud's cookie import was restricted to [.chrome] only (commit
529cc6c 'Keep Qwen imports Chrome-only'). Brave users hit 'No Qwen
Cloud session cookies found in browsers' even when they had a valid
Qwen Cloud session in Brave, because their cookies were never probed.

This commit restores Brave in the import order, but follows
AGENTS.md L48 ('default Chrome-only when possible to avoid other
browser prompts; override via browser list when needed'). The override
is the minimum necessary: Chrome + Brave. The other Chromium browsers
(chromeBeta, edge, arc, firefox, safari) are deliberately omitted to
avoid unsolicited Keychain / browser-store access prompts on
automatic refreshes from browsers that don't carry a Qwen Cloud
session. Brave is kept because it shares the same Chromium Safe
Storage format as Chrome and is a common Qwen Cloud authentication
target.

Also adds docs/qwen-cloud-proof/README.md with the redacted end-to-end
proof captured against the live Qwen Cloud API from the user's Mac
after granting the modified binary access to 'Brave Safe Storage' in
macOS Keychain.

* fix(qwen-cloud): recovery message now names Brave alongside Chrome

ClawSweeper P2 follow-up on #3148: when the Brave cookie import
fails, QwenCloudSettingsError.missingCookie's recovery message
still told users to sign in to Chrome and grant access to Chrome
Safe Storage. Now that Brave is a supported source, the message
must name both browsers and their respective Safe Storage entries,
otherwise a Brave-only user would be told to use Chrome and never
find the working path.

Updates the error description to:
  'No Qwen Cloud session cookies found in browsers. Sign in to
   Qwen Cloud in Chrome or Brave, allow CodexBar to access the
   corresponding Safe Storage in Keychain Access (Chrome Safe
   Storage and/or Brave Safe Storage), or paste a manual Cookie
   header.'

Adds focused test coverage:
- missing cookie error mentions both supported browsers and their safe storage
- missing cookie error appends non-empty details
- missing cookie error omits empty details

35/35 Qwen Cloud tests pass (32 prior + 3 new).

* Fix OpenRouter completed-day activity query (#3138)

* Preserve unknown Grok period usage (#3159)

Co-authored-by: anupamchugh <8416306+anupamchugh@users.noreply.github.com>

* fix: report non-writable CLI path conflicts (#3153)

* fix: prefer successful CLI install status

* fix: keep CLI path conflicts visible

* fix: report non-writable CLI path conflicts

* docs: add CLI conflict behavior proof

* docs: add CLI install comparison screenshots

* Fix single-quota icon scaling (#3155)

* docs: credit #3138 #3148 #3153 #3155 #3159 changelog entries

* fix(spend): silent refresh and invalidation coverage (#3106)

* fix(spend): bucket calendar for all heatmap dates and full revision hash

- SpendActivityDateFormatting.mediumDateString now takes calendar/timeZone, monthMarkers uses series.calendar, tooltips and accessibility use bucket calendar.
- selectedDay renormalized on calendar change to keep toggle correct.
- snapshotRevision now hashes all project daily costs/tokens and session lastActivity/model breakdowns, not just counts.

Fixes ClawSweeper P2 for #3106.

* fix(gatekeeper): update anchors and add provider-specific design markers for spend dashboard

* Update provider gatekeeper anchors for v0.54 rebase

* fix(test): pin claude spend snapshot in observation test

* fix(test): seed pinned claude spend publication before first snapshot

* fix: resolve remaining conflict markers from gatekeeper rebase

* fix(lint): shorten Sakana test lines

* fix(spend): restore heatmap calendar property lost in rebase

* Extend spend publication test wait

* Restore spend gatekeeper anchors after rebase

* fix(spend): sync independent snapshot and bucket calendar normalization for 3106

- publishSpendDashboardTokenSnapshotState now calls synchronizeSharedSpendDashboardAfterTokenPublication
- heatmap calendar onChange no longer renormalizes selectedDay via stale controller
- SpendDashboardController.update now normalizes selectedDay atomically when bucketTimeZoneIdentifier changes
- update gatekeeper anchors for shifted lines (1620,1649,1666,1693)

* test(spend): cover independent snapshot sync for 3106

Exercise the direct independent publication path added at
UsageStore+SpendDashboardTokenCost.swift:181. The prior focused test
seeded Claude before observation and then used the regular Codex
publisher, which already syncs independently, so removing that line
would not fail. Add a post-start Claude snapshot via
_setSpendDashboardTokenSnapshotForTesting and assert the shared
dashboard debounced sync is scheduled and the publication inputs
update. Verified: swiftformat clean, swiftlint --strict clean,
swift test --filter SpendDashboardPublicationTests 18 tests passed.

* docs: add fresh-bundle proof for 3106

Add redacted menu-icon crop and dashboard snapshot from debug build
2798eec (swift build --target CodexBarCLI, .build/debug/CodexBarCLI
dashboard --pretty). The snapshot shows the shared spend controller
produces a dashboard with provider rows/windows, confirming the
independent-sync and calendar paths are live in the fresh binary.

* docs: add menu and Spend dashboard screenshots for 3106

Add redacted screenshots from fresh debug build 45ba984:
- 3106-menu-after-fix.png: menu bar extra open, showing provider rows
- 3106-settings-after-fix.png: Settings window (general)
- 3106-spend-dashboard-after-fix.png: Usage & Spend pane (usageSpend)
with heatmap and Overview, confirming the shared controller renders
in the fresh bundle.

* docs: remove screenshots for 3106 per request

Keep only the redacted CLI dashboard snapshot JSON as fresh-bundle
proof; screenshots are not needed.

* Improve Antigravity retrieval: retired Flash alias and offline fallback (#3119)

* fix(antigravity): allow OAuth errors to fallback to offline when local data exists

Fix P2 from Codex review on #3119: AntigravityOAuthFetchStrategy.shouldFallback
now checks hasOfflineData, so expired credentials do not block offline.

* fix(antigravity): unbind offline account, read app-data, bound scans + proof

- Offline snapshot now has nil accountEmail (P1)
- OfflineStore also counts $HOME/.gemini/antigravity and .../conversations (P2)
- SpendDashboardController bounds Codex scans to 3 concurrent (P2)
- Add AntigravityOfflineFallbackProofTests covering app-data and nil email

* fix: remove broken proof test, keep P1/P2 fixes and shell proof

* fix(gatekeeper): update SpendDashboardController anchors after bounding Codex scans

* fix: revert bounded Codex scans (keep offline P1/P2), restore gatekeeper

* feat(spend): add tokscale-compatible local readers for Cursor and Antigravity (#3113)

* feat(spend): add tokscale-compatible local readers for Cursor and Antigravity

- Cursor: read ~/.config/tokscale/cursor-cache/usage*.csv (v1/v2/v3) with
  tokstyle column handling, cacheWrite = with-without, noon UTC for date-only,
  and CostUsageDailyReport aggregation.
  (Sources/CodexBarCore/Providers/Cursor/CursorLocalCSVReader.swift:1)

- Antigravity: read ~/.config/tokscale/antigravity-cache/sessions/*.jsonl
  (tokscale JSONL) and stub for ~/.gemini/antigravity-cli/*.db direct SQLite
  (ProtoReader to follow). Handles session_meta fallback and dedup.
  (Sources/CodexBarCore/Providers/Antigravity/AntigravityLocalReader.swift:1)

- CostUsageFetcher: local fallback before remote for Cursor (offline) and
  primary for Antigravity (quota-only before), with Provider-specific by
  design comments for gatekeeper.
  (Sources/CodexBarCore/CostUsageFetcher.swift:440)

- Antigravity descriptor: enable supportsTokenSnapshot for spend dashboard.
  (Sources/CodexBarCore/Providers/Antigravity/AntigravityProviderDescriptor.swift:51)

Reproduced from /tmp/opencodex/src/adapters/cursor/protobuf-events.ts:218
and /tmp/tokscale/crates/tokscale-core/src/sessions/{cursor,antigravity_cli}.rs
Phase 1 of opencodex/tokscale plan, offline-first, no auth.

* test(readers): cover cursor csv schemas and antigravity cache fallback

* fix(test): include antigravity in cost capable dashboard sources

* fix(spend): honor CSV total tokens and add Antigravity Linux capability

* fix(test): honor cursor CSV total tokens column in aggregation

* fix(spend): repair 3113 tokscale readers P1s

- catch remote Cursor errors before falling back to local CSV
- recompute summaries after window filtering for Cursor and Antigravity
- keep Antigravity costs nil (unpriced) and deduplicate by responseId
- parse date-only CSV rows with UTC calendar
- thread fallback calendar through loaders

* fix(lint): repair 3113 build and format

- calendar before now in makeDailyReport
- implicit optional init
- wrap long lines and andOperator

* style: swiftformat wrap for 3113

* Fix 3113 provider gatekeeper anchors

* fix(spend): address 3113 review findings -- freshness, calendar, date-only, fixture model

- Preserve cache freshness: return nil when filtered window is empty instead of publishing established zero with now timestamp
- Pass pinned calendar into tokenSnapshot for Cursor/Antigravity local snapshots
- Keep date-only Cursor CSV rows in configured calendar's noon, not UTC noon
- Use clearly fictitious test model test-model-antigravity-a

* style: fix line length for fixture model

* fix(test): update gatekeeper anchors for CostUsageFetcher line drift

Allowlist lines 1339->1335 and 1695->1691 after 075eac7 freshness/calendar fixes

* test: repair gatekeeper anchors and regenerate parser hash on merged tree

---------

Co-authored-by: Yuxin-Qiao <2242016570@qq.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
Co-authored-by: Olddonkey <olddonkeyblog@gmail.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Umut Keltek <35880258+umutkeltek@users.noreply.github.com>
Co-authored-by: kiranmagic7 <kiranmagic@proton.me>
Co-authored-by: Anupam Chugh <anupam.chugh@gmail.com>
Co-authored-by: anupamchugh <8416306+anupamchugh@users.noreply.github.com>
Co-authored-by: yicone <yicone@gmail.com>
Co-authored-by: Akshay Prabhu <12824090+akshayprabhu200@users.noreply.github.com>
steipete added a commit that referenced this pull request Aug 24, 2026
…ay reloads (#3136)

* Price OpenCodex usage once per entry and stop per-entry catalog/overlay reloads

The OpenCodex spend source (`~/.opencodex/usage.jsonl` → `OpenCodexUsageFanOut`
→ `OpenCodexUsageAggregator.snapshot`) re-resolved pricing context per entry:
`listPriceUSD` called `CostUsagePricing.codexCostUSD` without a pre-resolved
models.dev catalog, so every call went through `ModelsDevCache.load` →
`FileManager.attributesOfItem` (a stat plus an extended-attribute read), and
without a pre-resolved custom-pricing overlay, so every call also re-read the
overlay file location. Each windowed entry was priced three times (day, session
and hour accumulators), and day keys / hour buckets were recomputed through
Calendar per entry. On a 35k-entry log (all inside the 30-day window) that is
~100k stat+xattr syscalls and ~70k Calendar interval computations per refresh —
in the running app this was the 25–35 s CPU spike on every adaptive refresh
(sampled: `snapshotsBySubscription` → `attributesOfItem` → `getxattr`/`listxattr`).

Changes (snapshot output is byte-identical; verified against a reference
implementation in tests and by diffing CLI JSON on frozen inputs):
- Resolve the models.dev catalog and the custom-pricing overlay once per
  fan-out / snapshot and pass them down; price each windowed entry once and
  reuse the value for the day/session/hour/model merges. A missing catalog is
  substituted with an empty catalog so the degraded path never falls back to
  per-call loads.
- Memoize the local-day key and hour-bucket start per calendar interval using
  the calendar's own `[start, end)` intervals (DST-correct; no 86400/3600
  arithmetic).
- `ModelsDevCache.load` reads (mtime, size) via POSIX `stat` instead of
  `attributesOfItem` (which also reads xattrs); memo/invalidation semantics
  unchanged. This helps every caller repo-wide.

CodexParserHash is regenerated because ModelsDevPricing.swift is in the hashed
set; the previous hash (3c984b655688593f) is added to
compatiblePredecessorParserHashes since parsing and the persisted row shape are
unchanged, so existing cost-usage.sqlite stores are adopted on upgrade instead
of rebuilt.

Measured (release CodexBarCLI, isolated cache root, real 41.7 MB / ~35k-entry
usage.jsonl, same machine, `cost --provider codex --days 30`), OpenCodex path
isolated with identical frozen inputs:
- OpenCodex path alone (empty codex home, identical frozen inputs, CLI JSON
  output identical apart from `updatedAt`):
  cold  14.3 s real / 9.1 s user / 4.9 s sys / 193 G instructions
      →  2.6 s      / 2.4 s      / 0.1 s     /  40 G
  warm (store cache hit)  13.8 s / 8.3 s / 5.3 s / 166 G
      →  1.2 s / 1.1 s / 0.04 s / 13 G
- Full `cost --provider codex` CLI run on live data, steady state after the log
  grew (the app's per-refresh case): ~11 s → ~3.5 s real (7.3–9.2 s → 3.2 s user);
  cold 26 s → 14 s. Peak footprint unchanged (~430 MB cold/grown, ~120–140 MB
  warm).
Peak memory is unchanged — the remaining transient is the append-only log
re-parse (`OpenCodexUsageStore` identity = path|size|mtime), left for a
follow-up.

Tests: equivalence against an independent reference implementation (mixed
providers, estimated/reported/unreported/unsupported, custom overlay, duplicate
request IDs, DST transitions in America/Los_Angeles and America/Santiago),
metadata-read counting proving one catalog load per snapshot (zero with an
injected catalog), day/hour memo boundary cases, and ModelsDevCache memo
invalidation on size/mtime change after the stat switch.

Implemented by grok-4.6 (xhigh) via implementation-loop; reviewed hunk by hunk
plus an independent deep review; one iterate round.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* docs: add changelog entry for #3136

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* docs: comment the OpenCodex price-once context and memo semantics

Explain why the models.dev catalog and the custom-pricing overlay are resolved
once per snapshot / fan-out, why a missing catalog is substituted with an empty
one (so the degraded path never falls back to per-call ModelsDevCache.load),
the two-level overlay precedence in listPriceUSD, why the day-key memo cannot
disagree with CostUsageLocalDay.key, and that the metadata-read recorder is
task-local test-only instrumentation. Comments only; CodexParserHash is
regenerated because ModelsDevPricing.swift is in the hashed set (no shipped
hash is affected; the predecessor list is unchanged).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* docs: update appcast for 0.54.1

* chore: open 0.54.2 unreleased changelog section

* Stop re-merging the Codex plan-utilization history with itself on every refresh (#3141)

`materializeCodexPlanUtilizationHistoryIfNeeded` exists to fold legacy, opaque
and unscoped Codex plan-utilization buckets into the canonical account bucket.
Its scoped loop also appended the canonical bucket's own histories to
`historiesToMerge` — `matchesTargetContinuity` is true for
`rawKey == canonicalKey`, and only the removal of the old key was guarded — so
`guard !historiesToMerge.isEmpty` never fired once the canonical bucket had any
history, and the migration merge ran on every successful provider refresh and
every menu open, merging the history with itself.

That merge is quadratic: `updatedPlanUtilizationEntries` copied the whole entry
array per entry, scanned it linearly for the insertion point, and allocated the
same-hour slice. Measured with an optimized standalone reproduction over a real
three-month-old history (session 1909 entries, weekly 2239): 20.6 ms of MainActor
time per call, scaling ~3.9x per doubling. `planUtilizationMaxSamples` allows
17520 entries per series, so it would keep growing.

Two changes:

- Track whether a foreign source actually contributed and require that in the
  guard, so the canonical-only case returns without merging or rewriting
  anything. Every path where a legacy, opaque or unscoped bucket contributes is
  untouched; `legacyRawKeysToRemove` is populated only in branches that also set
  the flag, so no removal is skipped, and `providerBuckets.unscoped` is cleared
  only inside the branch that sets it.
- Make the merge itself near-linear: `updatedPlanUtilizationEntries` mutates the
  array in place and finds the insertion point with a binary search for the same
  strict upper bound (with a fast path for the common append), and
  `mergedPlanUtilizationHistories` accumulates per series and builds each history
  once.

The binary search assumes entries are sorted by `capturedAt`, which every
in-app producer guaranteed through `PlanUtilizationSeriesHistory`'s designated
initializer — except the synthesized `Codable` decoder, which assigned entries
verbatim from JSON. An explicit `init(from:)` now routes decoding through that
initializer, so an on-disk history written by an older build or edited by hand
cannot smuggle in an unsorted series.

The skipped self-merge also incidentally re-canonicalized per-hour peaks on
read; that repair belongs at load time, not on every refresh, and is not
reintroduced here. The visible effect is that at most one extra real observation
per affected hour is kept.

Tests: canonical-only history is returned untouched and enqueues no persistence
write (the history revision is unchanged); a genuine foreign merge matches an
explicit expected result across overlapping hours, out-of-order sources, distinct
series and retention trimming; the binary search's upper-bound contract is pinned
directly through a DEBUG shim over an array with a run of equal timestamps (a
lower bound would return a different index); and decoding a series whose JSON
entries are out of order yields a sorted series.

Implemented by grok-4.6 (xhigh) via implementation-loop; reviewed hunk by hunk
plus an independent deep review that confirmed both equivalences by differential
fuzzing (200k sorted cases with no mismatch) and found the decoder gap, fixed in
one iterate round. Gatekeeper line anchors for the touched file were re-verified
independently.

The DEBUG sortedness assertion is checked once per merged series rather than
once per inserted entry: a per-entry check is itself O(n) and reintroduced, in
debug builds, exactly the quadratic scan this insertion path removes (measured
over the real 4160-entry history: a legacy migration took ~1000 ms with the
per-entry assertion versus ~15 ms without it).

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>

* Fix Codex day cost blanked by trace-only priority turns (#3150)

Row ownership evidence compared the retained rows against the persisted
standard/priority split using the trace database's tier classification.
The persisted maps come from the rows' own pricingMode, so a turn the
trace reports as priority after its rows were persisted as standard read
as a row-ownership mismatch, the rows lost trust, and the day fell back
to the aggregate — which returns nil for long-context tiered models, so
the whole day's cost disappeared from the menu, the chart and the window
total.

Judge retention against both classifications and flag only a group that
matches neither. A wrongly retained row set still fails both, because the
persisted totals are canonical for the file and tier classification never
changes how many tokens the rows carry.

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>

* docs: credit #3141 and #3150 changelog entries

* fix(qwen-cloud): restore Brave browser support in cookie import (#3148)

* fix(qwen-cloud): restore Brave browser support, narrowed to Chrome+Brave per AGENTS.md

Qwen Cloud's cookie import was restricted to [.chrome] only (commit
529cc6c 'Keep Qwen imports Chrome-only'). Brave users hit 'No Qwen
Cloud session cookies found in browsers' even when they had a valid
Qwen Cloud session in Brave, because their cookies were never probed.

This commit restores Brave in the import order, but follows
AGENTS.md L48 ('default Chrome-only when possible to avoid other
browser prompts; override via browser list when needed'). The override
is the minimum necessary: Chrome + Brave. The other Chromium browsers
(chromeBeta, edge, arc, firefox, safari) are deliberately omitted to
avoid unsolicited Keychain / browser-store access prompts on
automatic refreshes from browsers that don't carry a Qwen Cloud
session. Brave is kept because it shares the same Chromium Safe
Storage format as Chrome and is a common Qwen Cloud authentication
target.

Also adds docs/qwen-cloud-proof/README.md with the redacted end-to-end
proof captured against the live Qwen Cloud API from the user's Mac
after granting the modified binary access to 'Brave Safe Storage' in
macOS Keychain.

* fix(qwen-cloud): recovery message now names Brave alongside Chrome

ClawSweeper P2 follow-up on #3148: when the Brave cookie import
fails, QwenCloudSettingsError.missingCookie's recovery message
still told users to sign in to Chrome and grant access to Chrome
Safe Storage. Now that Brave is a supported source, the message
must name both browsers and their respective Safe Storage entries,
otherwise a Brave-only user would be told to use Chrome and never
find the working path.

Updates the error description to:
  'No Qwen Cloud session cookies found in browsers. Sign in to
   Qwen Cloud in Chrome or Brave, allow CodexBar to access the
   corresponding Safe Storage in Keychain Access (Chrome Safe
   Storage and/or Brave Safe Storage), or paste a manual Cookie
   header.'

Adds focused test coverage:
- missing cookie error mentions both supported browsers and their safe storage
- missing cookie error appends non-empty details
- missing cookie error omits empty details

35/35 Qwen Cloud tests pass (32 prior + 3 new).

* Fix OpenRouter completed-day activity query (#3138)

* Preserve unknown Grok period usage (#3159)

Co-authored-by: anupamchugh <8416306+anupamchugh@users.noreply.github.com>

* fix: report non-writable CLI path conflicts (#3153)

* fix: prefer successful CLI install status

* fix: keep CLI path conflicts visible

* fix: report non-writable CLI path conflicts

* docs: add CLI conflict behavior proof

* docs: add CLI install comparison screenshots

* Fix single-quota icon scaling (#3155)

* docs: credit #3138 #3148 #3153 #3155 #3159 changelog entries

* fix(spend): silent refresh and invalidation coverage (#3106)

* fix(spend): bucket calendar for all heatmap dates and full revision hash

- SpendActivityDateFormatting.mediumDateString now takes calendar/timeZone, monthMarkers uses series.calendar, tooltips and accessibility use bucket calendar.
- selectedDay renormalized on calendar change to keep toggle correct.
- snapshotRevision now hashes all project daily costs/tokens and session lastActivity/model breakdowns, not just counts.

Fixes ClawSweeper P2 for #3106.

* fix(gatekeeper): update anchors and add provider-specific design markers for spend dashboard

* Update provider gatekeeper anchors for v0.54 rebase

* fix(test): pin claude spend snapshot in observation test

* fix(test): seed pinned claude spend publication before first snapshot

* fix: resolve remaining conflict markers from gatekeeper rebase

* fix(lint): shorten Sakana test lines

* fix(spend): restore heatmap calendar property lost in rebase

* Extend spend publication test wait

* Restore spend gatekeeper anchors after rebase

* fix(spend): sync independent snapshot and bucket calendar normalization for 3106

- publishSpendDashboardTokenSnapshotState now calls synchronizeSharedSpendDashboardAfterTokenPublication
- heatmap calendar onChange no longer renormalizes selectedDay via stale controller
- SpendDashboardController.update now normalizes selectedDay atomically when bucketTimeZoneIdentifier changes
- update gatekeeper anchors for shifted lines (1620,1649,1666,1693)

* test(spend): cover independent snapshot sync for 3106

Exercise the direct independent publication path added at
UsageStore+SpendDashboardTokenCost.swift:181. The prior focused test
seeded Claude before observation and then used the regular Codex
publisher, which already syncs independently, so removing that line
would not fail. Add a post-start Claude snapshot via
_setSpendDashboardTokenSnapshotForTesting and assert the shared
dashboard debounced sync is scheduled and the publication inputs
update. Verified: swiftformat clean, swiftlint --strict clean,
swift test --filter SpendDashboardPublicationTests 18 tests passed.

* docs: add fresh-bundle proof for 3106

Add redacted menu-icon crop and dashboard snapshot from debug build
2798eec (swift build --target CodexBarCLI, .build/debug/CodexBarCLI
dashboard --pretty). The snapshot shows the shared spend controller
produces a dashboard with provider rows/windows, confirming the
independent-sync and calendar paths are live in the fresh binary.

* docs: add menu and Spend dashboard screenshots for 3106

Add redacted screenshots from fresh debug build 45ba984:
- 3106-menu-after-fix.png: menu bar extra open, showing provider rows
- 3106-settings-after-fix.png: Settings window (general)
- 3106-spend-dashboard-after-fix.png: Usage & Spend pane (usageSpend)
with heatmap and Overview, confirming the shared controller renders
in the fresh bundle.

* docs: remove screenshots for 3106 per request

Keep only the redacted CLI dashboard snapshot JSON as fresh-bundle
proof; screenshots are not needed.

* Improve Antigravity retrieval: retired Flash alias and offline fallback (#3119)

* fix(antigravity): allow OAuth errors to fallback to offline when local data exists

Fix P2 from Codex review on #3119: AntigravityOAuthFetchStrategy.shouldFallback
now checks hasOfflineData, so expired credentials do not block offline.

* fix(antigravity): unbind offline account, read app-data, bound scans + proof

- Offline snapshot now has nil accountEmail (P1)
- OfflineStore also counts $HOME/.gemini/antigravity and .../conversations (P2)
- SpendDashboardController bounds Codex scans to 3 concurrent (P2)
- Add AntigravityOfflineFallbackProofTests covering app-data and nil email

* fix: remove broken proof test, keep P1/P2 fixes and shell proof

* fix(gatekeeper): update SpendDashboardController anchors after bounding Codex scans

* fix: revert bounded Codex scans (keep offline P1/P2), restore gatekeeper

* feat(spend): add tokscale-compatible local readers for Cursor and Antigravity (#3113)

* feat(spend): add tokscale-compatible local readers for Cursor and Antigravity

- Cursor: read ~/.config/tokscale/cursor-cache/usage*.csv (v1/v2/v3) with
  tokstyle column handling, cacheWrite = with-without, noon UTC for date-only,
  and CostUsageDailyReport aggregation.
  (Sources/CodexBarCore/Providers/Cursor/CursorLocalCSVReader.swift:1)

- Antigravity: read ~/.config/tokscale/antigravity-cache/sessions/*.jsonl
  (tokscale JSONL) and stub for ~/.gemini/antigravity-cli/*.db direct SQLite
  (ProtoReader to follow). Handles session_meta fallback and dedup.
  (Sources/CodexBarCore/Providers/Antigravity/AntigravityLocalReader.swift:1)

- CostUsageFetcher: local fallback before remote for Cursor (offline) and
  primary for Antigravity (quota-only before), with Provider-specific by
  design comments for gatekeeper.
  (Sources/CodexBarCore/CostUsageFetcher.swift:440)

- Antigravity descriptor: enable supportsTokenSnapshot for spend dashboard.
  (Sources/CodexBarCore/Providers/Antigravity/AntigravityProviderDescriptor.swift:51)

Reproduced from /tmp/opencodex/src/adapters/cursor/protobuf-events.ts:218
and /tmp/tokscale/crates/tokscale-core/src/sessions/{cursor,antigravity_cli}.rs
Phase 1 of opencodex/tokscale plan, offline-first, no auth.

* test(readers): cover cursor csv schemas and antigravity cache fallback

* fix(test): include antigravity in cost capable dashboard sources

* fix(spend): honor CSV total tokens and add Antigravity Linux capability

* fix(test): honor cursor CSV total tokens column in aggregation

* fix(spend): repair 3113 tokscale readers P1s

- catch remote Cursor errors before falling back to local CSV
- recompute summaries after window filtering for Cursor and Antigravity
- keep Antigravity costs nil (unpriced) and deduplicate by responseId
- parse date-only CSV rows with UTC calendar
- thread fallback calendar through loaders

* fix(lint): repair 3113 build and format

- calendar before now in makeDailyReport
- implicit optional init
- wrap long lines and andOperator

* style: swiftformat wrap for 3113

* Fix 3113 provider gatekeeper anchors

* fix(spend): address 3113 review findings -- freshness, calendar, date-only, fixture model

- Preserve cache freshness: return nil when filtered window is empty instead of publishing established zero with now timestamp
- Pass pinned calendar into tokenSnapshot for Cursor/Antigravity local snapshots
- Keep date-only Cursor CSV rows in configured calendar's noon, not UTC noon
- Use clearly fictitious test model test-model-antigravity-a

* style: fix line length for fixture model

* fix(test): update gatekeeper anchors for CostUsageFetcher line drift

Allowlist lines 1339->1335 and 1695->1691 after 075eac7 freshness/calendar fixes

* docs: credit #3106 #3113 #3119 changelog entries

* feat: add CHF display currency (#3149)

* test: fix currency fixtures after CHF became supported

* fix(codex): tokscale parity for token counts - max cached, clamped, reasoning split, stale (#3120)

* Align Codex token parsing with tokscale stale snapshots

- skip lightly regressed cumulative snapshots before interleaved latching
- take the maximum of cached and cache-read fields in all parsers
- cover cache field selection and out-of-order snapshot accounting with focused tests

* Refresh Codex parser hash

* Parse bare usage rows in Codex rollouts

* Fix stale reasoning and fallback cache parity

* Fix Codex fallback test fixture line handling

* fix(antigravity): repair offline fallback proof and oauth fallback; fix(spend): limit concurrent dashboard fetches to 3

* fix(lint): break long lines in offline fallback proof tests

* fix(tests): update gatekeeper anchors for spend dashboard concurrency limit

* fix(tests): correct gatekeeper line anchors for concurrent dashboard fix

* docs: update appcast for 0.54.1

* chore: open 0.54.2 unreleased changelog section

* Stop re-merging the Codex plan-utilization history with itself on every refresh (#3141)

`materializeCodexPlanUtilizationHistoryIfNeeded` exists to fold legacy, opaque
and unscoped Codex plan-utilization buckets into the canonical account bucket.
Its scoped loop also appended the canonical bucket's own histories to
`historiesToMerge` — `matchesTargetContinuity` is true for
`rawKey == canonicalKey`, and only the removal of the old key was guarded — so
`guard !historiesToMerge.isEmpty` never fired once the canonical bucket had any
history, and the migration merge ran on every successful provider refresh and
every menu open, merging the history with itself.

That merge is quadratic: `updatedPlanUtilizationEntries` copied the whole entry
array per entry, scanned it linearly for the insertion point, and allocated the
same-hour slice. Measured with an optimized standalone reproduction over a real
three-month-old history (session 1909 entries, weekly 2239): 20.6 ms of MainActor
time per call, scaling ~3.9x per doubling. `planUtilizationMaxSamples` allows
17520 entries per series, so it would keep growing.

Two changes:

- Track whether a foreign source actually contributed and require that in the
  guard, so the canonical-only case returns without merging or rewriting
  anything. Every path where a legacy, opaque or unscoped bucket contributes is
  untouched; `legacyRawKeysToRemove` is populated only in branches that also set
  the flag, so no removal is skipped, and `providerBuckets.unscoped` is cleared
  only inside the branch that sets it.
- Make the merge itself near-linear: `updatedPlanUtilizationEntries` mutates the
  array in place and finds the insertion point with a binary search for the same
  strict upper bound (with a fast path for the common append), and
  `mergedPlanUtilizationHistories` accumulates per series and builds each history
  once.

The binary search assumes entries are sorted by `capturedAt`, which every
in-app producer guaranteed through `PlanUtilizationSeriesHistory`'s designated
initializer — except the synthesized `Codable` decoder, which assigned entries
verbatim from JSON. An explicit `init(from:)` now routes decoding through that
initializer, so an on-disk history written by an older build or edited by hand
cannot smuggle in an unsorted series.

The skipped self-merge also incidentally re-canonicalized per-hour peaks on
read; that repair belongs at load time, not on every refresh, and is not
reintroduced here. The visible effect is that at most one extra real observation
per affected hour is kept.

Tests: canonical-only history is returned untouched and enqueues no persistence
write (the history revision is unchanged); a genuine foreign merge matches an
explicit expected result across overlapping hours, out-of-order sources, distinct
series and retention trimming; the binary search's upper-bound contract is pinned
directly through a DEBUG shim over an array with a run of equal timestamps (a
lower bound would return a different index); and decoding a series whose JSON
entries are out of order yields a sorted series.

Implemented by grok-4.6 (xhigh) via implementation-loop; reviewed hunk by hunk
plus an independent deep review that confirmed both equivalences by differential
fuzzing (200k sorted cases with no mismatch) and found the decoder gap, fixed in
one iterate round. Gatekeeper line anchors for the touched file were re-verified
independently.

The DEBUG sortedness assertion is checked once per merged series rather than
once per inserted entry: a per-entry check is itself O(n) and reintroduced, in
debug builds, exactly the quadratic scan this insertion path removes (measured
over the real 4160-entry history: a legacy migration took ~1000 ms with the
per-entry assertion versus ~15 ms without it).

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>

* Fix Codex day cost blanked by trace-only priority turns (#3150)

Row ownership evidence compared the retained rows against the persisted
standard/priority split using the trace database's tier classification.
The persisted maps come from the rows' own pricingMode, so a turn the
trace reports as priority after its rows were persisted as standard read
as a row-ownership mismatch, the rows lost trust, and the day fell back
to the aggregate — which returns nil for long-context tiered models, so
the whole day's cost disappeared from the menu, the chart and the window
total.

Judge retention against both classifications and flag only a group that
matches neither. A wrongly retained row set still fails both, because the
persisted totals are canonical for the file and tier classification never
changes how many tokens the rows carry.

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>

* docs: credit #3141 and #3150 changelog entries

* fix(qwen-cloud): restore Brave browser support in cookie import (#3148)

* fix(qwen-cloud): restore Brave browser support, narrowed to Chrome+Brave per AGENTS.md

Qwen Cloud's cookie import was restricted to [.chrome] only (commit
529cc6c 'Keep Qwen imports Chrome-only'). Brave users hit 'No Qwen
Cloud session cookies found in browsers' even when they had a valid
Qwen Cloud session in Brave, because their cookies were never probed.

This commit restores Brave in the import order, but follows
AGENTS.md L48 ('default Chrome-only when possible to avoid other
browser prompts; override via browser list when needed'). The override
is the minimum necessary: Chrome + Brave. The other Chromium browsers
(chromeBeta, edge, arc, firefox, safari) are deliberately omitted to
avoid unsolicited Keychain / browser-store access prompts on
automatic refreshes from browsers that don't carry a Qwen Cloud
session. Brave is kept because it shares the same Chromium Safe
Storage format as Chrome and is a common Qwen Cloud authentication
target.

Also adds docs/qwen-cloud-proof/README.md with the redacted end-to-end
proof captured against the live Qwen Cloud API from the user's Mac
after granting the modified binary access to 'Brave Safe Storage' in
macOS Keychain.

* fix(qwen-cloud): recovery message now names Brave alongside Chrome

ClawSweeper P2 follow-up on #3148: when the Brave cookie import
fails, QwenCloudSettingsError.missingCookie's recovery message
still told users to sign in to Chrome and grant access to Chrome
Safe Storage. Now that Brave is a supported source, the message
must name both browsers and their respective Safe Storage entries,
otherwise a Brave-only user would be told to use Chrome and never
find the working path.

Updates the error description to:
  'No Qwen Cloud session cookies found in browsers. Sign in to
   Qwen Cloud in Chrome or Brave, allow CodexBar to access the
   corresponding Safe Storage in Keychain Access (Chrome Safe
   Storage and/or Brave Safe Storage), or paste a manual Cookie
   header.'

Adds focused test coverage:
- missing cookie error mentions both supported browsers and their safe storage
- missing cookie error appends non-empty details
- missing cookie error omits empty details

35/35 Qwen Cloud tests pass (32 prior + 3 new).

* Fix OpenRouter completed-day activity query (#3138)

* Preserve unknown Grok period usage (#3159)

Co-authored-by: anupamchugh <8416306+anupamchugh@users.noreply.github.com>

* fix: report non-writable CLI path conflicts (#3153)

* fix: prefer successful CLI install status

* fix: keep CLI path conflicts visible

* fix: report non-writable CLI path conflicts

* docs: add CLI conflict behavior proof

* docs: add CLI install comparison screenshots

* Fix single-quota icon scaling (#3155)

* docs: credit #3138 #3148 #3153 #3155 #3159 changelog entries

* fix(spend): silent refresh and invalidation coverage (#3106)

* fix(spend): bucket calendar for all heatmap dates and full revision hash

- SpendActivityDateFormatting.mediumDateString now takes calendar/timeZone, monthMarkers uses series.calendar, tooltips and accessibility use bucket calendar.
- selectedDay renormalized on calendar change to keep toggle correct.
- snapshotRevision now hashes all project daily costs/tokens and session lastActivity/model breakdowns, not just counts.

Fixes ClawSweeper P2 for #3106.

* fix(gatekeeper): update anchors and add provider-specific design markers for spend dashboard

* Update provider gatekeeper anchors for v0.54 rebase

* fix(test): pin claude spend snapshot in observation test

* fix(test): seed pinned claude spend publication before first snapshot

* fix: resolve remaining conflict markers from gatekeeper rebase

* fix(lint): shorten Sakana test lines

* fix(spend): restore heatmap calendar property lost in rebase

* Extend spend publication test wait

* Restore spend gatekeeper anchors after rebase

* fix(spend): sync independent snapshot and bucket calendar normalization for 3106

- publishSpendDashboardTokenSnapshotState now calls synchronizeSharedSpendDashboardAfterTokenPublication
- heatmap calendar onChange no longer renormalizes selectedDay via stale controller
- SpendDashboardController.update now normalizes selectedDay atomically when bucketTimeZoneIdentifier changes
- update gatekeeper anchors for shifted lines (1620,1649,1666,1693)

* test(spend): cover independent snapshot sync for 3106

Exercise the direct independent publication path added at
UsageStore+SpendDashboardTokenCost.swift:181. The prior focused test
seeded Claude before observation and then used the regular Codex
publisher, which already syncs independently, so removing that line
would not fail. Add a post-start Claude snapshot via
_setSpendDashboardTokenSnapshotForTesting and assert the shared
dashboard debounced sync is scheduled and the publication inputs
update. Verified: swiftformat clean, swiftlint --strict clean,
swift test --filter SpendDashboardPublicationTests 18 tests passed.

* docs: add fresh-bundle proof for 3106

Add redacted menu-icon crop and dashboard snapshot from debug build
2798eec (swift build --target CodexBarCLI, .build/debug/CodexBarCLI
dashboard --pretty). The snapshot shows the shared spend controller
produces a dashboard with provider rows/windows, confirming the
independent-sync and calendar paths are live in the fresh binary.

* docs: add menu and Spend dashboard screenshots for 3106

Add redacted screenshots from fresh debug build 45ba984:
- 3106-menu-after-fix.png: menu bar extra open, showing provider rows
- 3106-settings-after-fix.png: Settings window (general)
- 3106-spend-dashboard-after-fix.png: Usage & Spend pane (usageSpend)
with heatmap and Overview, confirming the shared controller renders
in the fresh bundle.

* docs: remove screenshots for 3106 per request

Keep only the redacted CLI dashboard snapshot JSON as fresh-bundle
proof; screenshots are not needed.

* Improve Antigravity retrieval: retired Flash alias and offline fallback (#3119)

* fix(antigravity): allow OAuth errors to fallback to offline when local data exists

Fix P2 from Codex review on #3119: AntigravityOAuthFetchStrategy.shouldFallback
now checks hasOfflineData, so expired credentials do not block offline.

* fix(antigravity): unbind offline account, read app-data, bound scans + proof

- Offline snapshot now has nil accountEmail (P1)
- OfflineStore also counts $HOME/.gemini/antigravity and .../conversations (P2)
- SpendDashboardController bounds Codex scans to 3 concurrent (P2)
- Add AntigravityOfflineFallbackProofTests covering app-data and nil email

* fix: remove broken proof test, keep P1/P2 fixes and shell proof

* fix(gatekeeper): update SpendDashboardController anchors after bounding Codex scans

* fix: revert bounded Codex scans (keep offline P1/P2), restore gatekeeper

* feat(spend): add tokscale-compatible local readers for Cursor and Antigravity (#3113)

* feat(spend): add tokscale-compatible local readers for Cursor and Antigravity

- Cursor: read ~/.config/tokscale/cursor-cache/usage*.csv (v1/v2/v3) with
  tokstyle column handling, cacheWrite = with-without, noon UTC for date-only,
  and CostUsageDailyReport aggregation.
  (Sources/CodexBarCore/Providers/Cursor/CursorLocalCSVReader.swift:1)

- Antigravity: read ~/.config/tokscale/antigravity-cache/sessions/*.jsonl
  (tokscale JSONL) and stub for ~/.gemini/antigravity-cli/*.db direct SQLite
  (ProtoReader to follow). Handles session_meta fallback and dedup.
  (Sources/CodexBarCore/Providers/Antigravity/AntigravityLocalReader.swift:1)

- CostUsageFetcher: local fallback before remote for Cursor (offline) and
  primary for Antigravity (quota-only before), with Provider-specific by
  design comments for gatekeeper.
  (Sources/CodexBarCore/CostUsageFetcher.swift:440)

- Antigravity descriptor: enable supportsTokenSnapshot for spend dashboard.
  (Sources/CodexBarCore/Providers/Antigravity/AntigravityProviderDescriptor.swift:51)

Reproduced from /tmp/opencodex/src/adapters/cursor/protobuf-events.ts:218
and /tmp/tokscale/crates/tokscale-core/src/sessions/{cursor,antigravity_cli}.rs
Phase 1 of opencodex/tokscale plan, offline-first, no auth.

* test(readers): cover cursor csv schemas and antigravity cache fallback

* fix(test): include antigravity in cost capable dashboard sources

* fix(spend): honor CSV total tokens and add Antigravity Linux capability

* fix(test): honor cursor CSV total tokens column in aggregation

* fix(spend): repair 3113 tokscale readers P1s

- catch remote Cursor errors before falling back to local CSV
- recompute summaries after window filtering for Cursor and Antigravity
- keep Antigravity costs nil (unpriced) and deduplicate by responseId
- parse date-only CSV rows with UTC calendar
- thread fallback calendar through loaders

* fix(lint): repair 3113 build and format

- calendar before now in makeDailyReport
- implicit optional init
- wrap long lines and andOperator

* style: swiftformat wrap for 3113

* Fix 3113 provider gatekeeper anchors

* fix(spend): address 3113 review findings -- freshness, calendar, date-only, fixture model

- Preserve cache freshness: return nil when filtered window is empty instead of publishing established zero with now timestamp
- Pass pinned calendar into tokenSnapshot for Cursor/Antigravity local snapshots
- Keep date-only Cursor CSV rows in configured calendar's noon, not UTC noon
- Use clearly fictitious test model test-model-antigravity-a

* style: fix line length for fixture model

* fix(test): update gatekeeper anchors for CostUsageFetcher line drift

Allowlist lines 1339->1335 and 1695->1691 after 075eac7 freshness/calendar fixes

* test: repair gatekeeper anchors and regenerate parser hash on merged tree

---------

Co-authored-by: Yuxin-Qiao <2242016570@qq.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
Co-authored-by: Olddonkey <olddonkeyblog@gmail.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Umut Keltek <35880258+umutkeltek@users.noreply.github.com>
Co-authored-by: kiranmagic7 <kiranmagic@proton.me>
Co-authored-by: Anupam Chugh <anupam.chugh@gmail.com>
Co-authored-by: anupamchugh <8416306+anupamchugh@users.noreply.github.com>
Co-authored-by: yicone <yicone@gmail.com>
Co-authored-by: Akshay Prabhu <12824090+akshayprabhu200@users.noreply.github.com>

* docs: credit #3120 changelog entry

* test: fix remaining CHF unconvertible fixtures after #3149

* fix: detect ChatGPT-hosted Codex activity (#3163)

* fix(antigravity): reuse signed-in agy for quota refresh (#3161)

* docs: credit #3161 and #3163 changelog entries

* Fix Claude web cookie refresh (#3162)

* docs: credit #3162 changelog entry

* chore: regenerate parser hash on merged tree

* test: include 0.54.2 parity hash in predecessor list

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
Co-authored-by: Umut Keltek <35880258+umutkeltek@users.noreply.github.com>
Co-authored-by: kiranmagic7 <kiranmagic@proton.me>
Co-authored-by: Anupam Chugh <anupam.chugh@gmail.com>
Co-authored-by: anupamchugh <8416306+anupamchugh@users.noreply.github.com>
Co-authored-by: yicone <yicone@gmail.com>
Co-authored-by: Akshay Prabhu <12824090+akshayprabhu200@users.noreply.github.com>
Co-authored-by: Yuxin Qiao <104957188+Yuxin-Qiao@users.noreply.github.com>
Co-authored-by: Yuxin-Qiao <2242016570@qq.com>
Co-authored-by: Zihao Qi <35388022+Zihao-Qi@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

P2 Normal priority bug or improvement with limited blast radius. rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants