Commit graph

1364 commits

Author SHA1 Message Date
Resham Joshi
44a94f52cf
Merge pull request #883 from ozymandiashh/feat/877-preferred-terminal
feat(menubar): add preferred terminal setting with Terminal.app fallback
2026-08-03 15:47:58 -07:00
Resham Joshi
ce976e6806
Merge pull request #886 from getagentseal/fix/kimi-codex-model-normalization
fix(models): price codex Kimi rollouts recorded as kimi/k3[1m]
2026-08-03 15:40:58 -07:00
AgentSeal
a8a954fd78 fix(models): price codex Kimi rollouts recorded as kimi/k3[1m]
Codex driving a Kimi backend records the model as kimi/k3[1m] (provider prefix
plus a [1m] context tag). getCanonicalName stripped the prefix but not the tag,
so it matched no alias and priced to $0 - Kimi-via-codex spend was silently
reported as free, and the menubar showed no Codex segment. Strip a trailing
[...] context tag so kimi/k3[1m] -> k3 -> kimi-k3, repairing both cost and the
display name.
2026-08-04 00:38:40 +02:00
Resham Joshi
2c69516b02
Merge pull request #882 from ozymandiashh/fix/874-cline-vscode-variants
fix(cline): scan all VS Code variants for task storage
2026-08-03 15:33:23 -07:00
Resham Joshi
b42baa7a93
Merge pull request #885 from getagentseal/harden/codex-parse-field-guards
fix(codex): guard non-string timestamp and model on the parse path
2026-08-03 15:16:51 -07:00
Resham Joshi
2ae4cbc2c2
Merge pull request #884 from getagentseal/harden/menubar-download-retry
fix(menubar): retry mid-stream download drops and harden the retry budget
2026-08-03 15:16:27 -07:00
AgentSeal
4ff3497eb8 fix(codex): guard non-string timestamp and model on the parse path
Follow-up to #881. Structural discovery admits third-party rollouts whose
schema is unverified. Two unchecked JSON.parse fields still reached string ops
on the parse path: an unparseable timestamp threw RangeError out of the
fork-cutoff Date math, and a non-string model threw TypeError from calculateCost
(.replace). Either sank that session's usage to zero. Skip the fork cutoff for
an unparseable timestamp, and only adopt a string model (falling back to a real
model otherwise) so the session is counted instead of silently reading zero.
2026-08-04 00:14:28 +02:00
AgentSeal
c6c069ad92 fix(menubar): retry mid-stream download drops and harden the retry budget
Follow-up to #880. Make fetchReleaseAsset generic over a consume callback
that runs inside the retry loop, so a socket dropped mid-download is retried
and its partial file removed rather than aborting the install and leaving a
truncated zip. Clamp a non-finite attempt budget, drain non-ok bodies, and
carry the original error as cause. The checksum comparison stays outside the
retry, so an integrity mismatch still aborts immediately and never re-downloads.
2026-08-04 00:09:04 +02:00
Resham Joshi
733003df35
Merge pull request #881 from ozymandiashh/fix/873-codex-originator-structural
fix(codex): validate rollouts structurally instead of by originator
2026-08-03 15:08:00 -07:00
Resham Joshi
fd4ede2bb3
Merge pull request #880 from ozymandiashh/fix/876-menubar-download-retry
Some checks are pending
CI / semgrep (push) Waiting to run
fix(menubar): retry transient release-asset download failures
2026-08-03 14:26:03 -07:00
ozymandiashh
1959550218 feat(menubar): add preferred terminal setting with Terminal.app fallback
Full Report and Optimize always opened Terminal.app. Add a closed
PreferredTerminal enum (Terminal.app, iTerm2), a General settings picker,
and graceful fallback: chosen terminal -> Terminal.app -> headless spawn.

Defaults to Terminal.app so existing users see no change.

The terminal is selected from a closed enum, never a user string, so the
`tell application "..."` target stays a compile-time literal. Commands are
still whitespace-joined argv validated token-by-token by CodeburnCLI.isSafe
before any interpolation, preserving the shell-injection invariant.

Only terminals with a real "run in a live window" scripting verb are listed:
Terminal.app has `do script`, iTerm2 has `write text` on a session. Ghostty,
WezTerm, Warp, Alacritty and kitty expose no equivalent, so they keep the
existing headless fallback rather than shipping a window that closes on exit.

