Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
d78a02a
feat(gui): unify current and per-account quota views
invalid-email-address Sep 5, 2026
8960ddb
fix(gui): make quota lifecycle fixtures and cleanup compiler-safe
invalid-email-address Sep 5, 2026
9c280b6
Merge branch 'codex/provider-account-quota-api' into codex/provider-q…
invalid-email-address Sep 5, 2026
c279976
Merge branch 'codex/provider-account-quota-api' into codex/provider-q…
invalid-email-address Sep 5, 2026
209a9e1
Merge branch 'codex/provider-account-quota-api' into codex/provider-q…
invalid-email-address Sep 5, 2026
671a071
Merge branch 'codex/provider-account-quota-api' into codex/provider-q…
invalid-email-address Sep 5, 2026
e676200
Merge branch 'codex/provider-account-quota-api' into codex/provider-q…
invalid-email-address Sep 5, 2026
638f2b8
Merge branch 'codex/provider-account-quota-api' into codex/provider-q…
invalid-email-address Sep 5, 2026
eb18ad7
Merge branch 'codex/provider-account-quota-api' into codex/provider-q…
invalid-email-address Sep 5, 2026
b8d90fe
Merge branch 'codex/provider-account-quota-api' into codex/provider-q…
invalid-email-address Sep 5, 2026
dbf40c2
Merge branch 'codex/provider-account-quota-api' into codex/provider-q…
invalid-email-address Sep 5, 2026
77b95a8
Merge branch 'codex/provider-account-quota-api' into codex/provider-q…
invalid-email-address Sep 5, 2026
b378414
test(update): isolate recovery child Codex home
invalid-email-address Sep 5, 2026
86258ce
Merge branch 'codex/provider-account-quota-api' into codex/provider-q…
invalid-email-address Sep 5, 2026
675fad5
Merge branch 'codex/provider-quota-parity' into codex/provider-ci-iso…
invalid-email-address Sep 5, 2026
d7e67e9
docs: align fixture follow-up with independent landing
invalid-email-address Sep 5, 2026
c299797
Merge branch 'codex/provider-account-quota-api' into codex/provider-q…
invalid-email-address Sep 5, 2026
e5c791c
Merge branch 'codex/provider-quota-parity' into codex/provider-ci-iso…
invalid-email-address Sep 5, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,12 @@ jobs are still mandatory at landing, not claimed green. Next quota-api P reverif
the unchanged quota/management baseline; attribution changes do not alter its signatures.
API layer branch `codex/provider-account-quota-api` starts at the verified attribution head.

Quota API D: `768e5a004`, PR #3584, run33940065554 all four backend shards/gates/API usage
successful, including explicit credential isolation, passive no-network and post-await age
regressions. Wegener full/interdiff review and Kant omitted-mode delta PASS. No local validation
was executed. Next UI P reverified 030 against that additive row contract; remaining platform
rollups remain mandatory before merge. UI branch `codex/provider-quota-parity` starts here.

## Delegation write map (locked before Build)

User reiterated no local suites during quota-api A. No local test suite has been run;
Expand All @@ -69,5 +75,5 @@ CI result checks. This supersedes the earlier local-static/build verification al

- Attribution cycle: Harvey owns `src/usage`, `src/router.ts`, the identified Chat/Messages error catches, `src/server/management/shared.ts`, and corresponding existing backend regression files. Main owns the provider model annotation/share/caching UI, its GUI regressions, docs, commits and CI. No overlapping writes.
- Quota API cycle: Euclid owns quota readers/key-cache/types and relevant provider regression files. Main owns management route joins, API-route regressions and docs; clarify exact exported signatures before either writes.
- Quota UI cycle: main owns report projection/current section; a bounded worker may own account hook/panel refresh and regression changes only after an explicit P amendment gives disjoint paths.
- Quota UI cycle: main owns shared `types.ts`, `report.ts`, NEW ProviderAccountQuota/ProviderCurrentQuota, ProviderDetails/Overview/Usage, all locale keys, public docs and direct shared-renderer regression. Harvey owns useProviderAccountPools, Providers page refresh coordinator, shell refresh callback epoch, ProviderAuthPanel and focused hook/refresh regressions. Worker may read shared contracts but never edit them. No overlapping writes or local validation.
- Independent Kant audits plans read-only; fresh independent final implementation review remains required. No worker changes FSM, goals, branches, commits or remote state, and no worker runs local tests.
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Recovery fixture home isolation

