Classify Codex rate windows by duration (5h/weekly/30-day) - #2600
Conversation
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: bc2c051e5c
ℹ️ 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".
| case .monthly: | ||
| .monthly |
There was a problem hiding this comment.
Allow monthly Codex history through the chart filter
For a Codex snapshot with a 43,200-minute primary, this new role causes UsageStore+PlanUtilization.swift to record new samples under .monthly, but PlanUtilizationHistoryChartMenuView.visibleSeriesNames still allows only .session for a nonnil primary and .weekly for a secondary. Its filter therefore drops every newly recorded monthly series, leaving the utilization chart stale or empty for these accounts. Update the chart's duration-based visibility logic, including handling existing 43,200-minute histories previously stored as .session.
Useful? React with 👍 / 👎.
|
Codex review: needs changes before merge. Reviewed August 4, 2026, 11:30 PM ET / August 5, 2026, 03:30 UTC. ClawSweeper reviewWhat this changesThe PR classifies Codex rate-limit windows by duration so 30-day windows display as Monthly and Automatic can select a genuine weekly quota. Merge readinessThis PR remains necessary because current main and v0.47.0 still lack duration-based Codex lanes, but three compatibility defects remain in its Automatic selection and reset backfill paths. Priority: P2 Review scores
Verification
How this fits togetherCodexBar turns Codex rate-limit snapshots into semantic usage lanes consumed by the menu card, menu bar, widget, submenu, reset backfill, and history chart. Cached snapshots preserve reset information across incomplete provider responses. flowchart LR
A[Codex rate-limit snapshot] --> B[Duration-based lane projection]
B --> C[Menu card and submenu]
B --> D[Automatic menu-bar metric]
B --> E[Widget and history chart]
F[Cached rate snapshots] --> G[Reset backfill]
G --> B
Before merge
Findings
Agent review detailsSecurityNone. Review metrics
Merge-risk optionsMaintainer options:
Copy recommended automerge instructionTechnical reviewBest possible solution: Keep duration-based Monthly rendering, but preserve fallback-slot behavior for unknown durations, prioritize an exhausted valid Monthly limit, and retain cached Monthly data when a fresh primary slot is absent. Do we have a high-confidence way to reproduce the issue? Yes, source-level reproductions are clear: use an unknown 10,081-minute primary plus weekly secondary, an exhausted 43,200-minute primary plus healthy weekly secondary, and an absent fresh primary with a cached Monthly primary. No live account probe is needed for these model paths. Is this the best way to solve the issue? No. Duration-based rendering is the right narrow approach, but the branch must honor its documented unknown-duration fallback and retain valid constrained or cached Monthly windows. Full review comments:
Overall correctness: patch is incorrect AGENTS.md: found and applied where relevant. Codex review notes: model internal, reasoning high; reviewed against 5871d2036fc3. LabelsLabel justifications:
EvidenceAcceptance criteria:
What I checked:
Likely related people:
Rank-up movesOptional improvements that raise the rating; they are not merge blockers.
Rating scale
Overall follows the weaker of proof and patch quality. Workflow
HistoryReview history (10 earlier review cycles; latest 8 shown)
|
bc2c051 to
cbbc44d
Compare
|
@clawsweeper re-review Addressed the chart-history finding: Verification on the rebased branch (now on current main):
Note on real behavior proof: a live screenshot of the Monthly card requires an account whose OpenAI rate_limits currently report the 43,200-minute primary window (subscription-gap state); I have not probed any live account per repo policy. Source-level reproduction and regression coverage are in the PR. |
|
🦞🧹 I asked ClawSweeper to review this item again. Re-review progress:
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: cbbc44d606
ℹ️ 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".
| let primaryLabel = if provider == .codex { | ||
| CodexConsumerProjection.rateTitle( | ||
| lane: .session, | ||
| windowMinutes: snapshot.primary?.windowMinutes, | ||
| sessionLabel: metadata.sessionLabel, | ||
| weeklyLabel: metadata.weeklyLabel) |
There was a problem hiding this comment.
Suppress session pace for monthly Codex submenu windows
When the Codex primary is a 43,200-minute window, this now labels it “Monthly,” but usageSection still passes every raw primary to UsagePaceText.sessionSummary. That helper accepts Codex windows of any duration, so the provider submenu can render a session-style “Projected empty…” pace beneath the monthly quota, unlike the newly classified menu card where monthly pace is explicitly suppressed. Route the submenu through the classified lane or only add the session summary for the canonical 300-minute window.
Useful? React with 👍 / 👎.
Runtime proof (after-fix, head cbbc44d)Captured from the PR build running on macOS with an injected 43,200-minute Codex snapshot via a temporary local-only env probe ( OCR of the card (rendered by the PR build):
Menu bar metric (Automatic) with the same snapshot: @clawsweeper re-review |
|
@clawsweeper re-review Addressed the submenu pace finding (1ccd7c4):
Verification:
|
|
🦞🧹 I asked ClawSweeper to review this item again. Re-review progress:
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 1ccd7c4560
ℹ️ 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".
| case Self.monthlyWindowMinutes: | ||
| .monthly |
There was a problem hiding this comment.
Preserve monthly windows during reset backfill
When a snapshot contains the targeted 43,200-minute primary plus a weekly secondary and a trusted prior snapshot is available, this classification makes sourceRateWindow(for: .session) return nil. codexMergedResetBackfillSnapshot consequently builds a weekly-only backfill snapshot, and codexBackfillingResetWindows replaces the freshly fetched primary with that nil session value. After the next normal or per-account refresh, the monthly quota is therefore removed from the published snapshot; the backfill helpers need to preserve/backfill the monthly lane while reconstructing the original slots.
Useful? React with 👍 / 👎.
| if provider == .codex, window.windowMinutes != CodexConsumerProjection.sessionWindowMinutes { | ||
| return nil |
There was a problem hiding this comment.
Keep pace support for fallback session durations
For a Codex primary with an unrecognized duration, such as the supported 540-minute shape exercised by CodexCLIWindowNormalizationTests, classification deliberately falls back to the session lane, but this exact-duration guard now makes sessionPace return nil. That removes the previously available submenu pace and predictive pace warnings for fallback session windows, not just for the new monthly lane; suppress pace based on the monthly classification rather than rejecting every non-300-minute Codex session.
Useful? React with 👍 / 👎.
…ssion pace - Reset backfill rebuilt raw slots only from the session/weekly lanes, so a 43,200-minute primary (now classified .monthly) was dropped or overwritten by a stale cached session window whenever a trusted backfill baseline existed. Preserve monthly-classified slot windows in place and backfill their reset from a matching cached monthly window. - The session-pace guard rejected every non-300-minute Codex window, removing existing pace for fallback session durations (e.g. 540 minutes or nil). Suppress pace only for windows that classify into the weekly/monthly lanes, and let the central guard drive the card instead of exact-duration gates. - Move the Codex reset-backfill helpers into their own file (file-length lint). - Regression tests for both paths; they fail without the fixes.
|
@clawsweeper re-review Addressed the reset-backfill finding (2e7d47b):
Verification:
|
|
🦞🧹 I asked ClawSweeper to review this item again. Re-review progress:
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 2e7d47bdaa
ℹ️ 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".
| guard let weekly = self.menuBarSelectableRateWindow(for: .weekly), | ||
| windows.contains(where: { | ||
| $0.windowMinutes.map { $0 > Self.weeklyWindowMinutes } ?? false | ||
| }) |
There was a problem hiding this comment.
Prefer the exhausted monthly lane before weekly
When a Codex snapshot has a 30-day primary and a weekly secondary, this guard makes .automatic return the weekly window whenever any longer window exists. If the monthly quota is exhausted or otherwise the limiting cap while the weekly window still has room, the default menu-bar metric can show the weekly remaining percentage instead of the out-of-monthly-quota state; only prefer weekly when the longer window is not the constrained quota.
Useful? React with 👍 / 👎.
| return self.codexBackfillingResetWindow( | ||
| CodexConsumerProjection.sourceRateWindow(for: lane, snapshot: snapshot), | ||
| from: CodexConsumerProjection.sourceRateWindow(for: lane, snapshot: cached)) |
There was a problem hiding this comment.
Preserve cached monthly windows when the fresh slot is absent
When the trusted cache contains a future 43,200-minute Codex primary but the next result omits that primary slot, this fallback asks the cache for the .session lane; the cached monthly window now classifies outside .session, so the helper returns nil and snapshot.with(primary: nil, ...) drops the still-valid monthly reset/window. The fresh evidence beyond the earlier backfill comment is the absent-slot path: monthly is only preserved by the special case above when slotWindow is nonnil.
Useful? React with 👍 / 👎.
|
Landed. Verification before merge (independent maintainer-agent review; two real defects found and fixed on the branch before landing):
Fixes #2592. |
* fix: use in-process libproc instead of ps/lsof scans (#2599) Replace repeated macOS process-inspection subprocesses with scoped libproc and sysctl calls while preserving the Linux ps, lsof, and /proc paths. Add defensive PROCARGS2 parsing and self-process integration coverage to harden #2267. Co-authored-by: Peter Steinberger <steipete@mac-studio-sf2.local> * fix: size provider menus to their content (#2602) * build: refresh widget dependency lockfile (#2603) * build: sync widget Package.resolved with root (SweetCookieKit 0.5.1) (#2604) Root Package.resolved moved ahead of the widget workspace copy, so Scripts/package_app.sh release failed at the widget step with an out-of-date resolved file error. The script's failure handling was verified correct (non-zero exit, no stale bundle produced). Co-authored-by: Peter Steinberger <steipete@mac-studio-sf2.local> Co-authored-by: Claude Fable 5 <noreply@anthropic.com> * refactor: derive provider registrations from descriptors (#2606) * refactor: consolidate provider bootstrap manifests * refactor: derive provider icon styles from descriptors * refactor: derive provider ancillary registrations * refactor: derive widget provider metadata * test: enforce provider widget metadata sync * docs: simplify provider authoring workflow --------- Co-authored-by: Peter Steinberger <steipete@mac-studio-sf2.local> * fix: make iCloud sync survive first contact with a real fleet (#2608) Four fixes from live multi-Mac testing: - CKRecord rebase crash: allKeys() includes encrypted field names; routing them through the plain subscript threw NSInvalidArgumentException and crash-looped every receiver applying fetched records (regression test). - Fetch-before-push on first sync: a fresh device now applies fleet state before composing pushes, so its editCount-1 records can't win conflict ties and clobber the fleet. - Persist remote applies: applyExternalConfig skips disk writes by design (reload path); sync applies now schedulePersistConfig so config.json, the CLI, and the next launch see the merged result. - Dirty-set push gating: startup no longer wholesale-uploads local config (a relaunched stale Mac degraded the richest Mac's config twice in testing). Providers push only when locally edited (persisted dirty set, cleared on save success), with an empty-fleet bootstrap seeding path. Verified live: three-Mac convergence (golden profile incl. E2E-encrypted secrets propagated to two receivers; source unchanged; fleet devices and snapshots visible everywhere; no-account Mac degrades gracefully). Co-authored-by: Peter Steinberger <steipete@mac-studio-sf2.local> Co-authored-by: Claude Fable 5 <noreply@anthropic.com> * fix(alibaba-token-plan): personal requests rejected with Workspace.NotAuthorised (hardcoded switchAgent) (#2533) * fix(alibaba-token-plan): personal requests rejected with Workspace.NotAuthorised The Personal/Solo path hardcoded `switchAgent: 1_233_135` in the `cornerstoneParam` request body. The gateway binds that value to a specific account's workspace, so for any other account the call is rejected with `BailianGateway.Workspace.NotAuthorised` even though the outer envelope claims `code: "200"`. Omitting the field lets the gateway resolve the session's default workspace. Also: - Resolve `sec_token` best-effort for personal requests (the browser always sends it; some accounts are rejected without it) and append it to the body when available, mirroring the Teams path. - `throwIfErrorPayload` now reads `errorCode`/`errorMsg` from the nested frame that carries `success: false`, so the real gateway error is surfaced instead of a misleading "API error: 200". Authorization-family errors map to `invalidCredentials` so the UI prompts for re-authentication. Fixes #2500. Verified live against the mainland Personal/Solo API and covered by new regression tests. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix: preserve Alibaba personal sessions --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> Co-authored-by: Peter Steinberger <steipete@mac-studio-sf2.local> * Add Notion AI provider (#2552) * Add Notion AI provider Tracks the two usage-allowance windows Notion shows in Settings > Notion AI > Usage: the rolling 6-hour window and the billing-period window. Notion begins enforcing the allowance on 2026-08-03; before then the same endpoint already returns real numbers with "enforcement": "preview". Reads two cookie-authenticated endpoints on app.notion.com that the Notion web app itself calls: getSpaces for account identity and workspace plans, and getCreditRateLimitStatus for the allowance. Only Business and Enterprise workspaces carry one; anything else reports not_applicable as a clear error rather than an empty gauge. The imported cookie header is persisted through CookieHeaderCache. Chromium cookie reads are gated to user-initiated refreshes to avoid a Keychain prompt, so without a cached header the provider fails on every background refresh and reports "no cookies found" while the session is valid. Cookies are also de-duplicated by name across the Notion domains, since a stale token_v2 left on the legacy notion.so alongside the live one would otherwise send both. Every field of the rate-limit response is optional, so an unrelated 200 body decodes cleanly into an all-nil status. The parser rejects a payload carrying neither window, and a window with no usable limit is omitted rather than reported as 0% used. Adding the .notion debug-log case tipped UsageStore.debugLogText past the cyclomatic-complexity cap, so .openai and .azureopenai are folded into one case; both already call the same apiKeyDebugLine helper. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(notion): harden cookie session handling * test: isolate widget snapshot persistence * feat(notion): pace both allowance bars and honor CLI provider settings The billing-period window is scored against the real calendar month ending at its reset rather than a flat 30 days, and the rolling window gets the session-pace treatment on both the card and in `codexbar usage`. Notion reports only `periodEndMs` for the billing period, so the snapshot carries the shared monthly sentinel and the descriptor declares `.calendarMonthResetWindow`. Resolution then substitutes the true cycle length. Three paths scored these windows without resolving first -- `UsageStore.weeklyPace` (menu-bar pace token, "runs out" text, predictive pace warnings) and `resetWindowPaceDetail` when handed a precomputed pace -- so a February cycle read 6% expected at 0% used and a 31-day cycle lost its pace token for a day. Both now resolve, which also fixes the eight other providers that carry the same sentinel. The CLI ignored the Notion settings snapshot entirely, so Workspace ID, a manual cookie header, and the `off` source had no effect on `codexbar`. Also drops a rolling window length that parses to exactly the monthly sentinel (`30d`, `720h`, `43200m`), which would otherwise be resolved as a calendar cycle ending hours from now. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com> Co-authored-by: Peter Steinberger <steipete@mac-studio-sf2.local> * release: 0.47.0 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix: make widget ProviderChoice display metadata a static literal The AppIntents metadata processor in current Xcode rejects computed caseDisplayRepresentations (must be a literal, exhaustive dictionary), which broke the 0.47.0 release build. Titles are pinned to the descriptor registry by WidgetProviderChoiceTests so providers cannot drift silently. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs: update appcast for 0.47.0 * docs: open 0.47.1 Unreleased Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Add scoped-weekly (Fable) percentage as a menu-bar layout token (#2440) * Add scoped-weekly (Fable) percentage as a menu-bar layout token Claude's model-scoped weekly carve-outs (the "Fable only" window from #1851) already show in the dropdown and in quota notifications, but the always-visible menu bar could only show session, weekly, or automatic. This adds a scoped-weekly token so the promo-window limit can sit in the bar. - New PercentWindow.scopedWeekly, shown with an "F" prefix, selectable in the layout editor (palette, label, live and representative previews). - scopedWeeklyWindow resolver matches the claude-weekly-scoped-* id prefix rather than a model name; when several are active it shows the most constrained one. - The scoped-weekly percent is added to the icon-observation signature so a scoped-only change refreshes the title, mirroring weekly= and avoiding the stale-title problem from #2300 and #2299. - Absent window renders the standard "–" placeholder; editor label localized. Addresses #2360. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * Address review: label scoped-weekly token by active model Codex flagged that the token hard-coded "F" / "Fable only" while the resolver accepts any claude-weekly-scoped-* window and picks the most constrained, so a non-Fable window could be shown under the wrong model label. - scopedWeeklyNamedWindow now returns the NamedRateWindow. - The .scopedWeekly token derives its prefix from the active window's title (first letter) and its accessibility text from the full title, instead of a fixed "F" / "Fable only". - Carry the title through MenuBarLayoutRenderData and include it in the icon-observation signature, so a model change refreshes the title. - Resolver test now asserts a non-Fable most-constrained window carries its title. The editor palette label stays "Fable %" (a proper noun that reads the same across locales); a generic name is left as a naming choice. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * Add menu bar pace layout tokens The 0.45 layout editor replaced the old Percent/Pace/Both display modes, but no token exposes the signed pace delta the Both mode used to render. `Runs out` is not a substitute: it answers when a window ends, always estimating from the weekly (or automatic) lane, while pace answers how far off the sustainable rate usage currently runs. Add Session/Weekly/Auto pace tokens that mirror the percent tokens' window selection and reuse the existing `MenuBarDisplayText.paceText` formatting (`+11%` ahead of the rate, `-8%` behind, `0%` on pace). Each token resolves pace for its own window, so Weekly pace never borrows the session delta. Pace needs the store's historical dataset and work-day setting, so it is resolved upstream like `runsOut` through a shared `menuBarLayoutPaceText` helper that both the status item and the editor preview call. The existing 3% expected-usage floor in `weeklyPace` still applies, so a token renders the en-dash placeholder early in a window while its siblings stay visible. Refs #2534 * Include layout pace tokens in the icon observation signature Pace values change with the historical dataset, the work-day setting, and the clock, none of which move the percent fields already hashed by providerStoreIconObservationSignature. A historicalPaceRevision bump therefore woke the icon observer but produced an unchanged signature, so updateIcons() was skipped and a custom pace token kept its stale value until an unrelated icon change forced a redraw. Contribute the active layout's pace values to the signature the same way cost and account tokens already do, gated on the layout actually containing a pace token. The regression test renders two snapshots with identical used percents but different resets: without this fix both signatures were identical. Refs #2534 * docs: credit menu bar pace contributor * fix: localize scoped weekly label --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> Co-authored-by: Petr Kratochvíl <krato@krato.cz> Co-authored-by: Peter Steinberger <steipete@mac-studio-sf2.local> * Add one-shot dashboard snapshot command (#2499) * Add one-shot dashboard snapshot command * Fix dashboard pricing refresh policy * Honor Cursor source policy in dashboard * test: dedupe cliExecutableURL helper after branch update Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Peter Steinberger <steipete@gmail.com> Co-authored-by: Peter Steinberger <steipete@mac-studio-sf2.local> Co-authored-by: Claude Fable 5 <noreply@anthropic.com> * feat: JS provider plugin runtime prototype (behind debug flag) (#2617) * feat: add provider plugin runtime * feat: convert synthetic venice and crof to JS plugins * test: prove JS provider parity * docs: document provider plugin prototype * fix: satisfy plugin runtime lint checks --------- Co-authored-by: Peter Steinberger <steipete@mac-studio-sf2.local> * fix: sync CLI config edits via iCloud (#2618) Co-authored-by: Peter Steinberger <steipete@mac-studio-sf2.local> * test: make widget snapshot storage hermetic (#2619) Co-authored-by: Peter Steinberger <steipete@mac-studio-sf2.local> * feat(menu): compact usage detail rows (#2620) Co-authored-by: Peter Steinberger <steipete@mac-studio-sf2.local> Co-authored-by: Shun Min Chang <ji394m6y7@gmail.com> * feat: add China Kimi and GLM quota routing (#2621) Co-authored-by: Peter Steinberger <steipete@mac-studio-sf2.local> Co-authored-by: haoli <haoli@local.dev> * feat: enrich Kimi monthly usage from desktop (#2622) Co-authored-by: Peter Steinberger <steipete@mac-studio-sf2.local> Co-authored-by: haoli <haoli@local.dev> * feat: declarative provider detail sections + JS detail providers (#2624) * feat: add declarative provider detail model * feat: render declarative provider details * feat: bridge provider details from JavaScript * feat: convert detail providers to JavaScript * style: satisfy provider detail test lint * fix: align z.ai plugin quota lanes --------- Co-authored-by: Peter Steinberger <steipete@mac-studio-sf2.local> * Unify pi and OMP agent sessions (#2626) * Add live OhMyPi agent session discovery Detect running OhMyPi harness processes, correlate them with bounded session metadata, and expose normalized sessions through the existing CLI and menu paths. * Address OhMyPi review correlation gaps Resolve OhMyPi metadata per live process environment, reject stale records, and isolate directory scan budget so existing provider discovery remains intact. Add focused regression coverage and document the fail-closed behavior. * Preserve existing session metadata during OhMyPi scans Give OhMyPi its own bounded directory budget so Codex and Claude correlation remain available, with regression coverage for the shared-provider behavior. * Preserve legacy session JSON compatibility Add an explicit v2 session payload for OhMyPi while keeping legacy remote clients decodable, and document and test the negotiated fallback. * Expose OhMyPi in local session JSON Keep the documented local --json output complete while preserving the remote v2-first compatibility fallback for older installations. Update CLI help, focused protocol coverage, and session documentation to distinguish current local output from legacy remote responses. * fix(sessions): preserve legacy JSON compatibility * Correct session JSON protocol overview * feat(sessions): unify Pi-family discovery Co-authored-by: William Mitchell <wdmitchell.uk@gmail.com> --------- Co-authored-by: William Mitchell <wdmitchell.uk@gmail.com> Co-authored-by: Peter Steinberger <steipete@mac-studio-sf2.local> * fix(zoommate): preserve browser cookie scope (#2627) Co-authored-by: Peter Steinberger <steipete@mac-studio-sf2.local> * Keep cached spend visible while Codex refreshes (#2628) * fix: show cached spend while refreshing Co-authored-by: hhh2210 <hzy2210@gmail.com> * test: add cached spend refresh proof --------- Co-authored-by: Peter Steinberger <steipete@mac-studio-sf2.local> Co-authored-by: hhh2210 <hzy2210@gmail.com> * feat: route GLM credentials by region (#2623) Co-authored-by: Peter Steinberger <steipete@mac-studio-sf2.local> Co-authored-by: haoli <haoli@local.dev> * Define Codex Fast cost as API Fast USD (#2632) * Centralize Codex priority pricing * fix: define Codex Fast USD pricing * test: split pricing expectation math for CI type-checker Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Bryan Font <bfont@me.com> Co-authored-by: Peter Steinberger <steipete@mac-studio-sf2.local> Co-authored-by: Claude Fable 5 <noreply@anthropic.com> * fix: parse Command Code usage windows (#2630) Co-authored-by: Peter Steinberger <steipete@mac-studio-sf2.local> Co-authored-by: Derek Zeng <zengzhuoxi@gmail.com> * feat: plugin cookie capability and host API extensions * feat: extend plugin host and convert providers * fix: align z.ai plugin credential routing --------- Co-authored-by: Peter Steinberger <steipete@mac-studio-sf2.local> * refactor: introduce ProviderInstanceID identity seam (#2636) * test: characterize provider instance identity * feat: add provider instance identity seam --------- Co-authored-by: Peter Steinberger <steipete@mac-studio-sf2.local> * feat: user-installed provider plugins with TypeScript support (#2642) * feat: add user-installed provider plugins * fix: broker owns plugin HTTP representation headers Apply broker-owned Accept, Accept-Encoding, and Content-Type after plugin-supplied headers so a plugin cannot relax the user-plugin response boundary. Test transport gains configurable response headers. * build: add reproducible sucrase bundle verification script Regenerates and verifies Sources/CodexBarCore/Resources/Plugins/ sucrase-3.35.1.min.js from the official npm artifact (sucrase@3.35.1, esbuild@0.25.8 pinned, IIFE browser bundle). Expected SHA-256 4d997e15b72cbc9ccf6e743c30c6eb48bf4533f6709852367b40766be5eba70b was independently reproduced by the coordinator from the npm registry; 'check' mode fails closed on any mismatch. * fix: gate user-plugin registry lookup for non-JavaScriptCore platforms Linux CLI builds compile CodexBarConfig without the plugin runtime; unknown plugin config entries are dropped with the existing warning, matching the documented macOS-only plugin boundary. --------- Co-authored-by: Peter Steinberger <steipete@mac-studio-sf2.local> * test: deflake Kimi grace budget timing (#2657) Co-authored-by: Peter Steinberger <steipete@mac-studio-sf2.local> * test: keep widget-snapshot container I/O out of test runs (#2656) * test: keep widget-snapshot container I/O out of test runs AdaptiveRefreshHeuristicsTests (and any suite reaching persistWidgetSnapshot without a save override) could hang forever on macOS 26 hosts: WidgetSnapshotStore.load() opens the real app-group container file and the open() can block indefinitely behind app-data (TCC) gating. Gate persistWidgetSnapshot so test runs only persist when a test explicitly installs _test_widgetSnapshotSaveOverride, and pin the behavior with regression tests. * test: open widget-snapshot gate for injected snapshot URLs Main's plugin series added a widgetSnapshotURL injection seam that promotion tests rely on; the gate now admits it alongside the save override and replaces the duplicate inline DEBUG guard. --------- Co-authored-by: Peter Steinberger <steipete@mac-studio-sf2.local> * feat: add shared-cache token activity heatmap (#2625) * feat: add standalone token activity heatmap * fix: preserve token activity coverage * fix: complete activity heatmap navigation * fix: expose daily activity accessibility * fix: publish daily accessibility semantics * fix: expose dates in activity accessibility * fix: expose weekly activity accessibility * feat: derive token activity from shared cache --------- Co-authored-by: Yuxin Qiao <104957188+Yuxin-Qiao@users.noreply.github.com> Co-authored-by: Peter Steinberger <steipete@mac-studio-sf2.local> * Expose Codex scan completeness in cost JSON (#2520) * Expose provider-native cost scans * feat(cli): expose Codex cost scan completeness Co-authored-by: NickGuAI <yu.gu.columbia@gmail.com> --------- Co-authored-by: Peter Steinberger <steipete@mac-studio-sf2.local> * refactor: refresh Plugins pane visuals to match sibling settings panes (#2662) * feat: resizable settings sidebar with persisted width (#2660) * feat: resizable settings sidebar with persisted width Replace the fixed 260pt settings sidebar with a drag-resizable one (200-380pt, persisted via AppStorage). The divider stays the original SwiftUI hairline; an input-only AppKit strip overlaid on the detail pane's leading edge handles dragging, so the HStack layout is untouched. Hover cursor via .pointerStyle(.columnResize) on macOS 15+ plus an explicit-rect tracking area (visibleRect spans the whole window under NSHostingView, so .inVisibleRect never fired). * fix: hold the sidebar resize cursor with a cursor rect The hover cursor was set imperatively on mouse enter/move, which SwiftUI undid on its next render — the settings panes re-render continuously, so the resize cursor only flashed. Cursor rects are declarative: AppKit re-asks the view on every invalidation, so the cursor holds. Measured on a probe harness hosting this view under a 20Hz re-render loop, sampling NSCursor.currentSystem at 25Hz while an external driver moved the pointer: imperative-only gave 0% resize on hover and 40% during drag; with cursor rects, two clean transitions (arrow -> resize on entry, back on exit) and 100% held across hover and drag. * style: fix SwiftFormat violations in SidebarResizeHandle Mechanical ./Scripts/lint.sh format output; #2660 landed with docComments and wrap-body violations that broke CI on main. * Wait for OpenCode Go Zen balance in CLI usage reads (#2583) * Wait for OpenCode Go Zen balance in CLI usage reads * fix: bound OpenCode Go CLI balance lookup * Keep OpenCode Go Zen wait scoped to usage reads * Measure OpenCode Go Zen wait from task start --------- Co-authored-by: Peter Steinberger <steipete@mac-studio-sf2.local> * OpenCode Go: per-model cost breakdown by day (#2649) * feat: per-model cost breakdown for OpenCode Go's daily cost history Extract each local assistant message's modelID from opencode.db (the real model behind the constant opencode-go Zen-proxy providerID) and group cost/request counts by (day, model) instead of just by day, so the shared Cost history chart shows a per-model breakdown for OpenCode Go the same way it already does for Claude/Codex. Rows with no modelID fall back to an "unknown" bucket instead of being dropped. * fix: use lowercase docs/claude.md link to pass case-sensitive CI lint macOS resolves docs/CLAUDE.md to the same file as docs/claude.md, but the Linux lint runner's case-sensitive filesystem doesn't, failing the documentation-link check. * fix: normalize the trimmed model id when grouping OpenCode Go costs The grouping key used the untrimmed modelID even though emptiness was checked against the trimmed value, so a model id with incidental leading/trailing whitespace would form its own bucket instead of merging with the clean value for the same model. Group by the trimmed value consistently, and add regression tests for whitespace-only and whitespace-padded model ids. * Fix ETXTBSY race in claude-swap CLI card test (#2644) * Fix ETXTBSY race in claude-swap CLI card test The test wrote a shell script, set mode 0755, and executed it immediately. Under swift test --parallel a concurrent fork inherits the still-open write descriptor, so execve fails with ETXTBSY and the launch reports Cocoa 256. Write the script body as data and execute a checked-in trampoline that reads it, so execve only ever touches a file no test process has written. Measured in swift:6.3.3-noble on arm64: 112 failures in 600 attempts with the old shape, 0 in 600 with the trampoline. * Apply ETXTBSY fix to the Linux test target The first commit only touched Tests/CodexBarTests, which Package.swift builds on macOS alone. The Linux job compiles CodexBarLinuxTests from TestsLinux, so the race remained in the target that actually flaked. Convert all three TestsLinux sites through a shared FakeExecutable helper, and keep the macOS mirror in step. Verified on Linux arm64 in swift:6.3.3-noble: 355 tests in 53 suites pass. * Pin Perplexity promo expiry formatter to en_US_POSIX (#2651) * Pin Perplexity promo expiry formatter to en_US_POSIX The formatter set dateFormat but not locale, so MMM rendered in the user's language inside an otherwise English string and diverged from the JS plugin projection. Matches the en_US_POSIX pinning already used by every other provider formatter. Refs #868 * Assert the Perplexity promo expiry locale pin in tests Parity coverage only trips on a non-English host, so an English runner passes both before and after the regression. Add assertions that hold on every host: the formatter keeps its en_US_POSIX locale, and the rendered month stays ASCII English. * Move the promo expiry fixture off the UTC month boundary The formatter keeps the host time zone, so a midnight-UTC January instant renders as Dec 31 west of UTC and failed the month assertion there even with the locale pin correct. * Decode Copilot credits_used for token-billed seats (#2593) (#2613) * Decode Copilot token-billing credits used * Cover credits used counter in token-billing card regression * Keep Copilot credits counter accessible through snapshots * Preserve Copilot credits counter across quota fallback * Expose Copilot credits counter in diagnostics * Keep credits counter on zero entitlement quota fallback * fix: keep Codex fork catch-up progress across appends (#2648) * fix: keep Codex fork catch-up progress across appends * Keep subagent fork appends on full rescan * Preserve zero-work subagent retries * Report unreadable Claude OAuth refresh as terminal (#2650) * Report unreadable Claude OAuth refresh as terminal When the delegated Claude CLI touch completes but the Keychain fingerprint does not move, the attempt reported a retryable failure. With foreign Keychain reads disabled in release and no credentials file for the profile, that state cannot be retried out of: the refreshed credential is unreadable no matter how often the touch runs. Add Outcome.unreadableAfterRefresh for that case, back off on the long cooldown instead of the short one, and drop the 'run claude login, then retry' advice that cannot help. The touch still runs first, so older Claude Code versions that write the credentials file keep recovering. Refs #2634 * Keep the delegated refresh Outcome contract public-stable CodexBarCore exports Outcome through its library product, so the added case would break downstream exhaustive switches on source update. Move the unreadable verdict onto an internal AttemptResult alongside the outcome and restore the original public cases; the fetcher reads the detail through attemptDetailed. * Cover retryable touch failure in unreadable configuration The touch-error path staying retryable (short cooldown, no terminal verdict) is deliberate: the error may be transient, and on older Claude Code a retried touch can still create the credentials file. Document that at the decision site and lock it in with a regression test so the SessionError variant from #2634 has explicit coverage. --------- Co-authored-by: Peter Steinberger <steipete@gmail.com> * test: stabilize PTY overflow proof (#2664) * Classify Codex rate windows by duration (5h/weekly/30-day) (#2600) * Classify Codex rate windows by duration * Keep monthly Codex history visible in the chart * Suppress session pace for non-session Codex windows * Preserve monthly Codex windows in reset backfill and keep fallback session pace - Reset backfill rebuilt raw slots only from the session/weekly lanes, so a 43,200-minute primary (now classified .monthly) was dropped or overwritten by a stale cached session window whenever a trusted backfill baseline existed. Preserve monthly-classified slot windows in place and backfill their reset from a matching cached monthly window. - The session-pace guard rejected every non-300-minute Codex window, removing existing pace for fallback session durations (e.g. 540 minutes or nil). Suppress pace only for windows that classify into the weekly/monthly lanes, and let the central guard drive the card instead of exact-duration gates. - Move the Codex reset-backfill helpers into their own file (file-length lint). - Regression tests for both paths; they fail without the fixes. * Retain monthly reset baselines in codex backfill merge --------- Co-authored-by: Peter Steinberger <steipete@gmail.com> * Retain priced Codex models when Auto Review is unpriced (#2643) * Retain partial Codex model rows * Use safe Codex dashboard test fixture * Fail closed for malformed Codex model costs * Add regression test for fully unpriced Codex routing history * Extract model breakdown helpers to satisfy type body length --------- Co-authored-by: Peter Steinberger <steipete@mac-studio-sf2.local> Co-authored-by: Peter Steinberger <steipete@gmail.com> * Bound Codex cost cache persistence size (#2637) (#2646) * Bound Codex cost cache persistence size * Preserve resumable and fork-dependent cache entries * Prune cache against requested window * Prune candidate artifacts over byte budget * Scope cache guards and preserve active sessions * Keep cache loadable under byte budget * Mark trimmed caches for catch-up and prune discovery * Bound sole oversized entries and reset discovery cursors * Mark stripped entries incomplete * Preserve full report and live fork protection during trim * Compact protected fork parents over budget * Enforce byte cap after underestimated encodes * Exclude lineage-only parents from fork protection * Re-encode after forced prune and bound lookback state * Use report window for catch-up and exclude lineage parents * Compact parents required by kept trim survivors * Prune orphaned discovery mappings under budget * Preserve lookback work and align report bounds * fix: lower cost-cache load cap and never persist a refusable artifact The 1 GiB load cap still let a 256 MiB-1 GiB legacy artifact be decoded in one shot, which is exactly the MALLOC_LARGE multi-GiB spike #2637 traced. Since save now bounds Codex artifacts to 256 MiB, anything meaningfully above the budget is legacy and cheaper to rebuild bounded; drop the cap to 320 MiB (budget + enforcement slack). Also make save uphold the loader's contract: when budget enforcement cannot shrink the payload below the load cap (unstrippable resume or buffered state), remove the artifact instead of writing one that every launch would decode just to refuse. * Preserve recursive lookback roots and share ID capacity * Keep legacy lookback roots in the scan queue * Preserve complete report across repeated trims --------- Co-authored-by: Peter Steinberger <steipete@gmail.com> * docs: changelog for triage merge batch (#2649, #2613, #2600, #2646, #2648, #2650, #2643, #2651) * Use OpenRouter server remaining for key limit meter (#2612) * Use OpenRouter server remaining for key limit meter * Treat negative OpenRouter remaining as exhausted quota * Align JS OpenRouter meter with Swift quota path * Clamp JS OpenRouter server remaining to key limit Match the Swift quota path's inclusive [0, keyLimit] clamp so a server remaining above the configured limit renders 0% used instead of suppressing the meter. Adds an above-limit Swift/JS parity fixture. * Validate selected OpenRouter fallback quota value --------- Co-authored-by: Peter Steinberger <steipete@mac-studio-sf2.local> Co-authored-by: Peter Steinberger <steipete@gmail.com> * docs: changelog for OpenRouter key-limit fix (#2612) * build: sync widget-extension Package.resolved to SweetCookieKit 0.5.2 The root package moved to SweetCookieKit 0.5.2 but the widget project's pinned resolved file lagged at 0.5.1, breaking Scripts/package_app.sh (xcodebuild runs with automatic resolution disabled). CI does not build the widget xcodeproj, so this only surfaced in local packaging. * feat: localize Plugins pane (#2663) * feat: localize Plugins pane * docs: note Plugins pane localizations * refactor: migrate provider payloads onto declarative details model (#2658) * refactor: migrate provider details batch one * refactor: migrate provider details batch two * refactor: migrate provider details batch three * fix: preserve provider detail rendering parity * fix: migrate Copilot credits into provider details * fix: pin Poe details to UTC Poe timestamps are UTC and the daily buckets already used it, but the recent-activity labels and Today bucket used the local zone in both the Swift and JS paths, flaking the golden on non-Pacific runners. Golden proven invariant under TZ=UTC, America/Los_Angeles, Asia/Tokyo. * fix: normalize OpenRouter reset detail * test: pin Poe menu fixture to UTC * fix: preserve OpenRouter key details --------- Co-authored-by: Peter Steinberger <steipete@mac-studio-sf2.local> * refactor: decompose provider settings snapshot into provider-owned sections (#2659) * refactor: decompose provider settings snapshots * fix: restore empty provider settings factory --------- Co-authored-by: Peter Steinberger <steipete@mac-studio-sf2.local> * refactor: namespace provider-specific config fields into provider folders (#2661) * test: characterize provider config JSON bytes * refactor: namespace provider config fields --------- Co-authored-by: Peter Steinberger <steipete@mac-studio-sf2.local> * test: deflake refresh coalescing cleanup (#2672) * fix: bound widget-snapshot file I/O with timeout and circuit breaker (#2673) On macOS 26, opening the app-group snapshot can wedge indefinitely behind container or TCC I/O. Run snapshot reads and writes on bounded detached threads, then stop further process-local container access after the first timeout. Co-authored-by: Peter Steinberger <steipete@mac-studio-sf2.local> * fix: stop infinite Overview flicker from agent-session rescan feedback loop (#2652) (#2674) Hovering between provider chart submenus on the Overview tab with Agent Sessions and Cost Summary enabled could flicker forever. Every menu open (including each hover-opened hosted chart submenu) kicked off an agent-session rescan whose completion unconditionally invalidated all menus and rebuilt the tracked parent in place. Overview parents cannot smart-update, so the structural rebuild replaced the hovered row and force-closed its submenu; the reopen triggered another rescan, closing the loop. Three bounded changes break it: - AgentSessionsStore no longer publishes rescans that reproduce the current local/remote session content. - menuWillOpen triggers the session rescan (and menu-open note) only for root menus, not hover-opened submenus. - Session updates invalidate menus with deferred tracked-parent rebuild and stale-content preservation, matching the store-observation path. Co-authored-by: Peter Steinberger <steipete@mac-studio-sf2.local> * refactor: provider-owned credential adapters (#2676) * test: characterize provider credentials * refactor: add provider credential adapters * refactor: register provider cookie settings * refactor: move credential policy to providers * fix: qualify static credential adapter --------- Co-authored-by: Peter Steinberger <steipete@mac-studio-sf2.local> * refactor: cut over Crof and Venice plugins (#2677) Co-authored-by: Peter Steinberger <steipete@mac-studio-sf2.local> * feat: classified errors, deadlines, and overrides for JS plugins; cut over four more providers (#2678) * feat: classify plugin fetch failures * feat: add plugin request deadlines * feat: support plugin request overrides * refactor: cut over OpenRouter plugin * feat: map plugin data confidence * refactor: cut over ClawRouter plugin * refactor: cut over Deepgram plugin * refactor: cut over sub2api plugin * style: satisfy plugin cutover lint --------- Co-authored-by: Peter Steinberger <steipete@mac-studio-sf2.local> * test: accept GMT/UTC alias in sub2api timezone golden (#2680) * test: accept GMT/UTC alias in sub2api timezone golden Foundation reports GMT where JS Intl reports UTC on UTC-configured machines; the plugin sends the real current zone via Intl. Proven under TZ=UTC, America/Los_Angeles, Asia/Tokyo. Unbreaks main CI after the #2678 admin-merge left this red on UTC runners. * style: satisfy redundant-type lint in sub2api golden --------- Co-authored-by: Peter Steinberger <steipete@mac-studio-sf2.local> * refactor: derive the remaining per-provider bookkeeping from descriptors (#2682) * refactor: derive widget colors from provider branding * refactor: generate provider manifests from one registry * refactor: colocate provider cookie import priorities * refactor: move share plan labels into descriptors * refactor: move debug defaults into provider descriptors * refactor: derive debug pane provider curation * refactor: derive provider presentation capabilities * refactor: unify standard provider config bindings * style: remove stale lint suppression * fix: use grep instead of rg in manifest regeneration script CI lint runners do not have ripgrep; grep -rlE --include is portable and matches the same files. --------- Co-authored-by: Peter Steinberger <steipete@mac-studio-sf2.local> * refactor: unify CLI onto descriptor-owned provider capabilities (#2685) * test: characterize CLI provider policy output * test: exempt CLI JSON golden from body limit * test: scope CLI golden lint exemption * refactor: derive CLI rendering from provider descriptors * refactor: derive CLI source availability from descriptors * refactor: finish descriptor-owned CLI credentials * refactor: derive plan history policy from descriptors * refactor: document CLI provider-specific boundaries --------- Co-authored-by: Peter Steinberger <steipete@mac-studio-sf2.local> * refactor: derive remaining provider registries and correct authoring docs (#2686) * refactor: derive config validation capabilities * refactor: derive provider log categories * refactor: derive menu metric capabilities * build: derive provider manifest order * fix: preserve provider bootstrap order * test: derive credential gatekeeper coverage * docs: document provider registration truth --------- Co-authored-by: Peter Steinberger <steipete@mac-studio-sf2.local> * refactor: move usage presentation policy into provider descriptors (#2688) * test: pin provider presentation policy * refactor: move usage window policy into descriptors * refactor: move usage menu policy into descriptors --------- Co-authored-by: Peter Steinberger <steipete@clawstudio.local> * refactor: derive or justify every remaining provider special case (#2691) * refactor: derive settings provider special cases * refactor: justify usage store provider state * refactor: derive controller provider policy * refactor: derive cli provider capabilities * refactor: justify scanner provider ownership * test: gate provider special case clusters * build: refresh codex parser hash * style: satisfy special case gate lint * style: accept cli cards command size --------- Co-authored-by: Peter Steinberger <steipete@clawstudio.local> * test: harden the provider special-case gatekeeper and derive its findings (#2693) * test: harden provider architecture gatekeeper * refactor: derive provider presentation policies * test: pin provider dispatch constructs * test: document gatekeeper catalog lint scope --------- Co-authored-by: Peter Steinberger <steipete@clawstudio.local> * refactor: close remaining provider-architecture gaps and declare gatekeeper scope (#2694) * refactor: close provider architecture gaps * refactor: triage loose provider references * test: harden provider architecture gatekeeper * docs: declare gatekeeper threat model --------- Co-authored-by: Peter Steinberger <steipete@clawstudio.local> * refactor: close provider architecture gaps (#2698) Co-authored-by: Peter Steinberger <steipete@clawstudio.local> * test: close provider gatekeeper gaps (#2699) Co-authored-by: Peter Steinberger <steipete@clawstudio.local> * fix: harden provider gatekeeper lexer (#2700) Co-authored-by: Peter Steinberger <steipete@clawstudio.local> * fix: scope provider gatekeeper suppressions (#2702) * test: broaden provider architecture gatekeeper (#2704) * test: normalize inline block comments in gatekeeper scanning (#2705) Blank single-line block comments with index-preserving spaces so punctuation adjacency stays visible to position heuristics; document nested-suppressed-call attribution and multi-line block comments as parser-requiring out-of-scope patterns. * fix: retain flicker-probe sessions so release builds actually record (#2709) ProbeSession(...).begin() ran as an unretained temporary whose only other reference was the driving timer's [weak self], so release-mode builds deallocated the session at creation (compiler: "weak reference will always be nil") and the probe recorded nothing. Sessions are now strongly owned in MenuSwitchFlickerProbe.activeSessions until finish() unregisters them, and ProbeSession gained a Configuration seam (timings + menu opener) so a test can run a full session against a synthetic merged menu and prove it records switch activity and frame samples. * feat: nest claude-swap accounts in dashboard snapshot (#2713) * Fix Codex manual reset refresh (#2710) * Fix Antigravity agy cold-start quota readiness wait (#2665) * fix(serve): bound the whole request head, not just each read (#2684) * fix(serve): bound the whole request head, not just each read `readRequest` applied `requestReadTimeoutMilliseconds` per `recv` with no overall limit, so a client trickling one byte just inside that window never timed out and held its connection — and the cooperative-executor thread serving it — for as long as it kept sending. The Host allowlist and bearer-token check both run after the head is read, and over-cap connections are closed rather than queued, so a few such clients denied service to well-behaved ones entirely pre-auth. Track a monotonic start time and cap each wait at the remaining budget, failing the request once the overall deadline passes. A client that merely goes silent was already handled by the per-read timeout; this covers the one that keeps sending. Verified red→green on Linux: before, a legitimate client never got a slot within a 25s budget; after, it is served at 10.26s, matching the deadline. * fix(test): gate the serve deadline suite to Linux `TestsLinux` is declared unconditionally in Package.swift, so this file also compiles on macOS, where the raw socket calls (`SOCK_STREAM.rawValue`, Glibc-only imports) do not build and broke both macOS test shards. Wrap the file in `#if canImport(Glibc) || canImport(Musl)`, matching AntigravityProcessLauncherLinuxTests and the other syscall suites here. * test(serve): inject a short deadline so the suite cannot stall others The deadline test held executor threads for the full 10s production budget, which starved unrelated suites: CLIHooksWatchSleepLinuxTests asserts a stop signal is honored within 2s and instead measured 10.31s on CI. `.serialized` only orders cases within a suite, so it does not prevent that. Make the budget injectable on CLILocalHTTPServer, defaulting to the production value, and have the test pass 1500ms. Full suite drops from ~10.5s to ~2.0s. Verified the test still fails when the deadline logic is disabled (12.37s starvation), so the shorter budget did not weaken it, and ran the full suite 8x with no flake. * docs: add CodexBar Meter to Linux desktop integrations (#2696) * feat: add KRW to the preferred currency picker (#2669) KRW was the one major Asian currency missing from the picker added in #2490, so won-billed users converted USD estimates by hand. No new exchange-rate logic is needed: fetchLatestRatesIfNeeded already merges every rate in the open.er-api.com payload, which carries KRW. Listing the code in supportedCurrencies is what opens the requiresLiveRates gate, so selecting KRW triggers the same live-fetch and 24h-cache path as the existing currencies. The hardcoded fallback rate is only read before the first successful fetch. KRW is zero-decimal and currencyString pins en_US, so ICU resolves the fraction digits and won renders without a fractional separator on any host. Refs #2449 Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> * Align cost cache save overshoot with documented load cap (#2703) * Align cost cache save overshoot with documented load cap * Keep preceding codex cache producer key compatible * Keep current main codex cache producer key compatible * fix: accept all shipped codex producer keys back to #2632 --------- Co-authored-by: Peter Steinberger <steipete@gmail.com> * fix(commandcode): Add support for individual-goat plan ($70/mo credits) (#2706) * fix(commandcode): Add support for individual-goat plan (/mo credits) * fix(commandcode): Support new commandcode_prod_.session_token cookie name * fix(packaging): Restore widget extension packaging in package_app.sh * fix(commandcode): preserve legacy bare-token cookie fallback Keep __Secure-better-auth.session_token as the bare-token default until a renamed production cookie is proven live; accept the new commandcode_prod_ cookie names additively with regression coverage for both name families. --------- Co-authored-by: Peter Steinberger <steipete@gmail.com> * docs: changelog for review round (#2710, #2665, #2684, #2706, #2703, #2669) * feat(cli): add built-in web dashboard to codexbar serve (#2715) GET / now serves a self-contained HTML page that polls /dashboard/v1/snapshot and renders provider cards with usage windows, credits, and cost. The static UI stays unauthenticated (it carries no account data); tokens are entered in-browser, kept in localStorage, and sent as a bearer header. No visibility gating on polling so embedded panes that report document.hidden permanently still refresh. * Document Antigravity data sources when the app is closed (#2666) * Document Antigravity sources when the app is closed * docs: keep Antigravity experimental tag and reflect cold-start readiness wait (#2665) --------- Co-authored-by: Peter Steinberger <steipete@gmail.com> * fix(cli): deliver late serve results instead of discarding them (#2717) When a serve operation outlived its request deadline, the coordinator marked the slot timed out and threw away the eventually-produced value: it never reached the accept commit (response cache) and the queued same-config successor restarted the source from zero. On machines where the dashboard snapshot build outlasts --request-timeout, GET /dashboard/v1/snapshot 504ed forever while each retry burned another full cost-history scan. Timed-out sources now still commit through accept and hand the fresh result to a same-fingerprint successor without rerunning the source; changed-config successors promote as before. The web UI shows a friendlier 504 message noting it retries automatically. * feat(cli): add --identity redacted|full to the dashboard snapshot command (#2716) The one-shot `codexbar dashboard` always redacted account identity even though the builder supports full mode. Personal dashboards on trusted private networks (e.g. a tailnet portal) want real account emails. The serve HTTP transport is unchanged and stays always-redacted; --identity none is rejected. * feat(cli): add --output <path> to the dashboard snapshot command (#2719) Static-webroot publishers currently shell-wrap the one-shot command (codexbar dashboard > tmp && install tmp target) to avoid readers seeing a partial document. --output writes the snapshot atomically instead: staged temp file in the destination directory, fsync, rename(2) over the target, 0644. Stdout stays untouched when the flag is absent and silent on success when writing to a file. Empty paths are an args error and a missing parent directory fails with a clear message; directories are deliberately not created. * feat(serve): render claude-swap accounts in the web dashboard; add --identity full (#2720) The built-in web dashboard ignored the dashboard-v1 accounts[] array, so multi-account Claude setups showed a single ambient row, and the serve transport's always-redacted identity left the email unknown. Per-account sections now replace the ambient windows when accounts are present (label fallback when identity is redacted), accountsError surfaces on the card, and a serve-level --identity redacted|full startup flag reuses the dashboard command's decoder so trusted private deployments can show real emails. Default stays redacted. * docs: retitle unreleased to 0.48.0 and order changelog by user impact * feat(serve): spend charts, provider brand icons, grouped card layout for the web dashboard (#2722) The web dashboard showed only aggregate cost numbers while the app has graphs, used a plain top-stripe accent, and nested claude-swap accounts as rows inside one card. It now fetches /cost daily buckets and renders an inline SVG spend chart per provider (failures never block the snapshot render), serves the app's provider brand SVGs from an embedded /icons/ route (names validated against the embedded set; immutable cache), and lays out multi-account providers as one card per account under a titled group with status pills, replacing the border stripe with an icon-led header. * fix(serve): hide the web dashboard status chip when no provider status exists (#2723) The serve snapshot collector never fetches provider status pages, so status is always nil and every card rendered a gray 'unknown' chip. Render the chip only when the snapshot actually carries status data. * chore: bump version to 0.48.0 (112) and date the changelog * style: fix 69 SwiftLint violations from serve web-dashboard merges The serve dashboard PRs (#2715-#2723) merged during the GitHub Actions outage without CI lint validation. Mechanical, behavior-preserving fixes: - CLIServeProviderIcons.swift: generator now emits scoped swiftlint:disable/enable line_length around the base64 data (icon payload is byte-identical after regeneration) - CLIServeWebUI.swift: moved the html template constant to CLIServeWebUI+HTML.swift (enum body 1093 -> under 800 lines); the string literal content is verbatim-identical - Data->String conversions switched to failable String(bytes:encoding:) with fallbacks, matching repo convention - Long @Option help strings wrapped via adjacent + concatenation * chore: publish 0.48.0 appcast entry * chore: open 0.48.1 unreleased changelog section * fix: make usage labels follow bar fill (#2744) * feat(cli): paint the serve dashboard instantly and stream data in (#2746) Three layers, one goal: never make the browser wait on a snapshot build (measured 46.5s cold vs 22ms warm on the portal host). - Stale-while-revalidate: expired cache entries within staleTTL answer immediately from the last-good response while a coalesced background rebuild commits fresh data. Applies to /usage, /cost, and the dashboard snapshot; failure-fallback rules are unchanged. - Snapshot query params (schema stays v1): ?provider=<id> builds and caches one provider row independently; ?detail=shell returns config-only rows with no provider fetches or cost scans. - Web UI: renders the last snapshot from localStorage instantly on revisit, paints skeleton cards from the shell on cold start, fills each card as its provider resolves, then hands off to normal polling. * fix: pull 0.48.0 from the appcast — packaged app crashes at launch loading the CodexBarCore resource bundle (#2738, #2730) * fix: resolve CodexBarCore resource bundle safely in packaged apps 0.48.0 crashed at launch on every machine without the build worktree: the plugin runtime's Bundle.module accessor only probes the app root and the compiled-in build path, but the packaged app ships the bundle in Contents/Resources. Route all CodexBarCore resource loads through a safe resolver (mirroring the existing localization/brand-icon pattern), and add a gate test forbidding raw Bundle.module in CodexBarCore. Fixes #2738. Fixes #2730. * chore: bump version to 0.48.1 (113) and date the changelog * feat(cli): default dashboard identity to full emails (#2748) Redaction was the dashboard-v1 default at every layer; the --identity flag already existed on both the one-shot command and serve. Flip the default to full identity and keep --identity redacted as the opt-in privacy setting for snapshots that cross untrusted networks. The web UI stops re-redacting rows before persisting them to localStorage so cached paints match what the server serves in either mode. * docs: update appcast for 0.48.1 * Decode Codex monthly credit limit from spend_control.individual_limit (#2737) * Decode Codex monthly credit limit from spend_control * Keep spend_control below root and rate_limit precedence * Centralize credit-limit precedence in resolvedIndividualLimit * docs: move Codex spend_control credit-limit entry to 0.48.2 (#2737) * fix(cli): keep claude-swap account emails when the usage fetch fails (#2752) An account's name was derived only from its usage snapshot, which the projection returns as nil for any status other than ok. Accounts with expired or missing credentials therefore lost their identity and fell back to the hardcoded "Account N" slot label, even though claude-swap reports an email for every slot and the menu bar app shows it. Resolve the email from the projection's displayLabel when the snapshot has none, and feed that single value through the existing identity-mode treatment for both label and identity so redacted mode stays honest. * fix(zai): parse CREDIT_LIMIT quota entries; restore 5-hour primary reset (#2751) * fix(zai): parse CREDIT_LIMIT quota entries from credit-based plans z.ai's GLM Coding Plans (lite/standard/pro) now return CREDIT_LIMIT instead of TOKENS_LIMIT from the quota API. Both the Swift parser and the bundled zai.js plugin rejected the unknown type, leaving accounts stuck at 100% remaining / 0% used and letting the reset display fall back to a non-5-hour limit. Treat CREDIT_LIMIT like TOKENS_LIMIT for window selection, window minutes, and the 5-hour reset description; label credit plans as "Credit quota" / "Session credit quota"; accept "level" as a plan name in the plugin for Swift parity. Adds a CREDIT_LIMIT Swift/JS parity fixture. Squashed from PR #2725. Fixes #2724 * test(zai): lock CREDIT_LIMIT parsing and 5-hour reset selection (#2724, #2712) Parser-level regression tests built from the exact payload reported in #2724: the 5-hour credit window becomes the primary lane (300-minute window, 3.55% used, "5-hour" reset description) and the weekly credit window the secondary, with the primary reset timestamp taken from the 5-hour entry rather than a fallback limit's longer schedule (#2712's reported symptom). Adds the changelog entry crediting @stuible. --------- Co-authored-by: Josh Stuible <joshstuible@gmail.com> * fix(claude): render the menu-bar indicator from the active claude-swap account (#2750) * fix(claude): render the menu-bar indicator from the active claude-swap account When the claude-swap adapter owns Claude account presentation (2+ accounts, or 1 with the single-account opt-in), the menu renders adapter account cards while every menu-bar icon path still read the ambient Claude snapshot. With an ambient probe that yields no usable rate windows, the bar drew empty even though the adapter had fresh usage for the active account. Route the icon render, layout/reset scheduling, unified-icon provider pick, highest-usage ranking, icon observation signature, and switcher mini-bars through a single menu-bar presentation-snapshot selector that prefers the active claude-swap account snapshot and falls back to the ambient snapshot when the adapter is disabled, below its presentation threshold, or the active account has no usable usage. Fixes #2731 * test: reconcile provider gatekeeper anchors for cswap menu-bar fix * build: add packaged-app launch smoke check to the packaging pipeline (#2755) 0.48.0 crashed at launch on every machine without the build checkout because SwiftPM's Bundle.module accessor only probes the app root and the compile-time build path; local testing passed only because the build machine still had the checkout at that path. Release packaging now copies the packaged app to a temp dir and runs it with sandbox-exec denying every file read under the checkout: a deterministic CODEXBAR_RESOURCE_SMOKE=1 probe forces the resource loads that trapped in 0.48.0 (they are lazy, so a liveness check alone cannot reach them), then a 6s direct-launch liveness check guards other startup fatals. Direct binary spawn skips LaunchServices and cleanup kills only the spawned PID, so the check cannot fight a running production CodexBar. Headless runs hard-fail only on the resource-bundle trap signature. Pipeline guard suggested by the reporter of #2738. * feat: portable QuickJS plugin engine; run JS providers on Linux (#2753) * build: vendor pinned quickjs-ng engine * feat: add portable QuickJS plugin engine * refactor: remove Linux provider twins * fix: use canImport C-library chain in CLIPluginsCommand for musl * Fix CLI resource bundle resolution and packaging (#2757) * fix: ship and resolve the CodexBarCore resource bundle for CLI contexts Fixes #2756 * fix: support Linux CLI resource layouts * Add codexbar-cosmic-applet to Linux desktop integrations (#2734) * Handle Codex reset credits omitted after redemption (#2728) * Handle consumed Codex reset credits omitted by provider * Exclude expired Codex reset credits from consumption detection * docs: changelog for omitted reset credits fix (#2728) * Kimi: hide Code 7-day row when it duplicates the weekly lane (#2741) * Kimi: hide Code 7-day row when it duplicates the weekly lane GetUsages (FEATURE_CODING detail) and GetSubscriptionStats (ratelimitCode7d) report the same 7-day Code quota through two endpoints; live data shows identical usage and reset times. Hide the extra row when both lanes agree (within 1pt and 5 minutes), keep it when they diverge or the weekly detail is unreliable. * Require positive evidence before hiding Kimi Code 7-day row * Align Kimi lanes with official usage naming * fix: align menu bar reset countdown with menu (#2735) * docs: changelog for Kimi lane and countdown fixes (#2741, #2735) * feat: cut over Synthetic, Poe, xAI, and z.ai to JavaScript on all platforms (#2758) * feat(synthetic): cut over provider to JavaScript * feat(poe): cut over provider to JavaScript * feat(xai): cut over provider to JavaScript * feat(zai): cut over provider to JavaScript * style(zai): use predicate count * test: give the hung-script watchdog assertion CI headroom The 0.15s interrupt must fire promptly, not wait out the hang; the 1s elapsed bound flaked at 1.66s on a loaded ARM64 runner. 5s still proves prompt termination against an unbounded loop. * test: give hooks-watch interrupt assertion CI headroom The 0.3s stop signal must beat the 10s interval; the 2s elapsed bound flaked at 2.02s on a loaded x64 runner. 5s still proves prompt interruption. * docs: permit opt-in Claude statusLine JSON as an explicit data source (#2733) * Menu: let metric meta and reset rows wrap instead of truncating (#2742) * Menu: let metric meta and reset rows wrap instead of truncating Compact usage detail rows (#2620) put all pace detail on one line and moved reset times into the title row; longer locales (e.g. Chinese) truncate tail content. Allow the meta line and reset label to wrap to two lines so all information stays visible while keeping the compact layout. * Menu: track wrapping metric text in height-cache fingerprint * fix: track reset title width in height cache --------- Co-authored-by: Peter Steinberger <steipete@gmail.com> * docs: changelog for wrapping metric rows (#2742) * feat: add SQLite CostUsageStore foundation (refs #2760) (#2761) * feat: cut Codex cost persistence over to SQLite, delete the JSON artifact (refs #2760) (#2762) * feat: cut Codex cost persistence over to SQLite * docs: document SQLite cost history storage * test: reconcile provider gatekeeper for the SQLite cutover * fix: share the cost store serial executor * fix: keep the cost-usage database on transient SQLite failures, only rebuild on corruption (refs #2760) (#2765) withDatabase treated every error as corruption and deleted the entire database. That destroyed user history on lock contention (app and CLI both open writable connections), disk-full, and even a plain NOT NULL constraint violation from appending rows for an unregistered path -- which then failed again on the fresh database and nuked it a second time. Classify SQLite primary result codes: transient/environment/data-shape errors (BUSY, LOCKED, FULL, NOMEM, IOERR, CONSTRAINT, READONLY, ...) now return the fallback and keep the file; corruption and schema drift (CORRUPT, NOTADB, ERROR, invalid data, incompatible schema) still rebuild. Rebuilds and preserved failures are now logged via the token-cost category so field rebuilds are observable, and a failed transaction that could not roll back drops the connection instead of leaving it wedged. Adds a failure-injection suite: constraint orphans, connection reuse after a rolled-back transaction, mid-file structural corruption, deleted -wal / stale -shm sidecars, zero-byte databases, the database path occupied by a directory, and a future schema version on disk. * fix: restore JSON-cache retention semantics lost in the SQLite cutover (refs #2760) (#2767) * fix: restore JSON-cache retention semantics lost in the SQLite cutover (refs #2760) Semantics-parity audit of the CostUsageCache -> CostUsageStore migration found four behaviors the cutover dropped: - Retention pruned only the typed discovery columns while the scanner round-trips discovery through the opaque payload, so deleted files resurfaced on the next load; prune now updates both in lockstep and resets cursors/isComplete like the old cache did. - The row budget deleted the oldest files regardless of window; the old entry budget never sacrificed in-window or recently active files (the byte budget remains the only authority that may). - Fork-parent protection ignored the lineage-only dependency key and a stale parent survived when its only referencing child was pruned in the same pass; candidates now honor the dependency key and iterate to a fixpoint. - Out-of-window files with an in-window mtime (active sessions with unscanned rows) were deletable; they are protected again. saveCodexCache budgets are injectable for tests, restoring pins for previous-report preservation across trims and the non-Gregorian catch-up report (#2703), plus strip/compaction coverage. * docs: changelog for the SQLite retention semantics parity fixes (refs #2760) * perf: precompute per-path baseline counts in saveCodexCache instead of rescanning all rows per file (refs #2760) (#2770) * test: make cost-usage debounce and token-hydration tests order-independent (#2777) - Prevent a background models.dev pricing refresh from flipping codexPricingKey mid-test. - Replace the fixed 1s poll that raced the process-global CostUsageScanExecutor. * test: add StoreStress harness and contention regression tests (#2766) * fix: stabilize OpenRouter plugin parity (#2779) * Fix Codex cost pricing race (#2776) * fix: price persisted usage at report read time * fix: reprice project usage snapshots at read time * test: add corpus-scale proof gates for the SQLite cost store (refs #2760) (#2763) * test: add corpus-scale proof gates for the SQLite cost store (refs #2760) * test: freeze scale-proof reference date and assert exact retention count * test: bound persisted size of legacy payload * fix: wrap the cost-store save cycle in one transaction so a crash mid-save is all-or-nothing (refs #2760) (#2771) * fix: wrap the cost-store save cycle in one transaction so a crash mid-save is all-or-nothing (refs #2760) * fix: inline save-transaction control so the operation closure stays actor-isolated * fix: replace the save-transaction closure with begin/end calls to satisfy older Swift 6 region analysis * test: widen slow-refresh sleeps in CLIServeRouterTests so the request deadline always wins the race on loaded CI runners * Add CodexBar Plasma integration (#2768) Co-authored-by: Simone <185146821+Lucenx9@users.noreply.github.com> * Add Fireworks provider (30-day billing spend) (#2687) * Add Fireworks provider showing 30-day billing spend * Fix Fireworks URL/auth interpolation and test assertions * Validate Fireworks account slug; add malformed-slug test * fix: keep recently modified files out of cost-store window pruning (refs #2760) (#2764) * fix: keep recently modified files out of cost-store window pruning (refs #2760) * fix: use inclusive calendar-day bounds for retention mtime recency --------- Co-authored-by: Peter Steinberger <steipete@gmail.com> * docs: changelog for Fireworks provider and calendar-day pruning fix (#2687,…


Summary
RateLane.monthly).Automaticmenu-bar metric now prefers the genuine weekly lane when a longer window exists, so a stale 30-day primary cannot hide the binding weekly quota. Explicit Primary/Secondary preferences are unchanged.Closes #2592. Related: #2324 (inverse shape, weekly in
secondary); the switcher already prefers real 7-day windows, so this closes the remaining card/menu-bar gap.Reproduction
Focused tests fail on current main before this change and pass after:
CodexConsumerProjectionTests.thirty day primary window maps to a monthly lane instead of sessionCodexConsumerProjectionTests.automatic menu bar metric prefers the weekly window over a thirty day primaryMenuCardModelCodexProjectionTests.codex card titles follow window duration instead of slot positionOpen question for maintainers
Unknown durations (anything other than 300/10080/43200) still fall back to slot semantics ("Session"/"Weekly") to preserve existing behavior (including the drifted-weekly guard). #2592 also proposes a generic duration-derived title for those; that part needs a product decision, so it is left out of this PR.
Verification
make test: 794 selections, 67 groups, 0 failuresmake check: clean (SwiftFormat + SwiftLint strict)