The iTerm2 script targets `application "iTerm"`, not `"iTerm2"`. AppleScript
resolves the name of a not-yet-running app through LaunchServices by bundle
file name, and the bundle is iTerm.app. Measured on iTerm2 3.6.11: with the
app quit, `tell application "iTerm2"` fails to compile (-2741) while
`tell application "iTerm"` compiles, cold-launches iTerm2 and runs the
command. The `"iTerm2"` spelling only works while the app already happens to
be running.

Fallback is a chain that checks results rather than a single fire-and-forget
pick, because "installed" does not imply "scriptable": osascript can still
fail on a missing Automation approval or a broken bundle. Each candidate is
run, waited on and its exit status checked, off the main thread so the
popover stays responsive; only once every candidate has failed do we spawn
headless. Every step logs via NSLog, so a user who sees no window has a trail
in Console.app instead of an app that looks dead. The decision logic is
extracted into terminalChain/runFirstWorking so tests exercise
"primary failed -> fell back" without launching anything.

Document the setting in the README next to the other menubar defaults keys.

Closes #877
2026-08-04 00:19:00 +03:00
ozymandiashh
eece4cf005 fix(codex): validate rollouts structurally instead of by originator
Codex session discovery required `payload.originator` to start with
"codex" (case-insensitive). `originator` is a free-form client identity
string, not a format marker: any tool driving `codex app-server` writes
structurally identical rollouts under ~/.codex/sessions with its own
value ("t3code_desktop", "JetBrains.IntelliJ IDEA", ...). Those sessions
were silently dropped from every report, and each past fix only admitted
one more spelling.

Gate on structure instead: a first line that parses as JSON, has
type === "session_meta", and carries a plain-object payload. Foreign and
malformed files are still rejected. Directory ownership decides the
provider — codex.ts is the only provider that reads ~/.codex, and the
walk only visits rollout-*.jsonl under the strict YYYY/MM/DD path or
archived_sessions/ — so no double counting is possible. `originator` is
still parsed onto the meta entry; nothing downstream reads it.

Bump the daily cache to v16. Historical days are served from that cache
(usage-aggregator only recomputes today) and retention is ten years, so
without a bump an upgrading user with a warm cache keeps the pre-fix
rollups forever: discovery reruns, so the session COUNT moves, while
cost and calls stay frozen — a self-contradicting report that reads as
"fixed". Measured on a fixture with two same-day rollouts, one
codex-cli and one t3code_desktop:

  pristine main, fresh cache      cost 4.55  calls 1  sessions 1
  this branch, main's warm cache  cost 4.55  calls 1  sessions 2  (was)
  this branch, main's warm cache  cost 18.2  calls 2  sessions 2  (now)
  this branch, fresh cache        cost 18.2  calls 2  sessions 2  (truth)

CODEX_CACHE_VERSION and PROVIDER_PARSE_VERSIONS.codex deliberately stay
put: both caches are keyed per file path and are written only after a
successful parse, so a file rejected at discovery has no entry to
invalidate. Verified on the fixture above — main's codex-results.json
and session-cache.v7.json hold only the first-party rollout, and reusing
them unchanged still yields the correct total.

Harden `payload.cwd` while admitting unverified clients. It is declared
`string` but comes straight off JSON.parse, and a number/object/array
threw "cwd.replace is not a function" out of sanitizeProject; the throw
escaped discoverSessions into safeDiscoverSessions, which returns [] for
the WHOLE provider, so one malformed file made every Codex report read
zero. Guarded in discovery (falls back to the `unknown` project) and on
the parse side, where a non-string cwd would otherwise ride into
projectPath/workingDirectory and reach the parser's path helpers.

Closes #873, closes #626.
2026-08-04 00:15:24 +03:00
Resham Joshi
18e5011fc3
Merge pull request #860 from avs-io/fix/post-0918-polish
post-0.9.18 polish: compare flags in the TUI, context id prefix, active-days label
2026-08-03 14:11:05 -07:00
AgentSeal
3212b865f7 Merge branch 'main' into fix/post-0918-polish
Resolves tests/dashboard.test.ts (union of both new describe blocks and imports).
2026-08-03 23:08:13 +02:00
ozymandiashh
43410e88a7 fix(cline): scan all VS Code variants for task storage
Cline discovery only looked at the stable VS Code globalStorage root, so
tasks created in VS Code Insiders or VSCodium were never found. The
singular getVSCodeGlobalStoragePath helper returns paths[0], and because
the provider always passed a concrete overrideDir, the 3-variant fallback
inside discoverClineTasks was never reached - unlike the Roo Code and
KiloCode siblings, which pass overrideDir straight through.