The recovery fixture changed HOME and OPENCODEX_HOME but inherited the test runner's
explicit CODEX_HOME. The production home resolver prioritizes that explicit value, so
the detached child could share the parent test's Codex namespace. This is a confirmed
fixture-isolation defect, not proof of the earlier intermittent startup failure's cause.

`tests/update/update-stop-first.test.ts` now reuses `createIsolatedTestEnvironment` for
the case root and child environment. A negative-inheritance test checks its private Codex
directory, preserved real-home guard, unchanged parent input and absent service state.
The actual child environment is also checked with the production Codex home resolver.

Runtime selection, runtime overrides, bundled dependency, all timeouts, diagnostics and
reap-before-removal ordering are unchanged. No production code or port-probe code changes.
The port-probe investigation is separately owned by the coordinating work.

Independent plan and implementation review: Kant PASS, read-only. Initial preparation
commit `b37841448816107c856171277dff0464032d282e` was local-only and left the three
existing PR heads unchanged. The user later ended cross-task coordination. This follow-up
now forms the fourth layer of the independently integrated stack described in040, and
requires its own new exact-head remote CI before merge. No local test, typecheck, build,
lint or scan was run. Integration does not change the reviewed test patch or prove the
cause of any historical startup failure.
Original file line number Diff line number Diff line change
Expand Up @@ -79,3 +79,48 @@ MODIFY `structure/05_gui-and-management-api.md` usage/quota paragraphs and `docs
## Bypass/residual

UI state is presentation, not credential authorization. Backend fixed-destination account/key readers are the actual boundary. Existing account mutation permissions are unchanged; no new enforcement claim.

## P stale-check and locked refinements at 768e5a004

The backend now emits the row-level mode/availability fields, including Kimi's omitted-key-auth
default. 000's no-local-validation directive supersedes all local command examples above.

1. Main owns a shared `AccountQuotaMode` and `AccountQuotaReading` in existing view types:
`quotaMode?: "probe"|"passive"|"unsupported"`, `quota?: AccountQuota|null`,
`quotaUnavailable?: boolean`, `quotaPending?: boolean`. OAuth/key rows and hook interfaces
reuse it. Pending is GUI-only. Worker reads this contract, does not edit types.ts.
2. Main owns NEW `ProviderAccountQuota.tsx`, shared by all three tabs. Props exactly match
AccountQuotaReading. Unsupported ignores retained data; passive without observation has an
honest unobserved message; only explicit probe pending can show a skeleton. Failed reads
retain last-good numbers with a warning and their actual age. Reuse ProviderCapacityQuota
for windows AND credits-only balances. No summation of different accounts/keys.
3. NEW ProviderCurrentQuota accepts `report`, optional `reading: AccountQuotaReading`, and
optional refresh callback. ProviderDetails supplies the active OAuth/key row only. A present
row with a known quotaMode (including passive with no observation), pending/unavailable,
or an explicit quota overrides the provider report;
never substitute the previous active account's quota. Codex uses report aggregation's current
account. Use the actual quota.updatedAt for current metadata, not aggregate refresh time.
4. `currentAccountQuotaReport` treats present malformed aggregation as unknown, not as a
non-pool current quota. Valid aggregate missing current likewise stays unknown. Tests pin
aggregate20/current70, absent current, malformed aggregation and distinct measurement age.
5. Preserve callback argument order: shell reports `onQuotaRefreshSettled(ok, epoch)` with
captured epoch second. Page resolves matching ticket only; superseded/unmounted tickets
resolve false. Existing one-argument callback consumers still receive a boolean.
6. The account loader merges last-good quota only by surviving credential id, publishes explicit
pending for probe rows, awaits forced enrichment, handles HTTP failure behind generation
fences, and uses a bounded fetch deadline rather than a never-ending spinner. Passive and
unsupported rows never get absence-driven loading. Key subset refresh merges other providers.
7. Refresh outcome text says the quota check completed, not that every upstream value is freshly
measured; passive reloads and a provider-report last-good fallback cannot prove freshness.

New localized keys owned by main: `pws.currentAccountUsage`, `pws.quotaUnsupported`,
`pws.quotaUnobserved`, `pws.quotaCheckCompleted`. Existing error/refresh labels are reused.
Worker's AuthPanel imports ProviderAccountQuota and uses the above keys; no locale edits there.

Source delta verification is remote CI plus existing in-app browser on synthetic data only.

A repair: Kant identified a passive account switch while the old provider report remains cached.
Accepted. Any active passive row is authoritative even with no quota: render unobserved and never
reuse the old report. The direct current-section regression must provide an old report75% plus a
new active passive row lacking quota and assert that75% is absent. Unknown-mode legacy rows may
only use a provider report when no row-specific state/data exists; absence is never confirmation.
44 changes: 44 additions & 0 deletions devlog/_plan/260905_provider_usage_quota_parity/031_ui_build.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# Unified quota presentation evidence

Base `768e5a004`. Main owns shared reading/current projection and three-tab integration;
Harvey owns account enrichment, refresh epochs and AuthPanel integration. No local test,
typecheck, build, lint or privacy-scan commands were executed in this layer. Remote CI owns
all command-based validation.

Implemented one state/credit/window renderer across Overview, Usage and Accounts/API keys.
Known-mode active rows are authoritative, including empty passive readings. Pool totals are
not current-account fallback. Explicit null from the API remains null during client merges;
only absent data can carry forward. Forced reads settle both account enrichment and the
matching provider-report epoch. Main-owned source slice reviewed PASS by Kant; fresh integrated
review remains in progress.

## Browser evidence

Existing isolated Vite server on127.0.0.1:18184; natural production components mounted from
`gui/.tmp/provider-parity.html` with synthetic responses only. No live tokens, accounts or
upstream requests. The temporary fixture is not a shipped page.

- 031_current_overview.png: current35%/61% below usage statistics.
- 032_current_usage.png: same current reading below provider model usage.
- 033_oauth_accounts.png: two account rows show61% and12% separately.
- 034_api_key_balances.png: two keys show75USD and25USD separately, never summed.
- 035_passive_current_unobserved.png: actually selected the unobserved second passive account;
the previous account's report remains in fixture state but its numbers are absent.
- 036_pool_current.png: aggregate20%, current70%; selected-provider current section shows70%.
- Unsupported configured provider: explicit unsupported message, no current refresh control,
no retained quota numbers.
- 037_tablet_usage.png: actual CSS viewport767px, document width767px; Korean layout observed.
- 039_mobile_current.png: actual CSS viewport390px, document width390px; current section and
refresh control stay inside the viewport; the model table keeps its local horizontal scroll.
Refresh was activated with pointer and Return, displaying check-completed feedback with
visible focus. Raw browser capture avoids the in-app screenshot wrapper's zoom clipping.

Desktop, tablet and mobile captures were read back. Temporary viewport overrides were reset.
The source fixture's active account switch and refresh are synthetic UI state transitions,
not writes to the user's account. Exact-head remote CI and final stack integration are pending.

Remote React Doctor atd78a02a63 reported test-harness render-time global assignments in two
new hook tests, one unused mock parameter, and cleanup-ref capture warnings in the account
loader. Its detail was read from the signed-in GitHub summary using Aside, not by running
the tool locally. Repair keeps assertions: capture test observations in layout effects,
remove the unused parameter and capture the stable cleanup containers inside the effect.
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,13 @@ commit `b37841448816107c856171277dff0464032d282e`, limited to the update-recover
and its numbered record. Retain it as a fourth test-only stack layer. No new production
behavior is planned. Any semantic conflict requires a concrete plan amendment and review.

The UI cascade has one textual conflict in `gui/src/pages/Providers.tsx`: retain this
unit's `useQuotaRefreshCoordinator(apiBase)` and upstream's `fetchConfig:
refreshConfigResult` binding together. Keep the upstream void-returning `fetchConfig`
adapter and result-aware `useProviderModelsNotice` caller unchanged. Existing quota epoch
and registration-notice tests must both execute in the new CI. This composes the two
existing contracts; it does not restore superseded unbounded refresh waiters.

Preserve all original commits; use normal merge commits and fast-forward no-verify pushes,
not rebases or force-pushes. Inspect each integration diff, check that inherited quota,
registration and pricing semantics survive, and obtain independent review before publication.
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
8 changes: 8 additions & 0 deletions docs-site/src/content/docs/guides/web-dashboard.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,14 @@ vendor's model price alone is not enough to estimate cost; an exact provider or
still applies. Model shares are calculated within the selected provider. Requests for an unknown
reserved `policy/` name now fail before reaching an upstream provider; historical usage is retained.

The selected provider's **Overview** and **Usage** tabs show **Current account usage** below the
usage statistics. **Accounts** and **API keys** show each supported credential's own quota,
including credit balances. The provider-wide overview still shows pooled capacity where available;
it is not substituted for a missing current-account reading. Unsupported lookup, no passive
observation yet, loading, failed lookup with last-known values, and measured zero are separate
states. **Quota check completed** means the read settled—not that a passive observation became
new or that every upstream measurement was refreshed.

## Model visibility

The **Models** switches show final Codex visibility: a routed model is on only when its provider allowlist includes it (or no allowlist is set) and it is not disabled. Turning a model on reconciles both filters atomically; **All on** clears the provider allowlist so newly discovered models are also on.
Expand Down
5 changes: 5 additions & 0 deletions docs-site/src/content/docs/ko/guides/web-dashboard.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,11 @@ bun run dev:gui
**Logs**와 **Usage**의 비용 값은 보고된 토큰으로 계산한 API 정가 환산치입니다. 결제 영수증이나
실제 청구 증거가 아니며, 구독 사용량 또는 프로바이더 크레딧이 대신 적용될 수 있습니다.

프로바이더를 선택하면 **개요**와 **사용량** 탭의 통계 아래에 **현재 계정 사용량**이 표시됩니다.
**계정**과 **API 키** 탭에서는 조회를 지원하는 계정·키마다 할당량과 크레딧 잔액을 볼 수 있습니다.
풀 전체 용량을 현재 계정 값 대신 보여주지 않으며, 조회 미지원·관측값 없음·조회 중·조회 실패·실제 0%를 구별합니다.
**할당량 확인 완료**는 조회가 끝났다는 뜻입니다. 패시브 관측값까지 새 값으로 바뀌었다는 뜻은 아닙니다.

## 모델 노출

**Models** 스위치는 Codex의 최종 노출 상태를 나타냅니다. 라우팅 모델은 프로바이더 allowlist에 포함되거나 allowlist가 없고, 동시에 비활성화되지 않았을 때만 켜집니다. 모델을 켜면 두 필터를 원자적으로 조정하며, **모두 활성화**는 allowlist를 해제해 새로 발견되는 모델도 켭니다.
Expand Down
30 changes: 30 additions & 0 deletions gui/src/components/provider-workspace/ProviderAccountQuota.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { useT } from "../../i18n/shared";
import { accountQuotaFromReport } from "../../provider-workspace/report";
import { formatRelativeTime, relativeTimeLabelsFromT } from "../../provider-workspace/usage";
import { ProviderCapacityQuota } from "./ProviderCapacityQuota";
import type { AccountQuotaReading } from "./types";

/** The same reading states and credit/window renderer for current and all-account views. */
export default function ProviderAccountQuota({ quota: rawQuota, quotaMode, quotaUnavailable, quotaPending }: AccountQuotaReading) {
const t = useT();
const quota = accountQuotaFromReport({ quota: rawQuota });
if (quotaMode === "unsupported") {
return <p className="muted" data-quota-state="unsupported">{t("pws.quotaUnsupported")}</p>;
}
const pending = quotaMode === "probe" && quotaPending === true;
const state = quotaUnavailable ? "unavailable" : pending ? "pending" : quota ? "ready" : quotaMode === "passive" ? "unobserved" : "unknown";
return <div data-quota-state={state}>
{quotaUnavailable && <p className="muted pwi-auth-acct-quota-stale">{t("pws.accountQuotaUnavailable")}</p>}
{quota || pending ? (
<ProviderCapacityQuota
report={{ quota, updatedAt: quota?.updatedAt, ...(quotaMode === "passive" ? { observed: true } : {}) }}
pending={pending && !quota}
/>
) : !quotaUnavailable && (
<p className="muted">{t(quotaMode === "passive" ? "pws.quotaUnobserved" : "pws.quotaUnavailable")}</p>
)}
{quotaUnavailable && quota && <p className="muted">
{t("pws.stats.quotaUpdated")}: {formatRelativeTime(quota.updatedAt, relativeTimeLabelsFromT(t))}
</p>}
</div>;
}
Loading
Loading