Build the default roots from getVSCodeGlobalStoragePaths (stable,
Insiders, VSCodium) plus the ~/.cline/data root and hand them to
discoverClineTasks in one call. The existing dedupe by task id still
collapses a task id seen in more than one root, so totals cannot inflate.
The configuredDirs override used by tests and createClineProvider(dirs)
is unchanged.
2026-08-04 00:01:57 +03:00
Resham Joshi
8dd5a62801
Merge pull request #801 from EuanTop/fix/opencode-session-model
fix(opencode): read session fallback model from the real schema
2026-08-03 13:57:16 -07:00
AgentSeal
80f5ed6cf7 Merge branch 'main' into fix/opencode-session-model 2026-08-03 22:56:49 +02:00
Resham Joshi
8fd6aef9be
Merge pull request #879 from getagentseal/fix/mac-menubar-regular-weight
fix(mac): use regular weight for the menubar title text
2026-08-03 13:52:53 -07:00
AgentSeal
6530546f73 fix(mac): use regular weight for the menubar title text
Render the macOS menu-bar title with `.regular` instead of `.medium`, matching
the visual weight of SwiftBar / MeetingBar. The flame symbol keeps its `.medium`
config. Applies the one-line change from #851.

Co-authored-by: Tim De Pauw <timdp@users.noreply.github.com>
2026-08-03 22:52:22 +02:00
Resham Joshi
519f0a064d
Merge pull request #878 from getagentseal/fix/by-activity-straddle-undercount
fix(aggregator): attribute a midnight-straddling turn's By Activity to today on the all-provider view
2026-08-03 13:48:59 -07:00
AgentSeal
dbdbf466a0 fix(aggregator): attribute a straddling turn's category to today on the all-provider view
buildDurablePeriod derived the today slice of the multi-day, all-provider
headline from the unsliced whole-range parse, so a turn spanning local midnight
kept its category and turn count anchored on its yesterday start. The per-call
cost and calls bucketed onto today correctly, but By Activity and the JSON
daily turn count lost the post-midnight half — categories summed to only the
pre-midnight cost while the headline, By Model and By Project were right.

Slice the today parse with filterProjectsByDays first, which re-anchors the
straddling turn to its surviving today calls, so today's category cost lands on
today. Category cost is the sum of the slice's own calls, so day-N + day-N+1
still equals the whole-range total (no over-count); the per-day turn-count
split matches the cache side and the documented per-day semantics.

Adds a regression test in the straddling-turn conservation suite
(mutation-checked: fails on the pre-fix code). Also fills in the CHANGELOG
Unreleased entries for the batch (#853, #856, #872, #846/#859, #866/#867, #833).
2026-08-03 22:46:54 +02:00
ozymandiashh
eb90b29422 fix(menubar): retry transient release-asset download failures
`codeburn menubar` aborted the install on the first bad response from
GitHub release-asset delivery. A transient HTTP 500 on the checksum
fetch (issue #876) killed an otherwise healthy install even though the
asset was published correctly and the next request succeeded.

Retry the zip and checksum downloads up to 3 times with a short
exponential backoff (0.5s, 1s) on 5xx responses and network-level
errors. 4xx is never retried: 404/410 still falls through to the
release-API discovery path unchanged, and a 403/429 rate limit cannot
clear inside the backoff window so it surfaces immediately with its
retry-after hint. The checksum comparison stays outside the retry loop
so a genuine digest mismatch still aborts on the first look.

Thrown errors now name the requested URL so a failure is actionable.
Retry parameters and the fetch/sleep/log seams are injectable, matching
the options pattern in src/sync/push.ts and src/cache-refresh-lock.ts.
2026-08-03 23:08:59 +03:00
Resham Joshi
55bf65ef6a
Merge pull request #859 from avs-io/fix/omp-title-slot-discovery
pi/omp: discover transcripts whose session record follows a title slot
2026-08-03 13:00:17 -07:00
AgentSeal
62f6eb27c7 Merge branch 'main' into fix/omp-title-slot-discovery
# Conflicts:
#	src/providers/pi.ts
2026-08-03 21:59:19 +02:00
Resham Joshi
b8c92bfba7
Merge pull request #846 from jbspeakr/fix/omp-session-discovery
fix(omp): discover title-first session transcripts
2026-08-03 12:45:46 -07:00
AgentSeal
2c65764c0b Merge branch 'main' into fix/omp-session-discovery 2026-08-03 21:44:19 +02:00
Resham Joshi
dc3ea24b1d
Merge pull request #867 from marcreynolds/feat/menubar-degraded-device-indicator
feat(menubar): mark badge when a paired device is unreachable in combined scope
2026-08-03 12:35:17 -07:00
AgentSeal
067174b885 Merge branch 'main' into feat/menubar-degraded-device-indicator 2026-08-03 21:34:54 +02:00
Resham Joshi
bb506e0a3f
Merge pull request #866 from marcreynolds/feat/desktop-combined-scope
fix(dashboard): aggregate paired-device usage in the desktop Dashboard and menubar badge
2026-08-03 12:33:29 -07:00
AgentSeal
ac8ff954cd Merge branch 'main' into feat/desktop-combined-scope 2026-08-03 21:33:03 +02:00
Resham Joshi
458ce1b2d7
Merge pull request #848 from Enclavet/feat/sync-yield-attribution
feat(sync): opt-in git attribution spans on `sync push --attribution`
2026-08-03 12:05:45 -07:00
Resham Joshi
201b289ada
Merge pull request #872 from ozymandiashh/fix/provider-filter-claude-orphan-leak
fix(parser): stop --provider filters from re-surfacing cached claude sessions
2026-08-03 11:54:06 -07:00
Resham Joshi
e05db463a6
Merge pull request #875 from GodCC6/fix/project-filter-test-time-of-day
test: fix time-of-day flake in the durable-headline project-filter tests
2026-08-03 11:42:51 -07:00
chengchuan.zhou
15962202bd test(project-filter): make the durable-headline filter tests time-of-day proof
The tests added in #864 seeded today's live session at a fixed wall-clock
hour (12:00 local). The periods they build end at `new Date()`, and the
suite runs under TZ=UTC, so for any run before 12:00 UTC that timestamp
is in the FUTURE and the range filter correctly drops it. The live half
of the cache/live union then contributes nothing, and the one assertion
that needs a non-zero headline — the unattributed-cost footnote — fell
into renderOverview's "No usage found" early return and went red. Half
of every day was a failing window; a769b50 fixed the start-of-month
flake but this one survived it.

Verified by bisecting the fixture on the unpatched test: moving the
seeded hour from 12:00 to 01:00 (past, at a 03:38 UTC run) turns the
same 12 tests green, so the timestamp's position relative to `now` is
the whole cause.

- Seed the session a few minutes BEFORE now, clamped to today's
  midnight, so it is always both inside today and already in the past.
- Stop the footnote test depending on the live parse at all: seed a
  second, attributable cached day so the headline is non-zero from the
  cache alone. The test now exercises the footnote instead of the
  fixture's timing.
2026-08-03 11:42:32 +08:00
ozymandiashh
6b903a7777 fix(parser): stop --provider filters from re-surfacing cached claude sessions
Claude is scanned via scanProjectDirs instead of parseProviderSources, and
that call had no provider-filter guard. On a --provider <other> run
discoverAllSessions correctly returns no claude sources, so claudeDirs is
empty, but scanProjectDirs still ran: its orphan pass reads the whole cached
claude section and treats every file as no-longer-discovered, re-injecting
PR-bearing entries (and in read-only mode every cached entry) into the result.

The headline stayed correct because it comes from the provider-sliced daily
cache, so only the live-parse panels were wrong. By Model then listed
Anthropic models under --provider cursor while the total showed cursor alone.

Guard the scan with claudeInScope, mirroring the guard the durable-orphan
loop already applies. Deliberately not a claudeDirs.length check: when claude
is in scope but every transcript has been pruned, the orphan pass is what
keeps PR-attributed spend from vanishing.
2026-08-02 19:45:16 +03:00
Andrew Lee
50c8251719 fix(sync): close credential-leak paths; session retraction; span/key/CLI hardening
Review rounds 2-3 + self-review on --attribution:

Credential egress (round 2):
- normalizeRemoteUrl: scp userinfo expressed as an optional regex group
  let backtracking re-parse a credential prefix as host:path
  (x-access-token:ghp_...@host/repo -> token in git.repo). Userinfo is
  now split off at the first @ BEFORE any host matching.
- Positive validation (allow-list) as the final gate on EVERY branch:
  host must be hostname-shaped, every path segment repo-shaped, total
  identity <= 200 chars. Kills transport-helper remotes (ext:: leaks
  local SSH key paths, codecommit:: leaks AWS profile names), residual
  @, spaces/colons, and unbounded strings.
- sanitizePrLinks: links are rebuilt from origin + pathname — userinfo,
  query strings, and fragments are dropped instead of passed through;
  collapsed duplicates dedupe.

Attribution correctness (round 3 + self-review):
- Double-count fix with precise retraction semantics: when a commit
  migrates to a later-parsed tighter-window session, the loser re-emits
  git.commit_count=0. Empty records are emitted ONLY on a true loss in
  THIS computation (lostCandidacy) — a commit that merely aged out of
  the --since range was lost to nobody, and retracting it would
  permanently zero a still-correct server-side count. The sync layer
  additionally requires a prior ledgered state for the session.
- Session dedup key includes project + both window timestamps, so
  ongoing sessions re-emit with corrected span times.
- Span end times clamped like the usage builder (never 0, never
  earlier than start + 1ms).
- CLI mirrors the usage path on attribution push failures instead of
  claiming success.
- Identity normalization: case-insensitive .git strip, doubled path
  slashes collapse.

AI-Origin: human
2026-08-02 12:56:59 +00:00
Andrew Lee
ccee28ae82 fix(sync): address attribution review — cwd-fallback egress, Windows paths, PR-link validation
Review findings on the --attribution PR:

- Privacy: sessions whose project path no longer resolves inherited the
  cwd-fallback repo identity, egressing whatever (possibly confidential)
  repo the user pushes from and falsely attributing its commits.
  buildRepoGroups now tracks per-session identity provenance; the
  attribution path excludes fallback sessions from commit attribution
  entirely (no repo, no commits, PR links only) — they also can no
  longer steal a commit from a genuine session's window.
- Privacy: Windows drive-letter paths (C:/..., C:\..., drive-relative)
  parsed as scp-like remotes, emitting local filesystem paths as repo
  identities. normalizeRemoteUrl rejects drive letters and
  single-character hosts (dotless intranet hosts still accepted).
- Hardening: PR links are shape-checked before sending (https,
  /org/repo/pull/N path, <=256 chars, max 20 per session) — upstream
  parsers only truthiness-check them.
- Safety valve: MAX_ATTRIBUTION_PER_PUSH (10k) caps a first
  --since all --attribution push; dry-run reports the cap.
- Tests: adversarial normalize corpus, cwd-fallback egress repro,
  commit-stealing prevention, PR-link sanitization, and CLI-level tests
  (mock IdP + collector): dry-run sends nothing to the traces endpoint,
  flag-off emits no attribution span names on the wire.
- Docs: reconciled the 'never sent' wording with reality (PR links ride
  even when repo is null; device_id/methodology/timestamps disclosed).
  CHANGELOG Unreleased entry added.

AI-Origin: human
2026-08-02 12:29:18 +00:00
Andrew Lee
1bf7206842 feat(sync): push git attribution spans with --attribution
Expose the yield session-to-commit correlation through codeburn sync so
backends can join AI usage to git activity without local git hooks.

- yield: export normalizeRemoteUrl (host/org/repo; credentials, ports,
  and .git stripped) and computeAttributionRecords, which reuses the
  exact repo-grouping + tightest-window attribution from computeYield
  (extracted into a shared buildRepoGroups) and joins in the normalized
  origin remote and session prLinks.
- otlp: two new span types sharing the session traceId —
  codeburn.session.attribution (git.repo, git.pr_links, git.commit_count)
  and codeburn.commit (git.sha, git.in_main, git.was_reverted). Resource
  attribute codeburn.attribution_methodology=timestamp-window marks the
  attribution as inferred.
- push: generic send core reused by usage and attribution batches. Dedup
  keys encode mutable state (inMain/wasReverted), so a state transition
  re-sends the updated fact while identical states dedupe via the
  existing sent-ledger.
- cli: opt-in --attribution flag on sync push (dry-run aware); commits
  in repos with no network remote are never sent.

AI-Origin: human
2026-08-02 12:28:13 +00:00
Resham Joshi
2de4d100bf
Merge pull request #864 from GodCC6/fix/project-filter-durable-headline
Some checks failed
CI / semgrep (push) Has been cancelled
fix: --project/--exclude are ignored by the durable headline totals
2026-08-01 16:19:03 -07:00
AgentSeal
3432a07bec Merge branch 'main' into fix/project-filter-durable-headline 2026-08-02 01:18:13 +02:00
AgentSeal
a769b5008a fix(daily-cache): keep prototype-named project keys; make filter tests date-deterministic
Two issues on top of the --project/--exclude durable-headline fix:

- sanitizeProjects dropped any project whose key is an Object.prototype member
  name (constructor, valueOf, __proto__, ...). A project key is a directory
  basename, so such a name is legitimate, and dropping it left the day's
  per-project split summing to less than the day cost — so the sliced,
  project-filtered headline silently lost that project's spend with no footnote.
  The keys are written via setOwn (defineProperty), so keeping them is
  pollution-safe; only the redundant `name in Object.prototype` guard is removed.
  Regression test added (mutation-checked: fails without the guard removed).

- The new project-filter tests seeded a carried day 10 days ago but ranged over
  the calendar month, so within the first 10 days of a month that day fell out
  of range and the tests went red. Replaced with a fixed 20-day window that
  always spans the seeded day.
2026-08-02 01:16:35 +02:00
Resham Joshi
b94d0ac67c
Merge pull request #853 from KENSHI601/fix/turn-range-per-call-filter
fix(parser): range-filter calls inside turns instead of dropping day-spanning turns
2026-08-01 15:37:24 -07:00
AgentSeal
6c411ad96d Merge branch 'main' into fix/turn-range-per-call-filter 2026-08-02 00:36:34 +02:00
AgentSeal
5a12dccc70 fix(parser): classify range-sliced turns from the whole turn on the provider path
The Codex/OTel/network provider path classified a date-sliced turn from only
its in-range calls (cachedTurnToClassified(slicedTurn)), while the Claude path
kept the full-turn classification. So a midnight-straddling turn was
categorized and edit-counted differently by provider — a Read at 23:59 plus an
Edit at 00:05, sliced to the 23:59 side, read as coding/hasEdits on Claude but
exploration/no-edit on Codex. This contradicted the documented intent that
category/hasEdits/retries are whole-exchange judgments, not per-call sums.

Classify the full turn, then trim its calls to the range (mirroring the Claude
path's classifiedTurnSlicedToRange). Cost/calls still come from the retained
calls, so conservation is unchanged; only the turn-level judgments are now
provider-independent.
2026-08-02 00:35:30 +02:00
Resham Joshi
39441de8b2
Merge pull request #856 from avs-io/fix/daily-cache-degraded-completeness
daily-cache: never finalize history against a degraded session parse
2026-08-01 14:10:17 -07:00
AgentSeal
1579eb0899 Merge branch 'main' into fix/daily-cache-degraded-completeness 2026-08-01 23:09:27 +02:00
AgentSeal
90dffcddc2 fix(daily-cache): trust a stamped watermark so an idle tail is not re-derived every launch
The watermark pull-back could not tell a legitimately-finalized idle tail
(recent days had no activity, so they are absent from the cache) from the
corrupt cache it heals (a degraded parse finalized past days it never read).
Both look like lastComputedDate > newest populated day, so an idle user
re-parsed the tail on every launch, escalating to a full re-derive under
sustained lock contention where before it did nothing.

A degraded parse can no longer set complete, so the corrupt state can only
come from pre-fix code. Stamp watermarkTrusted whenever a COMPLETE parse
finalizes, and pull the watermark back only for unstamped caches. Pre-fix
caches heal once, then are trusted; caches the fixed code writes are trusted
from the first finalize. The heal still recovers genuinely missing days.
2026-08-01 23:07:22 +02:00
Resham Joshi
fee91be92b
Merge pull request #833 from rbstp/chatgpt-enterprise-support
feat(codex): show the credit limit on credit-metered ChatGPT workspaces
2026-08-01 13:38:00 -07:00
AgentSeal
53ecb3635c Merge branch 'main' into chatgpt-enterprise-support 2026-08-01 22:35:02 +02:00
AgentSeal
75a02854fd fix(codex): align credit/dollar footer formatting across desktop and menubar
The desktop dollar-balance footer used toFixed, which drops thousands
separators, while the new menubar footer groups via a currency
NumberFormatter, so the two clients showed $12500.00 vs $12,500.00 for
the same balance. The menubar credit footer also rounded half-even while
the desktop uses Math.round (half-up), disagreeing on exact-half values.

Render the desktop dollar path through the en_US currency locale, and pin
the menubar footer formatter to half-up. Adds a grouping assertion (TS)
and an exact-half rounding test (Swift).
2026-08-01 22:32:53 +02:00