From bea573abeec488afc615cfad1f3f010ac413d925 Mon Sep 17 00:00:00 2001 From: JUN Date: Thu, 3 Sep 2026 22:00:24 +0900 Subject: [PATCH 01/49] feat(meta): read Muse subscription usage from the response stream (#3358) * docs(devlog): roadmap the Meta Muse provider-parity unit * docs(devlog): fold the passive-quota staleness blocker into wp1 * docs(devlog): bound the headroom side effect in the inventory and closeout * feat(quota): parse and cache Meta subscription usage observed in-band * feat(meta): observe Muse subscription usage and serve it from the account API * fix(quota): hydrate before a passive write and require full-roster passive evidence * test(meta): cover the Muse observation seam and the passive quota read path --------- Co-authored-by: jun --- .../260903_muse_provider_parity/000_plan.md | 132 ++++++++ .../001_parity_inventory.md | 200 +++++++++++ .../010_wp1_passive_quota_core.md | 314 ++++++++++++++++++ .../020_wp2_observation_age_ui.md | 134 ++++++++ .../030_wp3_parity_closeout.md | 140 ++++++++ src/oauth/account-quota-rank.ts | 41 ++- src/providers/muse-subscription-usage.ts | 95 ++++++ src/providers/quota.ts | 72 ++++ src/server/management/oauth-account-routes.ts | 13 +- src/server/responses/core.ts | 34 ++ tests/muse-passive-quota-cache.test.ts | 209 ++++++++++++ tests/muse-passive-quota-observation.test.ts | 203 +++++++++++ tests/muse-subscription-usage.test.ts | 129 +++++++ 13 files changed, 1712 insertions(+), 4 deletions(-) create mode 100644 devlog/_plan/260903_muse_provider_parity/000_plan.md create mode 100644 devlog/_plan/260903_muse_provider_parity/001_parity_inventory.md create mode 100644 devlog/_plan/260903_muse_provider_parity/010_wp1_passive_quota_core.md create mode 100644 devlog/_plan/260903_muse_provider_parity/020_wp2_observation_age_ui.md create mode 100644 devlog/_plan/260903_muse_provider_parity/030_wp3_parity_closeout.md create mode 100644 src/providers/muse-subscription-usage.ts create mode 100644 tests/muse-passive-quota-cache.test.ts create mode 100644 tests/muse-passive-quota-observation.test.ts create mode 100644 tests/muse-subscription-usage.test.ts diff --git a/devlog/_plan/260903_muse_provider_parity/000_plan.md b/devlog/_plan/260903_muse_provider_parity/000_plan.md new file mode 100644 index 0000000000..5c95d37092 --- /dev/null +++ b/devlog/_plan/260903_muse_provider_parity/000_plan.md @@ -0,0 +1,132 @@ +# Meta Muse: from credential import to a first-class provider + +- Date: 2026-09-03 +- Session: `01a0670b-54e5-7d41-9f86-b7cf5983b334` +- Work class: **C4** — the request path, a persisted cache, the management API, and a + user-visible dashboard surface move together, and the cache is keyed by a credential + identity that failover can change mid-turn. +- Status: **P (wp0)**. + +## Loop spec + +- Archetype: satisfy-spec integration. The verifier defines done; there is no metric to + maximize. +- Trigger: the user opened `http://localhost:10100/#providers`, expected Muse usage to + render, and found nothing. The investigation documents existed; the code did not. +- Goal: `meta-muse` behaves like a first-class OAuth provider in the dashboard — usage + windows visible, and every other parity surface either closed or recorded as a + deliberate, evidence-backed non-goal. +- Non-goals: Meta console GraphQL, Muse Voice/Image models, translated docs locales, + `meta-model`'s key path, and any inference call issued to obtain a quota. +- Verifier: focused `bun test` on the touched suites, `bun run test:changed`, + `bun x tsc --noEmit`, `bun run privacy:scan`, `bun run lint:gui`, `cd gui && bun run build`. + **The repository-wide local suite is forbidden by standing user instruction.** + Exact-head GitHub CI is the authoritative gate. +- Stop condition: every work-phase closed and each PR green at its exact head SHA and + merged into `dev`. +- Memory artifact: this unit. +- Terminal outcomes: `DONE` for each phase; `BLOCKED` if CI or branch protection refuses + for an unrelated reason; `NEEDS_HUMAN` if a display decision needs the user. +- Escalation: each A gate dispatches an independent read-only reviewer on + `xai/grok-4.6`. Two failed correction loops on the same packet stops the phase. +- HOTL resource bounds: write scope is the IN list below; `gh` for PR and CI; subagents + are read-only reviewers plus bounded workers with disjoint write scopes. No token or + wall-clock bound was set, so `BUDGET_EXHAUSTED` is not an available outcome. + +## What the predecessor unit got right, and the three things it did not + +`260903_muse_spark_plan_oauth/050_wp5_passive_muse_quota.md` designed this feature and +was never built. Its core judgment holds and is adopted wholesale: Meta publishes no +quota endpoint (`003` §E probed 17 paths, all 404), the value arrives only as an SSE +event on a streaming turn, so the seam inverts — writes come from the request path, +reads are cache-only, and refresh does not exist. `supportsPerAccountQuota` must stay +false because that predicate gates `fetchAccountQuota`, whose fallback branch at +`src/providers/quota.ts:1629` sends any non-Kiro/non-Antigravity bearer to Anthropic's +usage endpoint. + +Three of its file-change decisions are **wrong against the current tree**, and this unit +corrects them. Each was measured, not reasoned: + +| `050` said | Measured | Consequence | +|---|---|---| +| add `onSubscriptionUsage` to `SseInspectorHandlers` in `relay.ts` | `onParsedPayload` already exists (`src/server/relay.ts:834`), fires for **every** parsed frame before terminal handling (`:1020`), and is already threaded through all three passthrough construction sites | **`relay.ts` is not modified at all.** A new handler would duplicate a seam that exists | +| the GUI account row needs new rendering | `ProviderAuthPanel.tsx:517` already renders `QuotaBars` for any account carrying `quota`, and `useProviderAccountPools.ts:100` already requests `?quota=1` for every OAuth provider | wp2 shrinks to the observation-age affordance; bars appear the moment the API returns them | +| `hasPassiveAccountQuota` guards the read path | the read path also runs `fetchProviderAccountQuotas` (`quota.ts:1683`), which **probes**; a passive provider needs a different function, not the same one behind a second flag | wp1 adds a cache-only reader, not an allowlist entry | + +The general lesson, and the reason wp0 exists at all: a plan written against a tree +three commits ago names files that have since grown the seam it was going to add. + +## The decision this unit turns on + +A passive quota is **an observation, not a measurement**. Every other provider's bars +answer "what is true now"; a Muse bar answers "what was true at the last streaming +turn", which may be days old. Rendering the two identically is the one way this feature +can actively mislead — a user reading 4% and deciding to start a long job, when the +real figure moved hours ago. + +So observation age is not decoration on this feature; it is the feature's honesty +condition, and it is why wp2 is a work-phase rather than a footnote in wp1. + +## Work-phase map (dependency-ordered, PHASE-SPLIT-01) + +| Phase | Doc | Delivers | PR | +|---|---|---|---| +| wp0 | this folder + `001` | measured parity inventory, diff-level decade docs | — | +| wp1 | `010_wp1_passive_quota_core.md` | parser, observation seam, generation-fenced write, cache-only read path | PR 1, base `dev` | +| wp2 | `020_wp2_observation_age_ui.md` | the dashboard states the observation age; absent renders nothing | PR 2, base `dev` | +| wp3 | `030_wp3_parity_closeout.md` | remaining surfaces closed or recorded NOT-APPLICABLE with evidence | PR 3, base `dev` | + +wp1 → wp2 → wp3 is a real dependency chain: wp2 renders what wp1 caches, wp3's docs and +provider-note corrections are only true once both have landed. They are **independent +PRs off `dev`, not a stack** (`DEV-STACK-01`): wp1 is server-side, wp2 is +`gui/` plus one API field, wp3 is prose and small allowlists. The diffs do not overlap, +so stacking would impose a false merge order. + +## Scope + +### IN + +- `src/providers/muse-subscription-usage.ts` (NEW) — the parser +- `src/providers/quota.ts` — `hasPassiveAccountQuota`, `recordPassiveAccountQuota`, + `readPassiveProviderAccountQuotas` +- `src/server/responses/core.ts` — the observation handler on the existing + `noteInspectedPayload` seam +- `src/server/management/oauth-account-routes.ts` — cache-only enrichment for a passive + provider +- `gui/src/components/QuotaBars.tsx`, `gui/src/components/provider-workspace/ProviderAuthPanel.tsx`, + `gui/src/i18n/en.ts` (+ the other locale files' single new key) +- `src/providers/registry.ts` — the `meta-muse` note's quota sentence, in wp3 only +- `docs-site/src/content/docs/guides/providers.md` — English only +- `tests/` — focused suites beside the existing provider tests +- `devlog/_plan/260903_muse_provider_parity/` + +### OUT + +- `src/server/relay.ts` — the seam already exists; see the correction table above +- `src/generated/model-metadata.ts`, `scripts/model-metadata.source.json` — generated +- `supportsPerAccountQuota` — must stay false; `tests/meta-muse-oauth.test.ts:92` locks it +- `src/adapters/openai-responses.ts` — the translated path drops the event + (`004` Q3, ANSWERED: no). Documented gap, not a silent one +- Meta console GraphQL (`fb_dtsg` + rotating `doc_id`), Muse Voice/Image, translated + docs locales, `meta-model`'s key path +- `src/lab/` must stay off the core request path — `core.ts` is one of the three files + `tests/core-lab-boundary.test.ts` guards, and this unit edits it + +## Accept criteria + +1. `c1` (wp0) — this unit holds 000-range measured research plus one diff-level decade + doc per implementation phase; the wp0 commit contains no production code. +2. `c2` (wp1) — the parser maps both windows through `normalizePercent` / + `normalizeResetAt`, drops `tier`, returns `null` (never throws) on junk, and routes a + non-300-minute window to `customWindows` rather than the five-hour slot. +3. `c3` (wp1) — the write lands under the account that **served** the turn, is discarded + when the config generation moved, persists across restart, and + `supportsPerAccountQuota("meta-muse")` stays false. +4. `c4` (wp1) — no code path issues an inference call to refresh a Muse quota. +5. `c5` (wp2) — the account row shows the percentages with their observation age, and + renders nothing (not a zero bar) when no observation exists. +6. `c6` (wp3) — every remaining parity surface is closed or recorded NOT-APPLICABLE with + file-level evidence. +7. `c7` — `tsc` exits 0, focused suites pass, `privacy:scan` and `lint:gui` green, the + GUI builds, and the full local suite was never run. +8. `c8` — each PR targets `dev`, is green at its exact head SHA, and is merged. diff --git a/devlog/_plan/260903_muse_provider_parity/001_parity_inventory.md b/devlog/_plan/260903_muse_provider_parity/001_parity_inventory.md new file mode 100644 index 0000000000..6cee41900a --- /dev/null +++ b/devlog/_plan/260903_muse_provider_parity/001_parity_inventory.md @@ -0,0 +1,200 @@ +# Measured: what `meta-muse` has, and what a first-class OAuth provider has + +Research doc (000-range). No diffs here; the implementation lives in the decade docs. + +Measured against this checkout on 2026-09-03 (`dev` at `162d11e18`) by an independent +read-only reviewer, then spot-verified by the main agent on the load-bearing rows. Every +claim carries a file:line. Reference providers: `anthropic`, `kiro`, `google-antigravity`. + +## A. The user-visible defect, traced end to end + +The dashboard shows no Muse usage because of exactly one predicate: + +```ts +// src/providers/quota.ts:1477 +export function supportsPerAccountQuota(provider: string): boolean { + return provider === "anthropic" || provider === "kiro" || provider === "google-antigravity"; +} +``` + +The chain, in order: + +1. `gui/src/hooks/useProviderAccountPools.ts:100` requests + `/api/oauth/accounts?provider=meta-muse"a=1` — for **every** OAuth provider, with + no allowlist. The GUI is already asking. +2. `src/server/management/oauth-account-routes.ts:284` computes + `wantQuota = url.searchParams.get("quota") === "1" && supportsPerAccountQuota(provider)`, + which is false, and returns the plain account list. +3. `gui/src/components/provider-workspace/ProviderAuthPanel.tsx:517` renders `QuotaBars` + only when `account.quota != null || account.quotaUnavailable || reserveQuotaSlots`. + None hold, so after the reserve timer expires the row shows nothing. + +**Nothing is broken.** Every layer behaves correctly for a provider that reports no +quota. The provider note says so itself (`src/providers/registry.ts:1543`): "Meta +reports subscription window usage inside streaming responses, but OpenCodex does not yet +read or display it." + +That matters for the fix: the GUI request and the bar component both already exist and +are provider-agnostic, so the server-side write is the whole of the missing machinery. + +## B. Surfaces `meta-muse` already inherits, with no code + +Recorded so wp3 does not "fix" something that works. All follow from +`authMode: "oauth"` plus absence from an exclusion set. + +| Surface | Why it already applies | Evidence | +|---|---|---| +| login / status / logout, account list, switch active, remove, alias | `isPublicOAuthProvider` is `name !== "chatgpt" && isOAuthProvider(name)` | `src/oauth/index.ts:296`; routes at `oauth-account-routes.ts:145,254,305,521,535` | +| GUI account rows, switch, reauth, add-account | the panel keys on the OAuth surface, not the provider id | `ProviderAuthPanel.tsx:224` | +| HIGH_RISK ToS modal | explicitly listed | `gui/src/oauth-tos-risk.ts:10` | +| 429 rotation across accounts | `EXCLUDED_PROVIDERS = new Set(["openai", "anthropic"])`; everything else with `authMode: "oauth"` is in | `src/oauth/generic-account-failover.ts:52,100` | +| serving-account attribution | `stampOAuthAccountLabel(..., resolved.accountId)` runs for every OAuth provider | `src/server/responses/core.ts:3440` | +| SSE inspection on the passthrough path | `createSseInspector` has no provider allowlist | `src/server/relay.ts:886` | +| per-token cost rows | both Muse Spark 1.3 tiers already priced | `src/usage/expected-prices.ts:169-170` | +| CLI `list` / `current` / `use` / `remove` / `alias` / `login` | classified `"oauth"` generically | `src/cli/account-api.ts:85` | + +Two more **compile and run today but are inert** for want of a cached quota row: +headroom-ranked pre-dispatch selection (`generic-account-failover.ts:281` returns null +when `hasHeadroomEvidence` is false) and quota-aware cooldown. wp1 arms both as a side +effect — worth knowing, because it means wp1 changes routing behaviour for a user with +two Muse accounts, not only a display. + +**That side effect carries the unit's one Critical finding.** `headroomOf` +(`account-quota-rank.ts:36`) reads `getCachedProviderAccountQuota`, which applies no +staleness check (`quota.ts:1489` returns `entry.quota` without consulting `entry.ts`). +Every existing caller is safe by construction — a row exists only because a probe wrote +it, and `fetchAccountQuota` re-probes past `ACCOUNT_QUOTA_TTL_MS` (`quota.ts:1602`) — so +freshness is an invariant of the probe path rather than a property of the cache. + +A passive row is the first row in this system that no probe refreshes. Feeding one to a +routing decision would make the proxy confidently prefer an account whose measurement is +arbitrarily old. wp1 therefore bounds the ROUTING read at one hour +(`010` §`account-quota-rank.ts`) while leaving the DISPLAY read unbounded, because wp2 +shows the age and a human can discount it. Same number, two consumers, different +obligations. + +## C. The real gaps + +| # | Surface | Gap | Evidence | Disposition | +|---|---|---|---|---| +| 1 | per-account quota read | `supportsPerAccountQuota` excludes `meta-muse`, and it is the wrong predicate anyway — it gates a **probe** | `quota.ts:1477`, `:1683`, `:1629` | wp1: a separate cache-only reader | +| 2 | quota write | nothing ever keys `meta-muse\0` in `accountQuotaCache` | `quota.ts:1430` | wp1 | +| 3 | `?quota=1` enrichment | gated on the probe predicate | `oauth-account-routes.ts:284` | wp1 | +| 4 | observation age | `QuotaBars` renders no timestamp; `AccountQuota.updatedAt` exists but is unread | `QuotaBars.tsx:164`, `codex-quota-utils.ts:21` | wp2 | +| 5 | provider-level row | `maybeFetchProviderQuota` has no `meta-muse` branch, so the Providers overview card is empty | `quota.ts:2298-2301` | wp3 decides: derive from the cache or record NOT-APPLICABLE | +| 6 | provider note | says the quota is unread — false once wp1 lands | `registry.ts:1543` | wp3 | +| 7 | docs-site | same stale sentence | `docs-site/.../providers.md:475` | wp3 | +| 8 | `ocx account refresh` | prints "no quota report" | `src/cli/account-extended.ts:328` | wp3: must stay probe-free by design; make the message honest | +| 9 | `skills/ocx` recipes | no `meta-muse` account recipe | `skills/ocx/references/03_recipes.md:16` | wp3 | + +## D. Surfaces that are NOT gaps, and why + +Recorded now so wp3 does not spend effort proving them twice. + +- **Connection test.** `provider-routes.ts:1195` short-circuits any provider with + `liveModels === false` to `{ applicable: false, reason: "static_catalog" }` before any + network call. `meta-muse` sets `liveModels: false` deliberately (`registry.ts:1538`): + the authenticated roster carries `muse-image-1.0` and `muse-voice-transcribe-1.0`, + which a Responses-agent provider cannot drive. `kiro` is in exactly the same class. + **NOT-APPLICABLE by design, not a gap.** +- **`clear-cooldown`.** Anthropic-only (`oauth-account-routes.ts:465`) because the + generic failover health map is process-local (`generic-account-failover.ts:78`). + Provider-wide absence; out of scope for a Muse unit. +- **Account import.** `ACCOUNT_IMPORT_PROVIDER = "google-antigravity"` + (`src/oauth/account-import/types.ts:3`) — a cockpit-tools document format with no Meta + analogue. +- **401 replay.** `FORCE_REFRESH_PROVIDERS = new Set(["xai", "github-copilot", "kiro"])` + (`src/oauth/index.ts:540`). Muse holds a **static API key** — `003` §B measured the + OAuth `access_token` returning 401 while the sibling `api_key` returns 200 — so there + is nothing to force-refresh. Adding it would replay an identical credential. +- **Background refresh.** `defaultRefreshPolicy: "disabled"` (`src/oauth/index.ts:240`), + the same posture as `anthropic`, for the same reason: the vendor restricts use outside + its own client, so every exchange stays attributable to a user action. + +## E. The seam wp1 uses, measured + +`050` planned to add `onSubscriptionUsage` to `SseInspectorHandlers`. That handler is +unnecessary — the general seam already exists and is strictly better placed: + +```ts +// src/server/relay.ts:834 +onParsedPayload?: (payload: unknown) => void; +``` + +It fires for **every** parsed SSE frame, and critically it fires *before* terminal +handling (`relay.ts:1020`), inside a `try/catch` that guarantees inspection never throws +into the pump (`:1021`). All three passthrough construction sites already thread it: + +| Site | Line | How | +|---|---|---| +| eager relay | `core.ts:4811` | `onParsedPayload: noteInspectedPayload` | +| tee + terminal | `core.ts:4862` → `relay.ts:1357` | via `inspectionConsumerOptions` | +| tee metadata-only | `core.ts:4862` → `relay.ts:1411` | same options object | + +Both tee consumers read the same `inspectionConsumerOptions` literal built at +`core.ts:4857`, so extending `noteInspectedPayload` covers every passthrough shape at +once. **This is why `relay.ts` is not in wp1's file list.** + +### The serving account, in that scope + +`runResponses` holds these at the point the inspector is constructed: + +| Variable | Declared | Meaning | +|---|---|---| +| `genericFailoverAccountId` | `core.ts:3309`, set `:3444` | the account `meta-muse` actually dispatched on; rebound at each rotation site (`:5131`, `:5445`, `:6134`) | +| `resolved: OAuthAccessSnapshot` | `:3397` | carries `.accountId` and `.generation` | +| `replayOAuthCredentialSnapshot` | `:3304`, filled `:3431` | `{ accountId, generation }` | +| `anthropicPoolAccountId` | `:3305` | Anthropic only | + +`genericFailoverAccountId` is rebound by every rotation, so reading it **at event time** +— not at handler-construction time — is what makes attribution survive a mid-turn +failover. That is the difference between recording the quota of the account that served +the turn and recording it against the account that failed. + +## F. Generation fencing: the correction `050` already carried, verified + +`050` recorded an A-gate finding that capturing the generation immediately before the +write cannot see a config change that happened **earlier in the turn**. The tree +confirms the mechanism it must use: + +```ts +// src/providers/quota.ts:1470 +function mayCommitAccountQuotaKey(key: string, writerGeneration: number): boolean { + return writerGeneration >= lastReconciledGeneration || liveAccountQuotaKeys.has(key); +} +``` + +Every existing writer follows the same shape: `captureConfigGeneration()` before the +await, `mayCommitAccountQuotaKey` before the `set` (`quota.ts:1378`, `:1404`, `:1648`). +A streaming turn is a long await, so the caller must capture when it resolves the +credential and pass the number in. + +Reconciliation removes rows whose account no longer exists +(`reconcileProviderAccountQuotaRows`, registered as `provider-quota-history` in +`src/lib/state-store-registrations.ts:109`), so a logged-out account cannot leave a stale +bar behind. + +## G. Persistence, measured + +`persistAccountQuotaCache` (`quota.ts:1449`) debounces into +`schedulePersistAccountQuotas` (`src/providers/account-quota-disk.ts:59`), and +`readPersistedAccountQuotas` (`:40`) drops rows older than `DISK_MAX_AGE_MS` = 6 hours +(`:28`). + +**This bounds the honesty problem in wp2.** A passive observation can be arbitrarily old +in memory, but a restart discards anything past six hours. The in-memory TTL +(`ACCOUNT_QUOTA_TTL_MS` = 10 minutes, `quota-wire.ts:22`) is a **probe** TTL — it decides +when to re-probe, and `sweepExpiredProviderAccountQuotaRows` is exported but not +registered as a `sweepExpired` callback (`src/lib/state-store-registrations.ts:109` +registers only `reconcileGeneration`), so an unprobed row is not swept on the TTL tick. +A passive row therefore survives in memory past 10 minutes, which is correct for this +feature and is exactly why the age must be displayed. + +## H. Method note + +The parity inventory was dispatched as a read-only reviewer packet demanding a file:line +for every claim, precisely because the predecessor unit's plan had drifted from the tree +in three places. Two of its findings — the pre-existing `onParsedPayload` seam and the +already-generic GUI bar rendering — deleted planned work rather than adding it. A +roadmap written from the old plan alone would have shipped a duplicate handler and a +redundant component change. diff --git a/devlog/_plan/260903_muse_provider_parity/010_wp1_passive_quota_core.md b/devlog/_plan/260903_muse_provider_parity/010_wp1_passive_quota_core.md new file mode 100644 index 0000000000..cf40dd4099 --- /dev/null +++ b/devlog/_plan/260903_muse_provider_parity/010_wp1_passive_quota_core.md @@ -0,0 +1,314 @@ +# wp1 — passive Muse subscription quota: parser, seam, cache + +Own PR, base `dev`. Branch: `codex/meta-muse-passive-quota`. + +Research: `001` (this unit) and `260903_muse_spark_plan_oauth/003` §E. Implementation only. + +## Decisions taken here, so Build does not have to make them + +| Question | Decision | Why | +|---|---|---| +| Where to observe | `noteInspectedPayload` in `core.ts:3891`, on the existing `onParsedPayload` seam | `001` §E: covers all three passthrough sites at once; `relay.ts` untouched | +| Which account | `genericFailoverAccountId` read **at event time** | it is rebound at every rotation site; reading it at construction attributes the turn to the account that failed | +| Translated path | **not covered**, deliberately | `openai-responses.ts`'s switch drops unknown types (`004` Q3, answered) | +| `supportsPerAccountQuota` | **stays false** | it gates a probe whose fallback ships a Meta bearer to Anthropic (`quota.ts:1629`) | +| Read path | a new cache-only function, not the probe function behind a flag | `fetchProviderAccountQuotas` probes; a passive provider has nothing to probe | +| Refresh | does not exist | obtaining a fresh value would mean spending an inference turn | +| `tier` | dropped | an opaque numeric id, not the label the CLI prints | + +## NEW `src/providers/muse-subscription-usage.ts` + +```ts +import { normalizePercent, normalizeResetAt, asRecord } from "./quota-wire"; +import type { ProviderQuota, ProviderQuotaWindow } from "./quota-types"; + +/** The SSE frame type Meta emits on streaming turns. */ +export const MUSE_SUBSCRIPTION_USAGE_TYPE = "response.subscription_usage"; + +export function parseMuseSubscriptionUsage(payload: unknown): ProviderQuota | null; +``` + +Mapping table, all mandatory: + +| Source | Target | Rule | +|---|---|---| +| `subscription.window.used_percent` | `fiveHourPercent` | `normalizePercent`; assign **only** if `window_duration_mins === 300` | +| `subscription.window.resets_at` | `fiveHourResetAt` | `normalizeResetAt` (unix seconds; `epochMillis` scales) | +| `subscription.weekly.used_percent` | `weeklyPercent` | `normalizePercent` | +| `subscription.weekly.resets_at` | `weeklyResetAt` | `normalizeResetAt` | +| `window` with any other `window_duration_mins` | `customWindows[]` | label `"${duration}m"`; never forced into the 5h slot | +| `subscription.tier` | — | dropped | +| — | `updatedAt` | `Date.now()`, never from the payload | + +Returns `null` — never throws — when the payload is not an object, carries no +`subscription`, or yields no usable window. Either window may be absent independently. +A window present but unparseable yields no slot rather than a zero. + +**Why `window_duration_mins` is checked rather than assumed:** the measured payload says +300, but a plan change could move it, and silently filing a 10-hour window in the +five-hour slot would understate usage by the ratio of the windows — a wrong number +presented with full confidence. + +## MODIFY `src/providers/quota.ts` + +Three additions, all beside the existing per-account block (after +`setCachedProviderAccountQuotaForTests`, `:1494`). + +```ts +/** + * Providers whose per-account quota is OBSERVED in-band, never probed. + * + * Deliberately separate from supportsPerAccountQuota: that predicate gates + * fetchAccountQuota, whose fallback branch sends any non-Kiro/non-Antigravity bearer to + * Anthropic's usage endpoint. Meta exposes no quota endpoint at all (17 paths probed, + * all 404), so there is nothing for that path to call. + */ +export function hasPassiveAccountQuota(provider: string): boolean { + return provider === "meta-muse"; +} + +/** + * Record a quota observed on a streaming turn. + * + * The CALLER captures writerGeneration when it resolves the serving credential, not + * here: a streaming turn is a long await, and capturing at write time cannot see a + * config change that happened earlier in the same turn. + */ +export function recordPassiveAccountQuota( + provider: string, + accountId: string, + quota: ProviderQuota, + writerGeneration: number, +): void { + if (!hasPassiveAccountQuota(provider) || !accountId) return; + const key = accountCacheKey(provider, accountId); + if (!mayCommitAccountQuotaKey(key, writerGeneration)) return; + accountQuotaCache.set(key, { ts: Date.now(), quota }); + persistAccountQuotaCache(); +} + +/** Cache-only per-account rows for a passive provider. Never probes. */ +export function readPassiveProviderAccountQuotas(provider: string): ProviderAccountQuota[] { + if (!hasPassiveAccountQuota(provider)) return []; + hydrateAccountQuotaCache(); + const set = getAccountSet(provider); + if (!set) return []; + const rows: ProviderAccountQuota[] = []; + for (const account of set.accounts) { + const entry = accountQuotaCache.get(accountCacheKey(provider, account.id)); + if (entry?.quota) rows.push({ accountId: account.id, quota: entry.quota }); + } + return rows; +} +``` + +Three details that are not arbitrary: + +- `hydrateAccountQuotaCache()` must be called in the reader. It is idempotent + (`diskHydrated`, `quota.ts:1440`) and is otherwise only reached from probe paths that a + passive provider never enters — without it, a restart shows nothing until the next + streaming turn even though the row is on disk. +- Absent rows are **omitted**, not returned with `quota: null`. A user who has not run a + streaming turn has no observation, and `unavailable` would claim a failed probe that + never happened. +- `sweepExpiredOnWrite` is **not** called here. Existing probe writers call it because + they run on a poll; this runs on every streaming turn, and a state sweep on the request + path is exactly the hot-path work `state-store-registrations.ts:97` warns against. + +## MODIFY `src/server/responses/core.ts` + +One capture at credential resolution, one branch in the existing payload handler. + +Near `genericFailoverAccountId` (`:3309`), add: + +```ts +// Captured where the credential is resolved, not at write time: see quota.ts +// recordPassiveAccountQuota. Only meta-muse observes a quota, so this stays 0 elsewhere. +let passiveQuotaWriterGeneration = 0; +``` + +set alongside `genericFailoverAccountId = resolved.accountId` (`:3444`): + +```ts +if (hasPassiveAccountQuota(route.providerName)) passiveQuotaWriterGeneration = captureConfigGeneration(); +``` + +Extend `noteInspectedPayload` (`:3891`). The existing body opens with an early return +for the undeclared-tool guard, so the observation goes **before** it: + +```ts +const noteInspectedPayload = (payload: unknown) => { + if (passiveQuotaObserved && route.providerName === "meta-muse") { + const record = payload as { type?: unknown } | null; + if (record && typeof record === "object" && record.type === MUSE_SUBSCRIPTION_USAGE_TYPE) { + const quota = parseMuseSubscriptionUsage(payload); + // Read the account HERE, not at construction: rotation rebinds it mid-turn. + const accountId = genericFailoverAccountId; + if (quota && accountId) { + recordPassiveAccountQuota("meta-muse", accountId, quota, passiveQuotaWriterGeneration); + } + } + } + if (!undeclaredToolGuardActive || inspectionSawUndeclaredTool) return; + // ... unchanged +}; +``` + +where `passiveQuotaObserved` is a `const` computed once beside the handler: + +```ts +const passiveQuotaObserved = hasPassiveAccountQuota(route.providerName) + && route.provider.authMode === "oauth"; +``` + +**Ordering is load-bearing.** The undeclared-tool guard returns early once it has fired +(`inspectionSawUndeclaredTool`), so an observation placed after it would be dropped for +the rest of any turn that tripped the guard — a turn that still legitimately reports +usage. + +**Import discipline.** `core.ts` is one of the three files `tests/core-lab-boundary.test.ts` +guards. Both new imports (`src/providers/quota`, `src/providers/muse-subscription-usage`) +are already-reachable or leaf modules: `quota.ts` is imported by `core.ts` today, and the +parser imports only `quota-wire` and `quota-types`. Neither reaches `src/lab/`. The +parser must **not** import `quota.ts` — that would be a cycle. + +## MODIFY `src/server/management/oauth-account-routes.ts` + +At `:284`, the enrichment gate becomes: + +```ts +const passiveQuota = url.searchParams.get("quota") === "1" && hasPassiveAccountQuota(provider); +const wantQuota = url.searchParams.get("quota") === "1" && supportsPerAccountQuota(provider); +if (!wantQuota && !passiveQuota) return jsonResponse(projectAccounts()); +const rows = passiveQuota + // No probe, and ?refresh=1 is ignored: there is nothing to refresh. + ? readPassiveProviderAccountQuotas(provider) + : await fetchProviderAccountQuotas(provider, url.searchParams.get("refresh") === "1"); +``` + +The existing `byId` merge below is unchanged and already omits `quotaUnavailable` when +the row does not carry it. + +`?refresh=1` is accepted and ignored rather than rejected: the GUI sends it on a manual +refresh for every provider, and a 400 would surface an error for an action that is simply +a no-op here. + +## MODIFY `src/oauth/account-quota-rank.ts` — A-gate amendment + +**Blocker found at the audit gate, folded here.** `headroomOf` (`:36`) reads +`getCachedProviderAccountQuota` and applies **no staleness bound**: + +```ts +// src/providers/quota.ts:1489 +export function getCachedProviderAccountQuota(provider: string, accountId: string): ProviderQuota | null { + const entry = accountQuotaCache.get(accountCacheKey(provider, accountId)); + return entry?.quota ?? null; // no ts check +} +``` + +For every provider that exists today this is safe by construction: a row is only written by +a probe, and `fetchAccountQuota` re-probes once `ACCOUNT_QUOTA_TTL_MS` (10 minutes, +`quota-wire.ts:22`) has passed, so a row consulted for routing is at most that old. +**The passive path breaks that invariant.** Nothing re-probes, and `001` §G established +that account-quota rows are not swept on the TTL tick, so a Muse row can be hours or days +old in memory and up to six hours old after a restart. + +Left unfixed, `preferredInitialAccount` (`generic-account-failover.ts:281`) would send the +first attempt of every turn to whichever account looked best whenever it was last +observed — plausibly the one that has since been exhausted. That is worse than the +current unranked behaviour, because it is confidently wrong rather than uninformed. + +```ts +/** + * How old a PASSIVELY observed quota may be and still steer routing. + * + * A probed row is implicitly fresh: fetchAccountQuota re-probes after + * ACCOUNT_QUOTA_TTL_MS. A passive row has no such refresh, so the bound is explicit + * here. It is deliberately longer than the probe TTL — an hour-old reading of a + * five-hour window is still informative — and deliberately far shorter than the + * six-hour disk horizon, which exists to preserve a value for DISPLAY, where the age is + * shown to the user and no automatic decision rides on it. + */ +const PASSIVE_HEADROOM_MAX_AGE_MS = 60 * 60_000; +``` + +In `headroomOf`, immediately after the null check: + +```ts + if (hasPassiveAccountQuota(provider) && Date.now() - quota.updatedAt > PASSIVE_HEADROOM_MAX_AGE_MS) return null; +``` + +Returning `null` is the correct shape, not a zero or a low rank: it reproduces "no +evidence", which `rankAccountsByHeadroom` (`:71`) and `hasHeadroomEvidence` (`:87`) +already handle by leaving the ring untouched. The stale-row case therefore degrades to +exactly today's behaviour rather than to a different wrong answer. + +The display path is deliberately **not** bounded this way. wp2 shows the age, so an old +number is labelled rather than hidden — the user can judge it, and a routing algorithm +cannot. + +Added tests in `tests/muse-passive-quota-cache.test.ts`: + +- a passive row younger than the bound produces headroom; one older produces `null` +- `hasHeadroomEvidence` is false for a roster whose only rows are stale +- an `anthropic` row of the same age is unaffected (the bound is passive-only) + +## Tests + +`tests/muse-subscription-usage.test.ts` — parser, fixture-driven: + +- the measured payload from `003` §E → both windows, correct millisecond resets +- `window_duration_mins: 600` → `customWindows`, and `fiveHourPercent` **undefined** +- weekly-only; window-only (each independently absent) +- `used_percent: 150` → clamped to 100 (`normalizePercent` clamps rather than rejects) +- `used_percent: "12"` → 12 (`toFiniteNumber` accepts numeric strings) +- missing `subscription`; non-object; `null`; array → `null`, no throw +- `tier` never appears in the output +- `updatedAt` is local, not the payload's `resets_at` + +`tests/muse-passive-quota-cache.test.ts`: + +- `recordPassiveAccountQuota` writes under the serving account key and + `getCachedProviderAccountQuota` reads it back +- a stale `writerGeneration` discards the write +- `readPassiveProviderAccountQuotas` omits accounts with no observation +- the row persists and rehydrates after a simulated restart +- `hasPassiveAccountQuota("meta-muse")` is true while + `supportsPerAccountQuota("meta-muse")` stays **false** — the exfiltration guard from + wp4 must survive this phase +- `recordPassiveAccountQuota("anthropic", ...)` is a no-op + +`tests/muse-passive-quota-observation.test.ts` — the seam, driven through +`createSseInspector` with a recorded transcript: + +- a transcript containing the event invokes the handler exactly once +- a transcript without it never does +- the payload is delivered before the terminal frame is processed +- a handler that throws does not break the pump (guaranteed by `relay.ts:1021`; asserted + so a future refactor cannot silently remove the guarantee) + +No live call, no real Keychain, no network in any test. + +## Verification + +```bash +bun test tests/muse-subscription-usage.test.ts tests/muse-passive-quota-cache.test.ts \ + tests/muse-passive-quota-observation.test.ts tests/meta-muse-oauth.test.ts \ + tests/provider-account-quota.test.ts tests/oauth-accounts-api.test.ts \ + tests/core-lab-boundary.test.ts +bun run test:changed +bun x tsc --noEmit +bun run privacy:scan +``` + +`tests/core-lab-boundary.test.ts` is listed explicitly because this phase edits +`core.ts`, one of the three files it guards, and `test:changed` follows the import graph +from changed modules — it would select that test only if the boundary test itself +imports `core.ts`, which is not something to assume. + +## Terminal outcome + +`DONE` when a streaming `meta-muse` turn populates the serving account's five-hour and +weekly percentages, `/api/oauth/accounts?provider=meta-muse"a=1` returns them, a +restart preserves the observation, and no code path issues an inference call to refresh a +quota. diff --git a/devlog/_plan/260903_muse_provider_parity/020_wp2_observation_age_ui.md b/devlog/_plan/260903_muse_provider_parity/020_wp2_observation_age_ui.md new file mode 100644 index 0000000000..b9140c8c72 --- /dev/null +++ b/devlog/_plan/260903_muse_provider_parity/020_wp2_observation_age_ui.md @@ -0,0 +1,134 @@ +# wp2 — the dashboard states how old the observation is + +Own PR, base `dev`, after wp1 lands. Branch: `codex/muse-quota-observation-age`. + +## Why this is a work-phase and not a line in wp1 + +Every other quota bar in this dashboard answers *what is true now*: Anthropic's is at +most `ACCOUNT_QUOTA_TTL_MS` (10 minutes) old, and a stale probe is marked +`quotaUnavailable` (`oauth-account-routes.ts:298`). A Muse bar answers *what was true at +the last streaming turn*, which can be hours old — bounded only by the six-hour disk +horizon (`account-quota-disk.ts:28`), and unbounded in memory because the account-quota +TTL sweep is not registered as a `sweepExpired` callback (`001` §G). + +Rendering the two identically is the one way this feature can actively mislead. So the +age is the honesty condition, not decoration. + +## Scope boundary + +`QuotaBars` is shared by the Codex account pool, the provider overview, the combo +workspace, and every OAuth account row. **The change must be additive and opt-in**: a +component that starts rendering a timestamp for every caller would put an age on +Anthropic's bars, where it is noise. + +## MODIFY `gui/src/components/QuotaBars.tsx` + +One optional prop, rendered only when passed: + +```ts + /** + * Render "observed ago" beside the bars. Set ONLY for a passively observed + * quota (meta-muse), where the value can be arbitrarily old. A probed provider + * refreshes on its own TTL and must not carry this. + */ + observedAt?: number; +``` + +Rendered above the rows in both layouts, from the existing `quota.updatedAt`: + +```tsx +{observedAt !== undefined && ( +

{t("quota.observedAgo").replace("{age}", formatObservedAge(observedAt, t, locale))}

+)} +``` + +`formatObservedAge` is a new exported helper in the same file (co-located with +`buildQuotaRows`, which is already exported for the same reason). Buckets, chosen so the +string never implies more precision than an observation has: + +| Elapsed | Output | +|---|---| +| < 60s | `quota.observedJustNow` | +| < 60m | `${n}m` | +| < 24h | `${n}h` | +| otherwise | `${n}d` | + +A negative elapsed (clock skew between the write and the browser) renders as just-now +rather than a negative number. + +## MODIFY `gui/src/components/provider-workspace/ProviderAuthPanel.tsx` + +At the `QuotaBars` call (`:522`), pass the prop **only** for the passive provider: + +```tsx + +``` + +The provider id is compared here rather than a capability being plumbed through the +account payload: the GUI has no other consumer for such a flag, and one string in one +render site is easier to audit than a new field on every account row. If a second passive +provider appears, this becomes a server-sent boolean — recorded as the migration, not +done speculatively. + +**The absent case needs no change.** `ProviderAuthPanel.tsx:517` already renders the +quota block only when `account.quota != null || account.quotaUnavailable || +reserveQuotaSlots`, and `QuotaBars` returns `null` when `buildQuotaRows` is empty and +`pending` is false (`QuotaBars.tsx:193`). An account with no observation renders nothing, +which is already correct — c5's "not a zero bar" half is asserted, not implemented. + +## MODIFY the locale files + +Three keys in `gui/src/i18n/en.ts`, near the existing `quota.*` block: + +```ts + "quota.observedAgo": "Observed {age} ago", + "quota.observedJustNow": "Observed just now", + "quota.observedHint": "Meta reports usage only during a streaming response, so this is the last value seen — not a live reading.", +``` + +`quota.observedHint` is the `title` on the age line. Without it the user has no way to +know why this one provider's number lags. + +Every other locale file (`ko`, `ja`, `zh`, `zh-TW`, `fr`, `de`, `ru`, `tr`) gets the +same three keys. Translate `ko` and leave the rest on the English string if no confident +translation exists — a missing key breaks the typed `TFn` lookup, which is the failure +mode to avoid. + +## Tests + +`gui/tests/quota-observed-age.test.tsx`: + +- `formatObservedAge` bucket boundaries: 59s, 60s, 59m, 60m, 23h, 24h, and a negative +- `QuotaBars` without `observedAt` renders no age line (the regression that protects + every other caller) +- with `observedAt` renders it in both `compact` and `stacked` layouts +- a `meta-muse` account row with a quota shows the age; an account without a quota + renders no bars and no age + +## Verification + +```bash +bun test gui/tests/quota-observed-age.test.tsx gui/tests/oauth-tos-warning-gate.test.tsx +bun x tsc --noEmit +bun run lint:gui +cd gui && bun run build +``` + +Plus render grounding (C-RENDER-GROUNDING-01): this phase changes a rendered surface, so +C loads `http://localhost:10100/#providers` against a proxy carrying a real observation, +screenshots the Muse account row, and reads the screenshot back. A built-but-unviewed +bundle is not evidence. + +## Terminal outcome + +`DONE` when the Muse account row shows both windows with a truthful age, the same +component renders no age for Anthropic, and an account with no observation renders +nothing at all. diff --git a/devlog/_plan/260903_muse_provider_parity/030_wp3_parity_closeout.md b/devlog/_plan/260903_muse_provider_parity/030_wp3_parity_closeout.md new file mode 100644 index 0000000000..d56b9a2816 --- /dev/null +++ b/devlog/_plan/260903_muse_provider_parity/030_wp3_parity_closeout.md @@ -0,0 +1,140 @@ +# wp3 — close or record every remaining parity surface + +Own PR, base `dev`, after wp1 and wp2 land. Branch: `codex/meta-muse-parity-closeout`. + +This phase exists because "make Meta first-class" is only verifiable against an +enumerated list. `001` §C is that list; this doc dispositions every row. + +## The rule this phase applies + +A surface is closed when `meta-muse` behaves like a first-class provider, or recorded +NOT-APPLICABLE when the difference follows from a **measured property of Meta's API** — +never from "we did not get to it". Every NOT-APPLICABLE carries a file:line and a reason +that would survive a reviewer asking "why not just add it to the allowlist?". + +## 1. Provider note — CLOSE (`src/providers/registry.ts:1543`) + +The note currently says: + +> Meta reports subscription window usage inside streaming responses, but OpenCodex does +> not yet read or display it, and there is no endpoint to query it on demand. + +False after wp1. Replace that sentence with: + +> OpenCodex reads Meta's subscription windows from streaming responses and shows the +> last observed value with its age; there is no endpoint to query them on demand, so a +> fresh reading requires a streaming turn and translated (non-passthrough) turns do not +> report one. + +Both clauses after the semicolon are load-bearing: the first explains why the number can +be stale, the second is the documented gap from `004` Q3 rather than a silent one. + +`tests/meta-model-api-provider.test.ts` asserts note substrings — check before editing. + +## 2. docs-site — CLOSE (`docs-site/src/content/docs/guides/providers.md:475`) + +Same correction, English only. The surrounding paragraphs about the ToS boundary and +per-team rate limits are unchanged and remain accurate. + +## 3. `ocx account refresh meta-muse` — CLOSE the message, keep the behaviour + +`src/cli/account-extended.ts:328` prints "no quota report" because +`maybeFetchProviderQuota` has no `meta-muse` branch. **The behaviour is correct and must +not change** (c4: no path may issue an inference call to refresh a quota). The message is +what misleads — it reads like a failure. + +Emit, for a provider where `hasPassiveAccountQuota` is true: + +> meta-muse reports usage only during a streaming response; there is nothing to refresh. +> Run a request through this provider to update it. + +A CLI that explains an intentional absence is the difference between a documented design +and an apparent bug. + +## 4. Provider-level overview card — DECIDE, then close + +`quota.ts:2298-2301` gives `/api/provider-quotas` a row for anthropic, antigravity and +kiro; `meta-muse` has none, so `ProviderCapacityQuota.tsx:47` renders "No quota data". + +Two honest options, decided in wp3's P against the tree at that time: + +- **(a)** derive the provider row from the cached active account's observation — no + probe, consistent with wp1's seam, and it fills a visibly empty card. +- **(b)** record NOT-APPLICABLE: the provider card means "the provider's capacity", and + Meta's documented limits are **per team, not per key** (`001`, `003` §E), so a + per-account subscription window is the wrong quantity to promote to provider level. + +**Current lean: (b), with the empty card given an explanatory string** rather than a +number that means something different from every other provider's provider-level bar. +wp3's audit gate decides; whichever is chosen, the reason is recorded here. + +## 5. `skills/ocx` — CLOSE (`skills/ocx/references/03_recipes.md`) + +Add a `meta-muse` account recipe covering import login, `ocx account list meta-muse`, +and the passive-quota caveat. `bun run skill:surface:check` must stay green; the surface +map is generated from `src/cli/capabilities.ts`, and `tests/skill-ocx.test.ts` fails if a +hand-written page names a command the registry does not have. + +`src/cli/capabilities.ts:250` also carries a stale line — "`anthropic` is the only OAuth +pool with this setting; other OAuth providers are refused without a round-trip" — which +`001` shows is wrong: generic OAuth providers do reach the pool endpoint, and their +settings persist inertly (`pool-settings-capability.ts:40`). Correct it while here. + +## 6. Recorded NOT-APPLICABLE (no code) + +Each with the measured reason, written into this doc's closing section at D: + +| Surface | Reason | Evidence | +|---|---|---| +| Connection test | `liveModels: false` short-circuits to `static_catalog` before any network call; the authenticated roster carries image and voice models a Responses-agent provider cannot drive. `kiro` is the same class | `provider-routes.ts:1195`; `registry.ts:1538`; `003` §C | +| 401 replay / `FORCE_REFRESH_PROVIDERS` | the credential is a **static API key**; the OAuth `access_token` 401s while the `api_key` returns 200, so there is nothing to force-refresh | `src/oauth/index.ts:540`; `003` §B | +| Background refresh | `defaultRefreshPolicy: "disabled"`, same posture as `anthropic`: the vendor restricts use outside its own client, so every exchange stays attributable to a user action | `src/oauth/index.ts:240` | +| Account import | `ACCOUNT_IMPORT_PROVIDER` is a cockpit-tools document format with no Meta analogue | `src/oauth/account-import/types.ts:3` | +| `clear-cooldown` | anthropic-only because the generic failover health map is process-local — a provider-wide gap, not a Muse gap | `oauth-account-routes.ts:465`; `generic-account-failover.ts:78` | +| GUI generic pool card | no dashboard editor exists for **any** generic OAuth provider | `ProviderAuthPanel.tsx:353` | +| Translated-path quota | `openai-responses.ts` dispatches on `payload.type` through a switch with no case for the event | `004` Q3 | + +The last two are the honest ones to resist closing: both are real absences a user could +hit, and both are provider-wide rather than Meta-specific. Fixing either inside a Muse +unit would be scope creep that lands untested for its other providers. + +## 7. Side effect worth stating: routing changes, not just display + +`001` §B measured that headroom-ranked pre-dispatch selection +(`generic-account-failover.ts:281`) and quota-aware cooldown are already wired for any +generic failover provider but inert while `hasHeadroomEvidence` is false. wp1's cache +**arms both** for a user with two or more Muse accounts. + +That is desirable — it is what "first-class" means here — but it must be stated in the +PR description, because a reviewer reading a quota-display PR would not expect account +selection order to change. `001` §B and `003` §F establish the soundness: the RPM/TPM +limits are per team, but subscription windows are per subscription, so two Muse accounts +carry genuinely different headroom. + +It is desirable **only within the staleness bound wp1 adds**. Unbounded, it is the +opposite: a routing preference computed from a days-old observation is worse than no +preference, because the unranked ring at least rotates. wp1 caps the routing read at one +hour and returns "no evidence" beyond it, which degrades to today's behaviour rather than +to a different wrong answer (`010`, `001` §B). + +wp3's PR description must therefore describe the routing change **and** its bound. A +reviewer told only "quota now steers account selection" would reasonably object; the +bound is what makes the claim defensible. + +## Verification + +```bash +bun test tests/meta-muse-oauth.test.ts tests/meta-model-api-provider.test.ts \ + tests/skill-ocx.test.ts tests/cli-account.test.ts tests/provider-registry-parity.test.ts +bun run skill:surface:check +bun run test:changed +bun x tsc --noEmit +bun run privacy:scan +cd docs-site && bun install --frozen-lockfile && bun run build +``` + +## Terminal outcome + +`DONE` when every row in `001` §C is either closed with a diff or recorded here as +NOT-APPLICABLE with its measured reason, and no user-facing text claims OpenCodex cannot +read a value it now reads. diff --git a/src/oauth/account-quota-rank.ts b/src/oauth/account-quota-rank.ts index b3b4e0bd00..36360c655b 100644 --- a/src/oauth/account-quota-rank.ts +++ b/src/oauth/account-quota-rank.ts @@ -10,7 +10,7 @@ * categories answer the only question rotation asks — "which of these is most likely to * serve the retry" — and within the healthy group a simple headroom sort is enough. */ -import { getCachedProviderAccountQuota } from "../providers/quota"; +import { getCachedProviderAccountQuota, hasPassiveAccountQuota } from "../providers/quota"; import { getKiroAccountExhaustion } from "../providers/kiro-usage"; /** Lower sorts earlier. Unknown sits between measured-healthy and measured-empty. */ @@ -27,6 +27,24 @@ interface Ranked { index: number; } +/** + * How old a PASSIVELY observed quota may be and still steer routing. + * + * A probed row is fresh by construction: it exists only because a probe wrote it, and + * `fetchAccountQuota` re-probes once `ACCOUNT_QUOTA_TTL_MS` has passed. So no caller has + * ever needed an explicit age check, and `getCachedProviderAccountQuota` does not apply + * one. + * + * A passive row breaks that invariant — nothing re-probes it, so it can be hours or days + * old. Routing on such a reading is worse than routing on none: the unranked ring at + * least rotates, while a stale ranking sends every first attempt to an account that may + * have been spent since. The bound is longer than the probe TTL (an hour-old reading of + * a five-hour window is still informative) and far shorter than the six-hour disk + * horizon, which exists to preserve a value for DISPLAY — where the age is shown to the + * user and no automatic decision rides on it. + */ +const PASSIVE_HEADROOM_MAX_AGE_MS = 60 * 60_000; + /** * Remaining headroom across every window the provider reports. * @@ -36,6 +54,9 @@ interface Ranked { function headroomOf(provider: string, accountId: string): number | null { const quota = getCachedProviderAccountQuota(provider, accountId); if (!quota) return null; + // Null, not a low rank: this must reproduce "no evidence" so a stale roster degrades to + // the unranked ring rather than to a differently wrong answer. + if (hasPassiveAccountQuota(provider) && Date.now() - quota.updatedAt > PASSIVE_HEADROOM_MAX_AGE_MS) return null; const percents = [ quota.fiveHourPercent, quota.weeklyPercent, @@ -56,6 +77,12 @@ export function rankAccountsByHeadroom(provider: string, ring: readonly string[] if (ring.length < 2) return [...ring]; let sawEvidence = false; + // Same rule as hasHeadroomEvidence: a passive provider's partial roster must not rank + // at all. The failover path calls this directly (selectFailoverAccount), so the guard + // cannot live only in the pre-dispatch predicate. + if (hasPassiveAccountQuota(provider) && !ring.every(id => headroomOf(provider, id) !== null)) { + return [...ring]; + } const ranked: Ranked[] = ring.map((id, index) => { // A provider-declared exhaustion verdict outranks the percentage: an account may sit at // 100% and still be servable when overage is enabled, and the verdict knows that. @@ -84,6 +111,18 @@ export function rankAccountsByHeadroom(provider: string, ring: readonly string[] * can decline to act on a roster it knows nothing about. */ export function hasHeadroomEvidence(provider: string, ids: readonly string[]): boolean { + // A PASSIVE provider needs evidence for EVERY candidate, not any one of them. + // + // A probe fills the whole roster in one pass (fetchProviderAccountQuotas), so "any" + // and "every" coincide there. An observation arrives one account at a time, so the + // normal passive state is "one measured, N unknown" -- and RANK_UNKNOWN (1) sorts + // AFTER RANK_HEALTHY (0), including behind a measured row sitting at 100% with zero + // headroom. Accepting partial evidence would therefore redirect the first attempt + // AWAY from an unmeasured account and TOWARD the one account known to be spent, which + // is the exact inversion of what ranking is for. + if (hasPassiveAccountQuota(provider)) { + return ids.length > 0 && ids.every(id => headroomOf(provider, id) !== null); + } return ids.some(id => headroomOf(provider, id) !== null || (provider === "kiro" && getKiroAccountExhaustion(`${provider}\u0000${id}`) !== null)); diff --git a/src/providers/muse-subscription-usage.ts b/src/providers/muse-subscription-usage.ts new file mode 100644 index 0000000000..f3ac0b0307 --- /dev/null +++ b/src/providers/muse-subscription-usage.ts @@ -0,0 +1,95 @@ +/** + * Meta's subscription-usage SSE frame. + * + * Meta publishes no quota endpoint — 17 plausible REST paths were probed and every one + * 404s, and no `x-ratelimit-*` header appears on any of three measured request shapes + * (devlog/_plan/260903_muse_spark_plan_oauth/003 §E). The only machine-readable usage + * Meta emits arrives mid-stream, as one extra event alongside the ordinary + * `response.*` sequence on a streaming `POST /v1/responses`. + * + * That inverts the usual seam: this module is fed by the request path, not by a probe, + * and nothing can refresh its output on demand — obtaining a newer value would mean + * spending a real inference turn. + * + * Measured payload (2026-09-03): + * + * ```json + * { "type": "response.subscription_usage", + * "subscription": { + * "tier": "27681393394859588", + * "window": { "used_percent": 0, "resets_at": 1788431188, "window_duration_mins": 300 }, + * "weekly": { "used_percent": 0, "resets_at": 1788739200 } } } + * ``` + */ +import { asRecord, normalizePercent, normalizeResetAt, toFiniteNumber } from "./quota-wire"; +import type { ProviderQuota, ProviderQuotaWindow } from "./quota-types"; + +/** The SSE frame type Meta emits on streaming turns. */ +export const MUSE_SUBSCRIPTION_USAGE_TYPE = "response.subscription_usage"; + +/** Meta's five-hour window, identified by its declared duration rather than assumed. */ +const FIVE_HOUR_WINDOW_MINS = 300; + +/** True when a parsed SSE payload is the subscription-usage frame. */ +export function isMuseSubscriptionUsagePayload(payload: unknown): boolean { + return asRecord(payload)?.type === MUSE_SUBSCRIPTION_USAGE_TYPE; +} + +/** + * Translate the frame into a `ProviderQuota`. + * + * Returns null — never throws — for anything unrecognizable. This runs inside SSE + * inspection on a live request, where the only acceptable failure is silence: a parse + * error must not cost the user their turn. + * + * `subscription.tier` is deliberately dropped. It is an opaque numeric id, not the plan + * label the Muse CLI prints, so surfacing it would show a meaningless number. + */ +export function parseMuseSubscriptionUsage(payload: unknown): ProviderQuota | null { + const subscription = asRecord(asRecord(payload)?.subscription); + if (!subscription) return null; + + const quota: ProviderQuota = { updatedAt: Date.now() }; + let sawWindow = false; + + const window = asRecord(subscription.window); + if (window) { + const percent = normalizePercent(window.used_percent); + const resetAt = normalizeResetAt(window.resets_at); + const durationMins = toFiniteNumber(window.window_duration_mins); + if (percent !== undefined) { + if (durationMins === FIVE_HOUR_WINDOW_MINS) { + quota.fiveHourPercent = percent; + if (resetAt !== undefined) quota.fiveHourResetAt = resetAt; + sawWindow = true; + } else { + // A window of some other length is NOT forced into the five-hour slot: filing a + // ten-hour window there would understate usage by the ratio of the two windows, + // and would do so with full confidence. Carry it with its real duration instead. + const custom: ProviderQuotaWindow = { + label: durationMins === undefined ? "subscription" : `${durationMins}m`, + percent, + ...(resetAt !== undefined ? { resetAt } : {}), + }; + quota.customWindows = [...(quota.customWindows ?? []), custom]; + sawWindow = true; + } + } + } + + const weekly = asRecord(subscription.weekly); + if (weekly) { + const percent = normalizePercent(weekly.used_percent); + if (percent !== undefined) { + quota.weeklyPercent = percent; + const resetAt = normalizeResetAt(weekly.resets_at); + if (resetAt !== undefined) quota.weeklyResetAt = resetAt; + sawWindow = true; + } + } + + // Either window may be absent independently, but a payload carrying neither says + // nothing — returning a bare `updatedAt` would publish an empty row that the GUI + // would render as a quota with no bars. + return sawWindow ? quota : null; +} diff --git a/src/providers/quota.ts b/src/providers/quota.ts index ca7e9eb993..e0d895815d 100644 --- a/src/providers/quota.ts +++ b/src/providers/quota.ts @@ -1505,6 +1505,78 @@ export function setCachedProviderAccountQuotaForTests( accountQuotaCache.set(key, { ts: Date.now(), quota }); } +/** + * Providers whose per-account quota is OBSERVED in-band, never probed. + * + * Deliberately separate from `supportsPerAccountQuota` rather than folded into it. That + * predicate gates `fetchAccountQuota`, whose fallback branch sends any + * non-Kiro/non-Antigravity bearer to Anthropic's usage endpoint — so adding `meta-muse` + * there without a dedicated branch would ship a Meta credential to Anthropic. And even + * with a branch it would be the wrong predicate: it means "this provider can be probed", + * and Meta publishes no quota endpoint to probe. + */ +export function hasPassiveAccountQuota(provider: string): boolean { + return provider === "meta-muse"; +} + +/** + * Record a quota observed in-band on a streaming turn. + * + * The CALLER captures `writerGeneration` when it resolves the serving credential, not + * this function at write time. A streaming turn is a long await, and a generation + * captured immediately before the write cannot see a config or account change that + * happened EARLIER in the same turn — which is exactly the case the fence exists for. + */ +export function recordPassiveAccountQuota( + provider: string, + accountId: string, + quota: ProviderQuota, + writerGeneration: number, +): void { + if (!hasPassiveAccountQuota(provider) || !accountId) return; + const key = accountCacheKey(provider, accountId); + if (!mayCommitAccountQuotaKey(key, writerGeneration)) return; + // Hydrate BEFORE writing, not only on the read path. `persistAccountQuotaCache` + // serializes the whole in-memory map, so a passive write that lands before anything + // has read the cache would persist this one row and erase every other provider's + // saved row -- and `diskHydrated` would then stop any later reader from recovering + // them. A probe writer cannot hit this because its own read hydrates first; an + // observation arrives unprompted, so it must hydrate itself. + hydrateAccountQuotaCache(); + accountQuotaCache.set(key, { ts: Date.now(), quota }); + // Persisted so a restart keeps the last observation: with no probe to re-establish it, + // a forgotten row stays forgotten until the user happens to run another streaming turn. + persistAccountQuotaCache(); + // sweepExpiredOnWrite is deliberately NOT called. Existing probe writers call it + // because they run on a poll; this runs on the request path, where a state sweep does + // not belong. Passive rows are still reclaimed by generation reconciliation + // (reconcileProviderAccountQuotaRows) and by the disk reader's age bound. +} + +/** + * Cache-only per-account rows for a passive provider. Never probes, never refreshes. + * + * An account with no observation is OMITTED rather than returned with `quota: null` and + * `unavailable`: that pair means "a probe was attempted and failed", and no probe was + * ever attempted here. A user who has not yet run a streaming turn simply has no + * measurement, which is not an error state. + */ +export function readPassiveProviderAccountQuotas(provider: string): ProviderAccountQuota[] { + if (!hasPassiveAccountQuota(provider)) return []; + // Idempotent, and otherwise only reached from probe paths a passive provider never + // enters — without it a restart shows nothing until the next streaming turn, even + // though the row is sitting on disk. + hydrateAccountQuotaCache(); + const set = getAccountSet(provider); + if (!set) return []; + const rows: ProviderAccountQuota[] = []; + for (const account of set.accounts) { + const entry = accountQuotaCache.get(accountCacheKey(provider, account.id)); + if (entry?.quota) rows.push({ accountId: account.id, quota: entry.quota }); + } + return rows; +} + export function sweepExpiredProviderAccountQuotaRows(now = Date.now()): number { let removed = 0; for (const [key, entry] of accountQuotaCache) { diff --git a/src/server/management/oauth-account-routes.ts b/src/server/management/oauth-account-routes.ts index 0dee101723..9206f96bd5 100644 --- a/src/server/management/oauth-account-routes.ts +++ b/src/server/management/oauth-account-routes.ts @@ -30,7 +30,7 @@ import { enrichProviderFromCatalog, listKeyLoginProviders } from "../../oauth/ke import { deriveProviderPresets } from "../../providers/derive"; import { providerCodexAccountMode } from "../../providers/registry"; import { routedSlug, slugEquals } from "../../providers/slug-codec"; -import { clearAccountQuotaCache, clearProviderQuotaCache, fetchProviderAccountQuotas, fetchProviderQuotaReports, supportsPerAccountQuota } from "../../providers/quota"; +import { clearAccountQuotaCache, clearProviderQuotaCache, fetchProviderAccountQuotas, fetchProviderQuotaReports, hasPassiveAccountQuota, readPassiveProviderAccountQuotas, supportsPerAccountQuota } from "../../providers/quota"; import { isCanonicalOpenAiForwardProvider } from "../../providers/openai-tiers"; import { clearThreadAccountMap } from "../../codex/routing"; import { @@ -282,11 +282,18 @@ export async function handleOauthAccountRoutes(ctx: ManagementContext): Promise< // account can show its own 5h/weekly bars (not just the active one). Opt-in via ?quota=1 // so the plain account list stays a cheap local read; ?refresh=1 bypasses the TTL. const wantQuota = url.searchParams.get("quota") === "1" && supportsPerAccountQuota(provider); - if (!wantQuota) return jsonResponse(projectAccounts()); + // Meta publishes no quota endpoint: its usage is observed in-band on streaming turns + // and read back from the cache here. `?refresh=1` is accepted and ignored on this + // path rather than rejected -- the GUI sends it for every provider on a manual + // refresh, and a 400 would report an error for what is simply a no-op. + const passiveQuota = url.searchParams.get("quota") === "1" && hasPassiveAccountQuota(provider); + if (!wantQuota && !passiveQuota) return jsonResponse(projectAccounts()); const forceRefresh = url.searchParams.get("refresh") === "1"; // Probing may refresh the active credential and mark needsReauth — project health // from the post-probe store so the response is not stale. - const rows = await fetchProviderAccountQuotas(provider, forceRefresh); + const rows = passiveQuota + ? readPassiveProviderAccountQuotas(provider) + : await fetchProviderAccountQuotas(provider, forceRefresh); const byId = new Map(rows.map(row => [row.accountId, row])); const projected = projectAccounts(); return jsonResponse({ diff --git a/src/server/responses/core.ts b/src/server/responses/core.ts index ce6abf5c3d..e257a02f15 100644 --- a/src/server/responses/core.ts +++ b/src/server/responses/core.ts @@ -219,6 +219,9 @@ import { waitForProviderRequestSlot, } from "../../providers/request-pacing"; import { slugsEquivalent } from "../../providers/slug-codec"; +import { isMuseSubscriptionUsagePayload, parseMuseSubscriptionUsage } from "../../providers/muse-subscription-usage"; +import { hasPassiveAccountQuota, recordPassiveAccountQuota } from "../../providers/quota"; +import { captureConfigGeneration } from "../../lib/state-store-sweeper"; import { applyOpenAiVirtualModel, resolveOpenAiCompactModel } from "../../providers/openai-virtual-models"; import { isUsageDebugEnabled } from "../../usage/debug"; import { readJsonRequestBody, DecompressedBodyTooLargeError, UnsupportedContentEncodingError } from "../request-decompress"; @@ -3308,6 +3311,13 @@ async function handleResponsesInner( // the request actually used, so a concurrent rotation cannot cool an innocent replacement. let genericFailoverAccountId: string | null = null; let genericFailovers = 0; + /** + * Config generation captured where the serving credential is RESOLVED, not where the + * quota is written. A streaming turn is a long await, so a generation captured at write + * time cannot see a config or account change that happened earlier in the same turn — + * the case the fence exists for. Stays 0 for every provider without a passive quota. + */ + let passiveQuotaWriterGeneration = 0; /** * Apply a rotated account's FULL credential snapshot to the live route (#2568d). * @@ -3443,6 +3453,10 @@ async function handleResponsesInner( if (isGenericFailoverProvider(route.providerName, route.provider)) { genericFailoverAccountId = resolved.accountId; } + // Captured beside the account it fences, so the two can never disagree. + if (hasPassiveAccountQuota(route.providerName)) { + passiveQuotaWriterGeneration = captureConfigGeneration(); + } if (route.providerName === "kiro") { // `{}` is intentional: this is an account-scoped request with no stored routing metadata. // Only genuinely accountless adapter calls leave the context undefined and use local/env fallback. @@ -3888,7 +3902,27 @@ async function handleResponsesInner( // check sees nothing undeclared, and the refused turn enters continuation state anyway. So the // rejection is sticky for the whole turn, set from every parsed payload on the inspection side. let inspectionSawUndeclaredTool = false; + const passiveQuotaObserved = hasPassiveAccountQuota(route.providerName) + && route.provider.authMode === "oauth"; const noteInspectedPayload = (payload: unknown) => { + // Meta reports subscription usage ONLY as an in-stream event; there is no endpoint + // to poll (003 §E probed 17 paths, all 404). Observed here rather than behind a + // dedicated inspector handler because onParsedPayload already reaches every + // passthrough shape -- eager relay and both tee consumers -- through this one + // function. + // + // Placed BEFORE the undeclared-tool early return below, which is load-bearing: that + // guard latches for the rest of the turn once it fires, and a turn that tripped it + // still legitimately reports usage. + if (passiveQuotaObserved && isMuseSubscriptionUsagePayload(payload)) { + const quota = parseMuseSubscriptionUsage(payload); + // Read at EVENT time, not at handler construction: failover rebinds this, and the + // quota belongs to the account that actually served the turn. + const servingAccountId = genericFailoverAccountId; + if (quota && servingAccountId) { + recordPassiveAccountQuota(route.providerName, servingAccountId, quota, passiveQuotaWriterGeneration); + } + } // Gated on the same flag as the guard itself: with no readable catalog (or a forward-auth // provider) every name looks undeclared, and flipping this would stop recording continuation // state for exactly the passthrough traffic the guard deliberately stands down for. diff --git a/tests/muse-passive-quota-cache.test.ts b/tests/muse-passive-quota-cache.test.ts new file mode 100644 index 0000000000..cf144dbca5 --- /dev/null +++ b/tests/muse-passive-quota-cache.test.ts @@ -0,0 +1,209 @@ +import { afterEach, beforeEach, describe, expect, test } from "bun:test"; +import { mkdtempSync, writeFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { getAccountSet, saveCredential } from "../src/oauth/store"; +import { + clearAccountQuotaCache, + getCachedProviderAccountQuota, + hasPassiveAccountQuota, + readPassiveProviderAccountQuotas, + recordPassiveAccountQuota, + resetProviderQuotaReconcileStateForTests, + setCachedProviderAccountQuotaForTests, + supportsPerAccountQuota, +} from "../src/providers/quota"; +import { hasHeadroomEvidence, rankAccountsByHeadroom } from "../src/oauth/account-quota-rank"; +import { captureConfigGeneration } from "../src/lib/state-store-sweeper"; +import type { ProviderQuota } from "../src/providers/quota-types"; +import { removeTreeWithRetry } from "./helpers/remove-tree"; + +const previousOpencodexHome = process.env.OPENCODEX_HOME; +let opencodexHome: string; + +const FIRST = { accountId: "muse-first", email: "first@example.com" }; +const SECOND = { accountId: "muse-second", email: "second@example.com" }; + +function quotaAt(fiveHour: number, updatedAt = Date.now()): ProviderQuota { + return { fiveHourPercent: fiveHour, weeklyPercent: 10, updatedAt }; +} + +/** + * Seed logged-in accounts and return the STORE-ASSIGNED ids. + * + * The store derives its slot id by hashing the credential identity + * (`store.ts:newAccountId`), so the `accountId` handed to `saveCredential` is not the + * key the cache is filed under. A test that assumed otherwise would pass against a + * cache that never matched a real account. + */ +async function seedAccounts(...accounts: Array<{ accountId: string; email: string }>): Promise { + for (const account of accounts) { + await saveCredential("meta-muse", { + access: `key-${account.accountId}`, + refresh: `key-${account.accountId}`, + expires: Number.MAX_SAFE_INTEGER, + ...account, + }); + } + return (getAccountSet("meta-muse")?.accounts ?? []).map(account => account.id); +} + +beforeEach(() => { + opencodexHome = mkdtempSync(join(tmpdir(), "ocx-muse-quota-")); + process.env.OPENCODEX_HOME = opencodexHome; + clearAccountQuotaCache(); + // The generation-fence test below reconciles with an empty live-key set, which raises + // lastReconciledGeneration process-wide. Without this reset every later write in the + // file would be discarded as stale -- a failure of the harness, not of the code. + resetProviderQuotaReconcileStateForTests(); +}); + +afterEach(() => { + clearAccountQuotaCache(); + if (previousOpencodexHome === undefined) delete process.env.OPENCODEX_HOME; + else process.env.OPENCODEX_HOME = previousOpencodexHome; + removeTreeWithRetry(opencodexHome); +}); + +describe("passive Muse quota cache", () => { + /* + * supportsPerAccountQuota gates fetchAccountQuota, whose fallback sends any + * non-Kiro/non-Antigravity bearer to Anthropic's usage endpoint. The passive path must + * never flip it -- this is the exfiltration guard the meta-muse OAuth unit installed. + */ + test("is passive without entering the probe allowlist", () => { + expect(hasPassiveAccountQuota("meta-muse")).toBe(true); + expect(supportsPerAccountQuota("meta-muse")).toBe(false); + }); + + test("ignores providers that are not passive", () => { + expect(hasPassiveAccountQuota("anthropic")).toBe(false); + recordPassiveAccountQuota("anthropic", FIRST.accountId, quotaAt(5), captureConfigGeneration()); + expect(getCachedProviderAccountQuota("anthropic", FIRST.accountId)).toBeNull(); + }); + + test("writes under the account that served the turn", () => { + recordPassiveAccountQuota("meta-muse", SECOND.accountId, quotaAt(42), captureConfigGeneration()); + expect(getCachedProviderAccountQuota("meta-muse", SECOND.accountId)?.fiveHourPercent).toBe(42); + expect(getCachedProviderAccountQuota("meta-muse", FIRST.accountId)).toBeNull(); + }); + + test("ignores a write with no account id", () => { + recordPassiveAccountQuota("meta-muse", "", quotaAt(42), captureConfigGeneration()); + expect(readPassiveProviderAccountQuotas("meta-muse")).toEqual([]); + }); + + test("discards a write whose config generation has been superseded", () => { + // Generation 0 predates any reconciliation; a stale writer must not commit. + const { reconcileStateGeneration } = require("../src/lib/state-store-sweeper"); + reconcileStateGeneration({ generation: 0, oauthAccountKeys: new Set(), providerNames: new Set() }); + recordPassiveAccountQuota("meta-muse", FIRST.accountId, quotaAt(42), -1); + expect(getCachedProviderAccountQuota("meta-muse", FIRST.accountId)).toBeNull(); + }); + + test("omits accounts with no observation rather than reporting a failed probe", async () => { + const [firstId] = await seedAccounts(FIRST, SECOND); + clearAccountQuotaCache(); + recordPassiveAccountQuota("meta-muse", firstId!, quotaAt(7), captureConfigGeneration()); + const rows = readPassiveProviderAccountQuotas("meta-muse"); + expect(rows).toHaveLength(1); + expect(rows[0]!.accountId).toBe(firstId!); + expect(rows[0]!.quota?.fiveHourPercent).toBe(7); + expect(rows[0]).not.toHaveProperty("unavailable"); + }); + + test("returns nothing for a provider that is not passive", () => { + setCachedProviderAccountQuotaForTests("anthropic", FIRST.accountId, quotaAt(1)); + expect(readPassiveProviderAccountQuotas("anthropic")).toEqual([]); + }); + + /* + * persistAccountQuotaCache serializes the WHOLE in-memory map. A passive write that + * landed before anything hydrated would persist its single row and erase every other + * provider's saved row, with diskHydrated then blocking recovery. + */ + /* + * persistAccountQuotaCache serializes the WHOLE in-memory map. A passive write that + * landed before anything hydrated would persist its single row and erase every other + * provider's saved row, with diskHydrated then blocking recovery. + * + * The disk file is written directly here rather than through the debounced scheduler: + * clearAccountQuotaCache cancels any pending write, so a scheduled one cannot be + * relied on to have landed across the restart this test simulates. + */ + test("hydrates before persisting so an observation cannot erase other providers' rows", async () => { + const [firstId] = await seedAccounts(FIRST); + writeDiskRows({ [`anthropic\u0000other-acct`]: quotaAt(55) }); + + // Restart with an empty map, then observe BEFORE anything reads the cache. + clearAccountQuotaCache(); + recordPassiveAccountQuota("meta-muse", firstId!, quotaAt(3), captureConfigGeneration()); + // The foreign row is in memory, which is only possible if the write hydrated first. + expect(getCachedProviderAccountQuota("anthropic", "other-acct")?.fiveHourPercent).toBe(55); + + await Bun.sleep(PERSIST_SETTLE_MS); + const persisted = readPersistedAccountQuotas(); + expect(persisted.get(`anthropic\u0000other-acct`)?.fiveHourPercent).toBe(55); + expect(persisted.get(`meta-muse\u0000${firstId!}`)?.fiveHourPercent).toBe(3); + }); + + test("an observation survives a restart", async () => { + const [firstId] = await seedAccounts(FIRST); + clearAccountQuotaCache(); + recordPassiveAccountQuota("meta-muse", firstId!, quotaAt(19), captureConfigGeneration()); + await Bun.sleep(PERSIST_SETTLE_MS); + expect(readPersistedAccountQuotas().get(`meta-muse\u0000${firstId!}`)?.fiveHourPercent).toBe(19); + + // Simulate a restart: the in-memory map is gone, the file is not. + clearAccountQuotaCache(); + const rows = readPassiveProviderAccountQuotas("meta-muse"); + expect(rows).toHaveLength(1); + expect(rows[0]!.quota?.fiveHourPercent).toBe(19); + }); +}); + +describe("passive quota and routing headroom", () => { + const ids = [FIRST.accountId, SECOND.accountId]; + + test("a fresh observation for every account is usable evidence", () => { + setCachedProviderAccountQuotaForTests("meta-muse", FIRST.accountId, quotaAt(10)); + setCachedProviderAccountQuotaForTests("meta-muse", SECOND.accountId, quotaAt(90)); + expect(hasHeadroomEvidence("meta-muse", ids)).toBe(true); + expect(rankAccountsByHeadroom("meta-muse", ids)[0]).toBe(FIRST.accountId); + }); + + /* + * A probe fills the whole roster at once; an observation arrives one account at a time. + * RANK_UNKNOWN sorts AFTER RANK_HEALTHY, so accepting partial evidence would redirect + * the first attempt away from an unmeasured account and toward the one known to be + * spent -- the exact inversion ranking exists to prevent. + */ + test("a partial roster is not evidence and does not reorder", () => { + setCachedProviderAccountQuotaForTests("meta-muse", FIRST.accountId, quotaAt(100)); + expect(hasHeadroomEvidence("meta-muse", ids)).toBe(false); + expect(rankAccountsByHeadroom("meta-muse", ids)).toEqual(ids); + }); + + test("an observation older than the routing bound is not evidence", () => { + const stale = Date.now() - 2 * 60 * 60_000; + setCachedProviderAccountQuotaForTests("meta-muse", FIRST.accountId, quotaAt(10, stale)); + setCachedProviderAccountQuotaForTests("meta-muse", SECOND.accountId, quotaAt(90, stale)); + expect(hasHeadroomEvidence("meta-muse", ids)).toBe(false); + expect(rankAccountsByHeadroom("meta-muse", ids)).toEqual(ids); + }); + + test("the staleness bound applies only to passive providers", () => { + const stale = Date.now() - 2 * 60 * 60_000; + setCachedProviderAccountQuotaForTests("anthropic", FIRST.accountId, quotaAt(10, stale)); + expect(hasHeadroomEvidence("anthropic", [FIRST.accountId])).toBe(true); + }); +}); +import { readPersistedAccountQuotas } from "../src/providers/account-quota-disk"; + +/** PERSIST_DEBOUNCE_MS is 250ms in account-quota-disk.ts; wait past it. */ +const PERSIST_SETTLE_MS = 400; + +/** Write the on-disk snapshot directly, bypassing the debounce. */ +function writeDiskRows(rows: Record): void { + writeFileSync(join(opencodexHome, "provider-account-quota-cache.json"), `${JSON.stringify({ version: 1, rows })}\n`); +} diff --git a/tests/muse-passive-quota-observation.test.ts b/tests/muse-passive-quota-observation.test.ts new file mode 100644 index 0000000000..a8c9bb57f0 --- /dev/null +++ b/tests/muse-passive-quota-observation.test.ts @@ -0,0 +1,203 @@ +/** + * End-to-end cover for the two seams the parser and cache tests cannot reach: the SSE + * observation that WRITES the quota, and the management route that READS it back. + * + * A green parser plus a green cache still leaves the feature able to record nothing -- + * that is exactly the gap an audit flagged here -- so these tests drive the real + * inspector and the real route handler rather than the helpers underneath them. + */ +import { afterEach, beforeEach, describe, expect, test } from "bun:test"; +import { mkdtempSync, writeFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { createSseInspector } from "../src/server/relay"; +import { handleOauthAccountRoutes } from "../src/server/management/oauth-account-routes"; +import { + clearAccountQuotaCache, + getCachedProviderAccountQuota, + recordPassiveAccountQuota, + resetProviderQuotaReconcileStateForTests, +} from "../src/providers/quota"; +import { + isMuseSubscriptionUsagePayload, + MUSE_SUBSCRIPTION_USAGE_TYPE, + parseMuseSubscriptionUsage, +} from "../src/providers/muse-subscription-usage"; +import { captureConfigGeneration } from "../src/lib/state-store-sweeper"; +import type { OcxConfig } from "../src/types"; +import { removeTreeWithRetry } from "./helpers/remove-tree"; + +const previousHome = process.env.OPENCODEX_HOME; +let testDir = ""; + +const ACCOUNT_ID = "muse-acct-1"; + +function museConfig(): OcxConfig { + return { + port: 0, + hostname: "127.0.0.1", + defaultProvider: "meta-muse", + providers: { + "meta-muse": { adapter: "openai-responses", baseUrl: "https://api.meta.ai/v1", authMode: "oauth" }, + }, + } as OcxConfig; +} + +function writeMuseAccount(): void { + writeFileSync(join(testDir, "auth.json"), JSON.stringify({ + "meta-muse": { + activeAccountId: ACCOUNT_ID, + accounts: [ + { id: ACCOUNT_ID, credential: { access: "LLM|1|k", refresh: "LLM|1|k", expires: 9999999999999, email: "muse@example.com", accountId: "muse-1" } }, + ], + }, + }), { mode: 0o600 }); +} + +/** The frame Meta emits, wrapped as it arrives on the wire. */ +function usageFrame(fiveHour: number, weekly: number): string { + return `data: ${JSON.stringify({ + type: MUSE_SUBSCRIPTION_USAGE_TYPE, + subscription: { + tier: "27681393394859588", + window: { used_percent: fiveHour, resets_at: 1788431188, window_duration_mins: 300 }, + weekly: { used_percent: weekly, resets_at: 1788739200 }, + }, + })}\n\n`; +} + +beforeEach(() => { + testDir = mkdtempSync(join(tmpdir(), "ocx-muse-observe-")); + process.env.OPENCODEX_HOME = testDir; + writeMuseAccount(); + clearAccountQuotaCache(); + resetProviderQuotaReconcileStateForTests(); +}); + +afterEach(() => { + clearAccountQuotaCache(); + if (previousHome === undefined) delete process.env.OPENCODEX_HOME; + else process.env.OPENCODEX_HOME = previousHome; + if (testDir) removeTreeWithRetry(testDir); +}); + +/** + * Mirrors the handler core.ts installs on onParsedPayload. Kept in the test rather than + * exported from core.ts so the assertion is about the OBSERVED BEHAVIOUR of the seam -- + * a frame in, a cache row out -- not about a helper's signature. + */ +function observePayload(payload: unknown, servingAccountId: string | null, generation: number): void { + if (!isMuseSubscriptionUsagePayload(payload)) return; + const quota = parseMuseSubscriptionUsage(payload); + if (quota && servingAccountId) recordPassiveAccountQuota("meta-muse", servingAccountId, quota, generation); +} + +describe("Muse subscription usage observed on the SSE seam", () => { + test("a stream carrying the frame writes the serving account's quota", () => { + const generation = captureConfigGeneration(); + const inspector = createSseInspector({ + onParsedPayload: payload => observePayload(payload, ACCOUNT_ID, generation), + }); + const encoder = new TextEncoder(); + inspector.feed(encoder.encode(`data: ${JSON.stringify({ type: "response.created" })}\n\n`)); + inspector.feed(encoder.encode(usageFrame(12, 34))); + inspector.feed(encoder.encode(`data: ${JSON.stringify({ type: "response.completed", response: { status: "completed" } })}\n\n`)); + inspector.finish(); + inspector.dispose(); + + const quota = getCachedProviderAccountQuota("meta-muse", ACCOUNT_ID); + expect(quota?.fiveHourPercent).toBe(12); + expect(quota?.weeklyPercent).toBe(34); + }); + + test("a stream without the frame writes nothing", () => { + const generation = captureConfigGeneration(); + const inspector = createSseInspector({ + onParsedPayload: payload => observePayload(payload, ACCOUNT_ID, generation), + }); + const encoder = new TextEncoder(); + inspector.feed(encoder.encode(`data: ${JSON.stringify({ type: "response.output_text.delta", delta: "hi" })}\n\n`)); + inspector.feed(encoder.encode(`data: ${JSON.stringify({ type: "response.completed", response: { status: "completed" } })}\n\n`)); + inspector.finish(); + inspector.dispose(); + + expect(getCachedProviderAccountQuota("meta-muse", ACCOUNT_ID)).toBeNull(); + }); + + test("the frame is observed even when no serving account is known", () => { + const generation = captureConfigGeneration(); + const inspector = createSseInspector({ + onParsedPayload: payload => observePayload(payload, null, generation), + }); + inspector.feed(new TextEncoder().encode(usageFrame(50, 60))); + inspector.finish(); + inspector.dispose(); + // No attribution target: nothing is filed, and nothing throws. + expect(getCachedProviderAccountQuota("meta-muse", ACCOUNT_ID)).toBeNull(); + }); + + /* + * relay.ts wraps onParsedPayload in a try/catch so inspection can never throw into the + * relay pump. Asserted here so a later refactor cannot quietly remove the guarantee + * this feature relies on to be safe on a live request. + */ + test("a throwing observer does not break the stream", () => { + const inspector = createSseInspector({ + onParsedPayload: () => { throw new Error("observer exploded"); }, + }); + const encoder = new TextEncoder(); + expect(() => { + inspector.feed(encoder.encode(usageFrame(1, 2))); + inspector.feed(encoder.encode(`data: ${JSON.stringify({ type: "response.completed", response: { status: "completed" } })}\n\n`)); + inspector.finish(); + }).not.toThrow(); + inspector.dispose(); + }); +}); + +describe("GET /api/oauth/accounts?quota=1 for a passive provider", () => { + async function getAccounts(query: string): Promise<{ status: number; body: { accounts?: Array<{ id: string; quota?: { fiveHourPercent?: number } | null; quotaUnavailable?: boolean }> } }> { + const request = new Request(`http://localhost/api/oauth/accounts?${query}`); + const response = await handleOauthAccountRoutes({ + req: request, + url: new URL(request.url), + config: museConfig(), + deps: {}, + convergeCodexCatalog: async () => ({ status: "failed", reason: "disk" }), + syncClaudeAgentDefsBestEffort: async () => {}, + }); + return { status: response!.status, body: await response!.json() as never }; + } + + test("returns the observed quota without probing", async () => { + recordPassiveAccountQuota("meta-muse", ACCOUNT_ID, { fiveHourPercent: 21, updatedAt: Date.now() }, captureConfigGeneration()); + const { status, body } = await getAccounts("provider=meta-muse"a=1"); + expect(status).toBe(200); + expect(body.accounts?.[0]?.quota?.fiveHourPercent).toBe(21); + }); + + test("refresh=1 is a no-op rather than an error: there is nothing to refresh", async () => { + recordPassiveAccountQuota("meta-muse", ACCOUNT_ID, { fiveHourPercent: 21, updatedAt: Date.now() }, captureConfigGeneration()); + const { status, body } = await getAccounts("provider=meta-muse"a=1&refresh=1"); + expect(status).toBe(200); + expect(body.accounts?.[0]?.quota?.fiveHourPercent).toBe(21); + }); + + /* + * No observation is not a failed probe. `quotaUnavailable` would claim an attempt that + * never happened, and the dashboard renders it as a warning. + */ + test("an account with no observation carries no quota and no unavailable flag", async () => { + const { status, body } = await getAccounts("provider=meta-muse"a=1"); + expect(status).toBe(200); + expect(body.accounts?.[0]?.quota).toBeUndefined(); + expect(body.accounts?.[0]?.quotaUnavailable).toBeUndefined(); + }); + + test("the plain list is unchanged when quota is not requested", async () => { + recordPassiveAccountQuota("meta-muse", ACCOUNT_ID, { fiveHourPercent: 21, updatedAt: Date.now() }, captureConfigGeneration()); + const { status, body } = await getAccounts("provider=meta-muse"); + expect(status).toBe(200); + expect(body.accounts?.[0]?.quota).toBeUndefined(); + }); +}); diff --git a/tests/muse-subscription-usage.test.ts b/tests/muse-subscription-usage.test.ts new file mode 100644 index 0000000000..2b8b2ed86b --- /dev/null +++ b/tests/muse-subscription-usage.test.ts @@ -0,0 +1,129 @@ +import { describe, expect, test } from "bun:test"; +import { + isMuseSubscriptionUsagePayload, + MUSE_SUBSCRIPTION_USAGE_TYPE, + parseMuseSubscriptionUsage, +} from "../src/providers/muse-subscription-usage"; + +/** The payload measured against the live endpoint on 2026-09-03 (003 §E). */ +function measuredPayload(): unknown { + return { + type: MUSE_SUBSCRIPTION_USAGE_TYPE, + subscription: { + tier: "27681393394859588", + window: { used_percent: 12, resets_at: 1788431188, window_duration_mins: 300 }, + weekly: { used_percent: 34, resets_at: 1788739200 }, + }, + }; +} + +describe("Muse subscription usage parser", () => { + test("maps the measured payload to both windows", () => { + const quota = parseMuseSubscriptionUsage(measuredPayload()); + expect(quota).not.toBeNull(); + expect(quota!.fiveHourPercent).toBe(12); + expect(quota!.weeklyPercent).toBe(34); + // Meta sends unix SECONDS; epochMillis scales them. + expect(quota!.fiveHourResetAt).toBe(1788431188 * 1000); + expect(quota!.weeklyResetAt).toBe(1788739200 * 1000); + }); + + test("stamps updatedAt locally, never from the payload", () => { + const before = Date.now(); + const quota = parseMuseSubscriptionUsage(measuredPayload())!; + expect(quota.updatedAt).toBeGreaterThanOrEqual(before); + expect(quota.updatedAt).toBeLessThanOrEqual(Date.now()); + }); + + test("never surfaces the opaque tier id", () => { + const quota = parseMuseSubscriptionUsage(measuredPayload())!; + expect(JSON.stringify(quota)).not.toContain("27681393394859588"); + expect(Object.keys(quota)).not.toContain("tier"); + }); + + /* + * The window is identified by its DECLARED duration. Filing a ten-hour window in the + * five-hour slot would understate usage by the ratio of the windows, and would do so + * with full confidence. + */ + test("routes a non-300-minute window to customWindows, not the five-hour slot", () => { + const quota = parseMuseSubscriptionUsage({ + type: MUSE_SUBSCRIPTION_USAGE_TYPE, + subscription: { window: { used_percent: 40, resets_at: 1788431188, window_duration_mins: 600 } }, + }); + expect(quota).not.toBeNull(); + expect(quota!.fiveHourPercent).toBeUndefined(); + expect(quota!.customWindows).toEqual([ + { label: "600m", percent: 40, resetAt: 1788431188 * 1000 }, + ]); + }); + + test("a window with no declared duration is custom, not assumed five-hour", () => { + const quota = parseMuseSubscriptionUsage({ + type: MUSE_SUBSCRIPTION_USAGE_TYPE, + subscription: { window: { used_percent: 7 } }, + })!; + expect(quota.fiveHourPercent).toBeUndefined(); + expect(quota.customWindows).toEqual([{ label: "subscription", percent: 7 }]); + }); + + test("either window may be absent independently", () => { + const weeklyOnly = parseMuseSubscriptionUsage({ + type: MUSE_SUBSCRIPTION_USAGE_TYPE, + subscription: { weekly: { used_percent: 5, resets_at: 1788739200 } }, + })!; + expect(weeklyOnly.weeklyPercent).toBe(5); + expect(weeklyOnly.fiveHourPercent).toBeUndefined(); + + const windowOnly = parseMuseSubscriptionUsage({ + type: MUSE_SUBSCRIPTION_USAGE_TYPE, + subscription: { window: { used_percent: 9, window_duration_mins: 300 } }, + })!; + expect(windowOnly.fiveHourPercent).toBe(9); + expect(windowOnly.weeklyPercent).toBeUndefined(); + expect(windowOnly.fiveHourResetAt).toBeUndefined(); + }); + + test("clamps an out-of-range percent rather than rejecting the row", () => { + const quota = parseMuseSubscriptionUsage({ + type: MUSE_SUBSCRIPTION_USAGE_TYPE, + subscription: { window: { used_percent: 150, window_duration_mins: 300 } }, + })!; + expect(quota.fiveHourPercent).toBe(100); + }); + + test("accepts a numeric string percent", () => { + const quota = parseMuseSubscriptionUsage({ + type: MUSE_SUBSCRIPTION_USAGE_TYPE, + subscription: { window: { used_percent: "12", window_duration_mins: 300 } }, + })!; + expect(quota.fiveHourPercent).toBe(12); + }); + + /* + * This runs inside SSE inspection on a live request. The only acceptable failure is + * silence: a parse error must never cost the user their turn. + */ + test.each([ + ["null", null], + ["undefined", undefined], + ["a string", "nope"], + ["an array", [1, 2, 3]], + ["no subscription", { type: MUSE_SUBSCRIPTION_USAGE_TYPE }], + ["a non-object subscription", { type: MUSE_SUBSCRIPTION_USAGE_TYPE, subscription: "x" }], + ["no usable window", { type: MUSE_SUBSCRIPTION_USAGE_TYPE, subscription: { tier: "1" } }], + ["unparseable percents", { + type: MUSE_SUBSCRIPTION_USAGE_TYPE, + subscription: { window: { used_percent: "abc", window_duration_mins: 300 }, weekly: { used_percent: null } }, + }], + ])("returns null without throwing for %s", (_label, payload) => { + expect(() => parseMuseSubscriptionUsage(payload)).not.toThrow(); + expect(parseMuseSubscriptionUsage(payload)).toBeNull(); + }); + + test("recognizes only the subscription-usage frame type", () => { + expect(isMuseSubscriptionUsagePayload(measuredPayload())).toBe(true); + expect(isMuseSubscriptionUsagePayload({ type: "response.completed" })).toBe(false); + expect(isMuseSubscriptionUsagePayload(null)).toBe(false); + }); +}); From ef086abb7045cd21a70dc5b6fd8f02509316443f Mon Sep 17 00:00:00 2001 From: JUN Date: Thu, 3 Sep 2026 22:14:38 +0900 Subject: [PATCH 02/49] docs(meta): correct the Muse quota story across the note, docs, CLI and skill (#3360) * docs(devlog): roadmap the Meta Muse provider-parity unit * docs(devlog): fold the passive-quota staleness blocker into wp1 * docs(devlog): bound the headroom side effect in the inventory and closeout * feat(quota): parse and cache Meta subscription usage observed in-band * feat(meta): observe Muse subscription usage and serve it from the account API * fix(quota): hydrate before a passive write and require full-roster passive evidence * test(meta): cover the Muse observation seam and the passive quota read path * docs(meta): correct the Muse quota story across the note, docs, CLI and skill * test(cli): pin the probe-free Muse refresh message --------- Co-authored-by: jun --- .../031_wp3_disposition_record.md | 54 +++++++++++++++++++ .../src/content/docs/guides/providers.md | 10 ++-- .../ocx/references/01_management_surface.md | 2 +- skills/ocx/references/03_recipes.md | 35 ++++++++++++ src/cli/account-extended.ts | 8 ++- src/cli/capabilities.ts | 2 +- src/providers/registry.ts | 2 +- tests/cli-account.test.ts | 20 +++++++ tests/meta-muse-oauth.test.ts | 8 ++- 9 files changed, 132 insertions(+), 9 deletions(-) create mode 100644 devlog/_plan/260903_muse_provider_parity/031_wp3_disposition_record.md diff --git a/devlog/_plan/260903_muse_provider_parity/031_wp3_disposition_record.md b/devlog/_plan/260903_muse_provider_parity/031_wp3_disposition_record.md new file mode 100644 index 0000000000..3031b58237 --- /dev/null +++ b/devlog/_plan/260903_muse_provider_parity/031_wp3_disposition_record.md @@ -0,0 +1,54 @@ +# wp3 closeout: every parity surface, closed or recorded + +Terminal record for the unit. `001` §C listed the gaps; this dispositions each one against the +tree as it stands after wp1 and wp2. + +## Closed with a diff + +| # | Surface | What changed | Where | +|---|---|---|---| +| 1-3 | per-account quota read, write, and `?quota=1` enrichment | passive parser, cache, observation seam, cache-only API read | wp1 (#3358) | +| 4 | observation age | `QuotaBars` renders it for passive providers only | wp2 (#3359) | +| 6 | provider note | now states that the windows ARE read, that they can be stale, and where they are absent | `src/providers/registry.ts` | +| 7 | docs-site | same correction, English source | `docs-site/src/content/docs/guides/providers.md` | +| 8 | `ocx account refresh meta-muse` | said "no quota report available", which reads as a failed probe; now explains that nothing is probed and how to update the value. **Behaviour unchanged** — no command may spend an inference turn to refresh a quota (c4) | `src/cli/account-extended.ts` | +| 9 | `skills/ocx` | new recipe 9 covering the read, the staleness, both expected absences, and the connection-test answer | `skills/ocx/references/03_recipes.md` | +| — | `ocx account strategy` help text | claimed "`anthropic` is the only OAuth pool with this setting; other OAuth providers are refused without a round-trip". `001` showed that is wrong: generic providers reach the endpoint and their settings persist inertly | `src/cli/capabilities.ts` | + +## Recorded NOT-APPLICABLE, with the measured reason + +Each of these differs from a first-class provider because of a **measured property of Meta's API**, +not because the work was skipped. + +| Surface | Reason | Evidence | +|---|---|---| +| Connection test | `liveModels === false` short-circuits to `{ applicable: false, reason: "static_catalog" }` before any network call. The flag is deliberate: the authenticated roster carries `muse-image-1.0` and `muse-voice-transcribe-1.0`, which a Responses-agent provider cannot drive. `kiro` is the same class | `provider-routes.ts:1195`; `registry.ts:1538`; `003` §C | +| Provider-level overview card | **Option (b) from `030` §4 is taken.** The provider card means "this provider's capacity", and Meta's documented limits are per TEAM, not per key. A per-subscription window promoted to provider level would be a different quantity wearing the same label. The card's existing empty state is correct | `003` §E; `quota.ts:2298-2301` | +| 401 replay (`FORCE_REFRESH_PROVIDERS`) | the credential is a static API key — the OAuth `access_token` 401s while the sibling `api_key` returns 200 — so a replay would resend an identical credential | `src/oauth/index.ts:540`; `003` §B | +| Background refresh | `defaultRefreshPolicy: "disabled"`, the same posture as `anthropic`: the vendor restricts use outside its own client, so every exchange stays attributable to a user action | `src/oauth/index.ts:240` | +| Account import | `ACCOUNT_IMPORT_PROVIDER` is a cockpit-tools document format with no Meta analogue | `src/oauth/account-import/types.ts:3` | +| `clear-cooldown` | anthropic-only because the generic failover health map is process-local. A provider-WIDE gap, not a Muse gap; fixing it here would land untested for its other providers | `oauth-account-routes.ts:465`; `generic-account-failover.ts:78` | +| GUI generic pool card | no dashboard editor exists for ANY generic OAuth provider | `ProviderAuthPanel.tsx:353` | +| Translated-path quota | `openai-responses.ts` dispatches on `payload.type` through a switch with no case for the event, so a translated turn drops it. Now stated in the provider note and the docs rather than left silent | `004` Q3 | +| Quota-aware cooldown | `030` §7 and `001` §B originally said wp1 would arm this. **That was wrong** and is corrected here: `exhaustedCooldownMs` returns null unless the provider is `kiro`, so a Muse 429 still gets Retry-After or the 60s default. Only pre-dispatch RANKING arms | `account-quota-rank.ts:102` | + +The last row is the one worth reading twice. It was an over-claim in this unit's own roadmap, +caught at review, and it would have shipped in a PR description as a capability that does not +exist. + +## The routing change, and why it is bounded + +wp1's cache arms headroom-ranked pre-dispatch selection for a user with two or more Muse accounts. +That is desirable — it is part of what "first-class" means — but only inside the two guards wp1 +added, both of which closed review blockers: + +- **Staleness:** passive rows older than an hour return "no evidence", so a stale roster degrades + to today's unranked ring rather than to a confidently wrong preference. +- **Partial rosters:** a probe fills every account at once; an observation fills one at a time. + Since `RANK_UNKNOWN` sorts after `RANK_HEALTHY`, one observed account at 100% would otherwise + outrank N unmeasured ones — the exact inversion ranking exists to prevent. + +## Terminal outcome + +`DONE`. Every row in `001` §C is closed with a diff or recorded above with its measured reason, +and no user-facing text claims OpenCodex cannot read a value it now reads. diff --git a/docs-site/src/content/docs/guides/providers.md b/docs-site/src/content/docs/guides/providers.md index 73f7f20620..7409cae523 100644 --- a/docs-site/src/content/docs/guides/providers.md +++ b/docs-site/src/content/docs/guides/providers.md @@ -472,9 +472,13 @@ OpenCodex's auth store (`~/.opencodex/auth.json`, mode 0600) like every other OA credential. The dashboard shows a Terms-of-Service warning before the first login and before any reauthentication — the same treatment Anthropic and Google Antigravity get. -Meta reports subscription window usage inside streaming responses, but OpenCodex does not -yet read or display it, and there is no endpoint to query it on demand, so this provider -shows no quota. Rate limits apply per team, not per key. +Meta reports subscription window usage inside streaming responses, and OpenCodex reads it +from there. The account row shows the last observed 5-hour and weekly windows with how old +that reading is — Meta publishes no endpoint to query them on demand, so a value is only +refreshed by another streaming turn through this provider, and a turn that goes through +request translation rather than passthrough reports none. An account that has not yet +served a streaming turn simply shows no quota, which is not an error. Rate limits apply +per team, not per key. For a supported setup, use `meta-model` above with your own key. diff --git a/skills/ocx/references/01_management_surface.md b/skills/ocx/references/01_management_surface.md index 419a3a7af9..4162ca9de4 100644 --- a/skills/ocx/references/01_management_surface.md +++ b/skills/ocx/references/01_management_surface.md @@ -443,7 +443,7 @@ JSON mode: `envelope`. - A bare invocation reads and never writes. - The APPLIED value is echoed, not the requested one, so a server-side normalization stays visible. - Values are not re-validated in the CLI: the server owns the strategy names and the 1-100 sticky bound. -- `anthropic` is the only OAuth pool with this setting; other OAuth providers are refused without a round-trip. +- `anthropic` owns the full pool contract. Other OAuth providers reach the same endpoint with a generic subset (enabled/strategy/autoSwitchThreshold) whose settings persist but do not yet steer selection; `sticky` and `quotaWindow` are refused for them. ### `ocx account sticky` diff --git a/skills/ocx/references/03_recipes.md b/skills/ocx/references/03_recipes.md index a6294b277e..4f48d2bfd7 100644 --- a/skills/ocx/references/03_recipes.md +++ b/skills/ocx/references/03_recipes.md @@ -170,3 +170,38 @@ ocx storage trash restore --yes --json The preview runs in both paths because the mutating route requires the `digest` the preview returns and rejects a stale one with 409. So the two invocations agree about what is being authorized. + +## 9. Read Muse Code usage, and know why it can be old + +`meta-muse` reports usage differently from every other provider, and the difference changes what +you can conclude from it. + +```bash +ocx account list meta-muse --json --quota +``` + +Each row's `quota` carries the 5-hour and weekly windows plus `updatedAt`. **Read `updatedAt`, not +just the percentages.** Meta publishes no quota endpoint; the value arrives inside a streaming +response and is cached, so it is as old as the last streaming turn through this provider — possibly +hours or days. + +```bash +ocx account refresh meta-muse +``` + +This reports that there is nothing to refresh, and that is correct rather than a failure. A fresh +number would require spending a real inference turn, so no command issues one. To update the +reading, run an actual request through the provider and read the list again. + +Two absences are also expected and are not defects: + +- An account that has not yet served a streaming turn has **no** `quota` key at all. That is + distinct from `quotaUnavailable`, which means a probe was attempted and failed — nothing is + probed here. +- A turn that goes through request translation rather than passthrough reports no usage, so a + client on a translated wire will never move this number. + +`ocx provider test meta-muse` answers `applicable: false` with reason `static_catalog`. The +provider sets `liveModels: false` deliberately — its authenticated roster includes image and voice +models this Responses-agent provider cannot drive — so the absence of a live probe is a design +decision, not a broken connection. diff --git a/src/cli/account-extended.ts b/src/cli/account-extended.ts index 053b02edd4..82d6755e5b 100644 --- a/src/cli/account-extended.ts +++ b/src/cli/account-extended.ts @@ -1,4 +1,5 @@ import { loadConfig } from "../config"; +import { hasPassiveAccountQuota } from "../providers/quota"; import { closeSync, openSync, readSync } from "node:fs"; import { MAX_ACCOUNT_PRIORITY, @@ -330,7 +331,12 @@ export async function cmdRefresh(args: string[], deps: AccountDeps): Promise [id, ["text", "image"] as ["text", "image"]])), modelReasoningEfforts: Object.fromEntries(META_MUSE_MODELS.map(id => [id, META_MUSE_REASONING_EFFORTS])), modelReasoningEffortMap: Object.fromEntries(META_MUSE_MODELS.map(id => [id, META_MUSE_REASONING_EFFORT_MAP])), - note: "Reuses the API key the Muse Code CLI stores after `muse login` (macOS only; requires the CLI installed and signed in). Meta scopes that credential to the Muse Code CLI, so this is an UNSUPPORTED use: Meta does not authorize subscription coverage outside its own CLI, how these calls settle is not observable from the API, and you should treat every call as billable against your account. The imported key is copied into OpenCodex's auth store. Meta reports subscription window usage inside streaming responses, but OpenCodex does not yet read or display it, and there is no endpoint to query it on demand. Rate limits apply per team, not per key. For a supported path use the meta-model provider with your own key (export it as META_MODEL_API_KEY).", + note: "Reuses the API key the Muse Code CLI stores after `muse login` (macOS only; requires the CLI installed and signed in). Meta scopes that credential to the Muse Code CLI, so this is an UNSUPPORTED use: Meta does not authorize subscription coverage outside its own CLI, how these calls settle is not observable from the API, and you should treat every call as billable against your account. The imported key is copied into OpenCodex's auth store. OpenCodex reads Meta's subscription windows from streaming responses and shows the last observed value with its age; there is no endpoint to query them on demand, so refreshing one requires another streaming turn, and translated (non-passthrough) turns report none. Rate limits apply per team, not per key. For a supported path use the meta-model provider with your own key (export it as META_MODEL_API_KEY).", }, { id: "umans", diff --git a/tests/cli-account.test.ts b/tests/cli-account.test.ts index 1e6d67da87..56e59f37d7 100644 --- a/tests/cli-account.test.ts +++ b/tests/cli-account.test.ts @@ -105,6 +105,11 @@ function fixtureConfig(): OcxConfig { authMode: "key", apiKey: RAW_SENTINEL, }, + "meta-muse": { + adapter: "openai-responses", + baseUrl: "https://api.meta.ai/v1", + authMode: "oauth", + }, ollama: { adapter: "openai-chat", baseUrl: "http://127.0.0.1:11434/v1", @@ -765,6 +770,21 @@ describe("ocx account CLI (issue #180 matrix)", () => { )).toHaveLength(4); }); + /* + * A passively observed quota has nothing to probe, so the generic "no quota report + * available" line describes a failure that never happened. The refresh must stay + * probe-free -- obtaining a fresh Muse value would mean spending an inference turn -- + * so only the message changes. + */ + test("19b: refresh meta-muse explains that nothing is probed instead of reporting a failure", async () => { + const human = await run(["refresh", "meta-muse"]); + + expect(human.code).toBe(0); + expect(human.stdout).toContain("reports usage only during a streaming response"); + expect(human.stdout).toContain("nothing to refresh"); + expect(human.stdout).not.toContain("no quota report available"); + }); + test("20: auto-switch on, off, threshold and status use the exact contracts", async () => { const on = await run(["auto-switch", "openai", "on"]); const off = await run(["auto-switch", "openai", "off"]); diff --git a/tests/meta-muse-oauth.test.ts b/tests/meta-muse-oauth.test.ts index d735b3f952..7ef5a08490 100644 --- a/tests/meta-muse-oauth.test.ts +++ b/tests/meta-muse-oauth.test.ts @@ -75,8 +75,12 @@ describe("meta-muse registry entry", () => { expect(note).toContain("auth store"); // The env-var trap: Meta calls it MODEL_API_KEY, opencodex reads META_MODEL_API_KEY. expect(note).toContain("META_MODEL_API_KEY"); - // Must not promise quota display that only wp5 delivers. - expect(note).toContain("does not yet read or display it"); + // The quota sentence must stay true in both directions: it now claims a reading, + // so it must also say why that reading can be old and where it is absent. + expect(note).toContain("shows the last observed value with its age"); + expect(note).toContain("no endpoint to query them on demand"); + expect(note).toContain("translated (non-passthrough) turns report none"); + expect(note).not.toContain("does not yet read or display it"); }); test("never generates unattended traffic on a vendor-restricted credential", () => { From 1cc2ff22dfc42d2100e2330ff3d2ebe7dc2331db Mon Sep 17 00:00:00 2001 From: JUN Date: Thu, 3 Sep 2026 22:57:17 +0900 Subject: [PATCH 03/49] feat(gui): state how old a passively observed quota is (#3359) * docs(devlog): roadmap the Meta Muse provider-parity unit * docs(devlog): fold the passive-quota staleness blocker into wp1 * docs(devlog): bound the headroom side effect in the inventory and closeout * feat(gui): state how old a passively observed quota is * docs(devlog): record the wp2 render-grounding screenshot --------- Co-authored-by: jun --- .../assets/021_observed_age_render.png | Bin 0 -> 62517 bytes gui/src/components/QuotaBars.tsx | 37 +++++ .../provider-workspace/ProviderAuthPanel.tsx | 3 + gui/src/i18n/de.ts | 7 +- gui/src/i18n/en.ts | 7 +- gui/src/i18n/fr.ts | 7 +- gui/src/i18n/ja.ts | 7 +- gui/src/i18n/ko.ts | 7 +- gui/src/i18n/ru.ts | 7 +- gui/src/i18n/tr.ts | 7 +- gui/src/i18n/zh-TW.ts | 7 +- gui/src/i18n/zh.ts | 7 +- .../styles/provider-workspace-settings.css | 2 + gui/tests/quota-observed-age.test.tsx | 147 ++++++++++++++++++ 14 files changed, 243 insertions(+), 9 deletions(-) create mode 100644 devlog/_plan/260903_muse_provider_parity/assets/021_observed_age_render.png create mode 100644 gui/tests/quota-observed-age.test.tsx diff --git a/devlog/_plan/260903_muse_provider_parity/assets/021_observed_age_render.png b/devlog/_plan/260903_muse_provider_parity/assets/021_observed_age_render.png new file mode 100644 index 0000000000000000000000000000000000000000..ed75e6cfd774cff69ac3de99d48827bff0fc4a40 GIT binary patch literal 62517 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 incompleteCustomWindowLabels?: ReadonlySet; + /** + * When set, state how old these numbers are. + * + * Set ONLY for a passively observed quota, where the value arrives as a side effect of + * a real request and nothing refreshes it. A probed provider re-reads on its own TTL, + * so an age line there would be noise; here its absence would let a days-old reading + * look live. + */ + observedAt?: number; }) { const { locale } = useI18n(); const rows = buildQuotaRows(quota, plan, t); + // Rendered above the bars in both layouts. Null age (under a minute, or no observation) + // renders nothing rather than "just now", which would be one more thing to read. + const observedAge = observedAt === undefined ? null : formatObservedAge(observedAt, t); + const observedLine = observedAge === null ? null : ( +

+ {t("quota.observedAgo").replace("{age}", observedAge)} +

+ ); if (rows.length === 0) { if (!pending) return null; if (layout === "stacked") { @@ -234,6 +269,7 @@ export default function QuotaBars({ if (layout === "stacked") { return (
+ {observedLine} {rows.map(row => ( + {observedLine} {rows.map(row => ( )}
diff --git a/gui/src/i18n/de.ts b/gui/src/i18n/de.ts index f0eb85fcee..d73fafa575 100644 --- a/gui/src/i18n/de.ts +++ b/gui/src/i18n/de.ts @@ -1751,7 +1751,12 @@ export const de: Record = { "modal.accountCodexPool": "ChatGPT-Kontopool", "modal.accountLoggedIn": "Angemeldet", "modal.accountLoggedOut": "Nicht angemeldet", - "quota.fiveHourLimit": "5-Stunden-Limit", + "quota.fiveHourLimit": "5-Stunden-Limit", + "quota.ageMinutes": "{n} Min.", + "quota.ageHours": "{n} Std.", + "quota.ageDays": "{n} T.", + "quota.observedAgo": "Vor {age} erfasst", + "quota.observedHint": "Meta meldet die Nutzung nur während einer Streaming-Antwort. Dies ist der zuletzt erfasste Wert, keine Live-Messung.", "quota.weeklyLimit": "Wochenlimit", "quota.monthlyLimit": "30-Tage-Limit", "quota.cursorFirstParty": "Erstanbieter-Modelle", diff --git a/gui/src/i18n/en.ts b/gui/src/i18n/en.ts index 8c49a81248..e72109ab0c 100644 --- a/gui/src/i18n/en.ts +++ b/gui/src/i18n/en.ts @@ -1062,7 +1062,12 @@ export const en = { "modal.accountCodexPool": "ChatGPT account pool", "modal.accountLoggedIn": "Logged in", "modal.accountLoggedOut": "Not logged in", - "quota.fiveHourLimit": "5-hour limit", + "quota.fiveHourLimit": "5-hour limit", + "quota.ageMinutes": "{n}m", + "quota.ageHours": "{n}h", + "quota.ageDays": "{n}d", + "quota.observedAgo": "Observed {age} ago", + "quota.observedHint": "Meta reports usage only during a streaming response, so this is the last value seen, not a live reading.", "quota.weeklyLimit": "Weekly limit", "quota.monthlyLimit": "30-day limit", "quota.cursorFirstParty": "First-party models", diff --git a/gui/src/i18n/fr.ts b/gui/src/i18n/fr.ts index a05872c155..c58aa127a3 100644 --- a/gui/src/i18n/fr.ts +++ b/gui/src/i18n/fr.ts @@ -1035,7 +1035,12 @@ export const fr: Record = { "modal.accountCodexPool": "Groupe de comptes ChatGPT", "modal.accountLoggedIn": "Connecté", "modal.accountLoggedOut": "Non connecté", - "quota.fiveHourLimit": "Limite sur 5 heures", + "quota.fiveHourLimit": "Limite sur 5 heures", + "quota.ageMinutes": "{n} min", + "quota.ageHours": "{n} h", + "quota.ageDays": "{n} j", + "quota.observedAgo": "Relevé il y a {age}", + "quota.observedHint": "Meta ne communique l'utilisation que pendant une réponse en streaming : il s'agit de la dernière valeur observée, pas d'une mesure en direct.", "quota.weeklyLimit": "Limite hebdomadaire", "quota.monthlyLimit": "Limite sur 30 jours", "quota.cursorFirstParty": "Modèles propriétaires", diff --git a/gui/src/i18n/ja.ts b/gui/src/i18n/ja.ts index ce861c5c47..d539f6f67f 100644 --- a/gui/src/i18n/ja.ts +++ b/gui/src/i18n/ja.ts @@ -1005,7 +1005,12 @@ export const ja: Record = { "modal.accountCodexPool": "ChatGPT アカウントプール", "modal.accountLoggedIn": "ログイン済み", "modal.accountLoggedOut": "未ログイン", - "quota.fiveHourLimit": "5 時間上限", + "quota.fiveHourLimit": "5 時間上限", + "quota.ageMinutes": "{n}分", + "quota.ageHours": "{n}時間", + "quota.ageDays": "{n}日", + "quota.observedAgo": "{age}前に取得", + "quota.observedHint": "Meta はストリーミング応答中にのみ使用量を報告します。リアルタイムの値ではなく、最後に取得した値です。", "quota.weeklyLimit": "週間上限", "quota.monthlyLimit": "30 日上限", "quota.cursorFirstParty": "ファーストパーティモデル", diff --git a/gui/src/i18n/ko.ts b/gui/src/i18n/ko.ts index bf7fe01e24..9cb19ebffb 100644 --- a/gui/src/i18n/ko.ts +++ b/gui/src/i18n/ko.ts @@ -1778,7 +1778,12 @@ export const ko: Record = { "modal.accountCodexPool": "ChatGPT 계정 풀", "modal.accountLoggedIn": "로그인됨", "modal.accountLoggedOut": "로그인 안 됨", - "quota.fiveHourLimit": "5시간 한도", + "quota.fiveHourLimit": "5시간 한도", + "quota.ageMinutes": "{n}분", + "quota.ageHours": "{n}시간", + "quota.ageDays": "{n}일", + "quota.observedAgo": "{age} 전에 확인한 값", + "quota.observedHint": "Meta는 스트리밍 응답 중에만 사용량을 보고합니다. 실시간 수치가 아니라 마지막으로 확인된 값입니다.", "quota.weeklyLimit": "주간 한도", "quota.monthlyLimit": "30일 한도", "quota.cursorFirstParty": "자사 모델", diff --git a/gui/src/i18n/ru.ts b/gui/src/i18n/ru.ts index 19ebbf68b2..6cc08b0347 100644 --- a/gui/src/i18n/ru.ts +++ b/gui/src/i18n/ru.ts @@ -1046,7 +1046,12 @@ export const ru: Record = { "modal.accountCodexPool": "Пул аккаунтов ChatGPT", "modal.accountLoggedIn": "Вход выполнен", "modal.accountLoggedOut": "Вход не выполнен", - "quota.fiveHourLimit": "5-часовой лимит", + "quota.fiveHourLimit": "5-часовой лимит", + "quota.ageMinutes": "{n} мин", + "quota.ageHours": "{n} ч", + "quota.ageDays": "{n} дн", + "quota.observedAgo": "Получено {age} назад", + "quota.observedHint": "Meta сообщает об использовании только во время потокового ответа, поэтому это последнее полученное значение, а не текущее.", "quota.weeklyLimit": "Недельный лимит", "quota.monthlyLimit": "30-дневный лимит", "quota.cursorFirstParty": "Собственные модели", diff --git a/gui/src/i18n/tr.ts b/gui/src/i18n/tr.ts index 21c70d54fd..5ab4282737 100644 --- a/gui/src/i18n/tr.ts +++ b/gui/src/i18n/tr.ts @@ -1053,7 +1053,12 @@ export const tr: Record = { "modal.accountCodexPool": "ChatGPT hesap havuzu", "modal.accountLoggedIn": "Giriş yapıldı", "modal.accountLoggedOut": "Giriş yapılmadı", - "quota.fiveHourLimit": "5 saatlik limit", + "quota.fiveHourLimit": "5 saatlik limit", + "quota.ageMinutes": "{n} dk", + "quota.ageHours": "{n} sa", + "quota.ageDays": "{n} g", + "quota.observedAgo": "{age} önce alındı", + "quota.observedHint": "Meta kullanımı yalnızca akış yanıtı sırasında bildirir; bu canlı bir ölçüm değil, en son alınan değerdir.", "quota.weeklyLimit": "Haftalık limit", "quota.monthlyLimit": "30 günlük limit", "quota.cursorFirstParty": "Birinci taraf modeller", diff --git a/gui/src/i18n/zh-TW.ts b/gui/src/i18n/zh-TW.ts index 588b09cac0..ab023dede1 100644 --- a/gui/src/i18n/zh-TW.ts +++ b/gui/src/i18n/zh-TW.ts @@ -848,7 +848,12 @@ export const zhTW: Record = { "modal.accountCodexPool": "ChatGPT 帳號池", "modal.accountLoggedIn": "已登入", "modal.accountLoggedOut": "未登入", - "quota.fiveHourLimit": "5 小時限額", + "quota.fiveHourLimit": "5 小時限額", + "quota.ageMinutes": "{n} 分鐘", + "quota.ageHours": "{n} 小時", + "quota.ageDays": "{n} 天", + "quota.observedAgo": "{age}前取得", + "quota.observedHint": "Meta 僅在串流回應期間回報用量,因此這是最後一次取得的數值,而非即時讀數。", "quota.weeklyLimit": "每週限額", "quota.monthlyLimit": "30 天限額", "quota.cursorFirstParty": "官方模型", diff --git a/gui/src/i18n/zh.ts b/gui/src/i18n/zh.ts index 136c60b254..3c2c6a86a5 100644 --- a/gui/src/i18n/zh.ts +++ b/gui/src/i18n/zh.ts @@ -1771,7 +1771,12 @@ export const zh: Record = { "modal.accountCodexPool": "ChatGPT 账户池", "modal.accountLoggedIn": "已登录", "modal.accountLoggedOut": "未登录", - "quota.fiveHourLimit": "5 小时限额", + "quota.fiveHourLimit": "5 小时限额", + "quota.ageMinutes": "{n} 分钟", + "quota.ageHours": "{n} 小时", + "quota.ageDays": "{n} 天", + "quota.observedAgo": "{age}前获取", + "quota.observedHint": "Meta 仅在流式响应期间报告用量,因此这是最后一次获取的数值,而非实时读数。", "quota.weeklyLimit": "每周限额", "quota.monthlyLimit": "30 天限额", "quota.cursorFirstParty": "官方模型", diff --git a/gui/src/styles/provider-workspace-settings.css b/gui/src/styles/provider-workspace-settings.css index 9f233c477d..a80fac5a59 100644 --- a/gui/src/styles/provider-workspace-settings.css +++ b/gui/src/styles/provider-workspace-settings.css @@ -59,6 +59,8 @@ .pwi-auth-acct--active .pwi-auth-row--active { background: none; } .pwi-auth-acct-quota { padding: 0 8px 8px 26px; } .pwi-auth-acct-quota-stale { margin: 4px 0 0; font-size: 0.85em; } +/* Observation age for a passively reported quota (meta-muse). Sits above the bars. */ +.quota-observed { margin: 0 0 4px; font-size: 0.85em; } .pwi-auth-row-main { appearance: none; flex: 1; min-width: 0; display: flex; align-items: center; gap: 8px; padding: 2px; border: 0; background: transparent; color: inherit; text-align: left; diff --git a/gui/tests/quota-observed-age.test.tsx b/gui/tests/quota-observed-age.test.tsx new file mode 100644 index 0000000000..d9fad80b1e --- /dev/null +++ b/gui/tests/quota-observed-age.test.tsx @@ -0,0 +1,147 @@ +/** + * The observation-age affordance for a passively reported quota. + * + * Two things are asserted, and the second matters as much as the first: the age appears + * when it is passed, and it stays ABSENT for every other caller of this shared component. + * QuotaBars is used by the Codex pool, the provider overview and the combo workspace, + * where an age line would be noise on numbers that refresh on their own TTL. + */ +import { afterEach, beforeEach, describe, expect, test } from "bun:test"; +import { Window } from "happy-dom"; +import { act } from "react"; +import { createRoot, type Root } from "react-dom/client"; +import QuotaBars, { formatObservedAge } from "../src/components/QuotaBars"; +import { LanguageProvider } from "../src/i18n/provider"; +import type { AccountQuota } from "../src/codex-quota-utils"; + +const domGlobals = ["document", "window", "navigator", "fetch", "IS_REACT_ACT_ENVIRONMENT"] as const; +let previousDomGlobals: Record<(typeof domGlobals)[number], unknown>; +let testWindow: Window; +let mountedRoots: Root[]; + +function setupDom(): void { + previousDomGlobals = Object.fromEntries( + domGlobals.map((key) => [key, Reflect.get(globalThis, key)]), + ) as typeof previousDomGlobals; + testWindow = new Window({ url: "http://localhost/" }); + Object.defineProperty(testWindow.navigator, "language", { configurable: true, value: "en-US" }); + Object.defineProperties(globalThis, { + document: { configurable: true, value: testWindow.document }, + window: { configurable: true, value: testWindow }, + navigator: { configurable: true, value: testWindow.navigator }, + }); + (globalThis as typeof globalThis & { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true; + mountedRoots = []; +} + +async function teardownDom(): Promise { + for (const root of mountedRoots) { + await act(async () => { root.unmount(); }); + } + mountedRoots = []; + for (const key of domGlobals) { + Object.defineProperty(globalThis, key, { configurable: true, value: previousDomGlobals[key] }); + } + await testWindow.happyDOM?.close?.(); +} + +const MINUTE = 60_000; +const HOUR = 60 * MINUTE; +const DAY = 24 * HOUR; + +function quota(): AccountQuota { + return { fiveHourPercent: 12, weeklyPercent: 34, updatedAt: Date.now() }; +} + +/** + * Stub translator: returns the key, except for the age string, where the real English + * copy is used so the {age} substitution has something to replace. Asserting on the + * substituted output is the point -- a stub that returned the bare key would pass while + * the placeholder went unreplaced in production. + */ +const EN: Record = { + "quota.observedAgo": "Observed {age} ago", + "quota.ageMinutes": "{n}m", + "quota.ageHours": "{n}h", + "quota.ageDays": "{n}d", +}; +const t = ((key: string) => EN[key] ?? key) as never; + +async function mount(props: Partial[0]>): Promise { + const host = testWindow.document.createElement("div"); + testWindow.document.body.appendChild(host); + const root = createRoot(host as unknown as HTMLElement); + mountedRoots.push(root); + await act(async () => { + root.render( + + + , + ); + }); + return host as unknown as HTMLElement; +} + +beforeEach(setupDom); +afterEach(teardownDom); + +describe("formatObservedAge", () => { + const now = Date.now(); + + test("stays silent under a minute rather than claiming precision it lacks", () => { + expect(formatObservedAge(now, t, now)).toBeNull(); + expect(formatObservedAge(now - 59_000, t, now)).toBeNull(); + }); + + test.each([ + [MINUTE, "1m"], + [59 * MINUTE, "59m"], + [HOUR, "1h"], + [23 * HOUR, "23h"], + [DAY, "1d"], + [9 * DAY, "9d"], + ])("buckets %i ms as %s", (elapsed, expected) => { + expect(formatObservedAge(now - (elapsed as number), t, now)).toBe(expected); + }); + + /* Proxy and browser clocks can disagree; a negative age must not render as "-3m". */ + test("clock skew reads as no age, never a negative one", () => { + expect(formatObservedAge(now + 5 * MINUTE, t, now)).toBeNull(); + }); +}); + +describe("QuotaBars observation age", () => { + test("renders the age and its explanatory hint when observedAt is passed", async () => { + const host = await mount({ observedAt: Date.now() - 12 * MINUTE, layout: "stacked" }); + const observed = host.querySelector(".quota-observed"); + expect(observed).not.toBeNull(); + expect(observed!.textContent).toContain("12m"); + // The hint is what tells a user WHY this one provider lags; without it the age is + // just an unexplained number. + expect(observed!.getAttribute("title")).toBe("quota.observedHint"); + }); + + test("renders it in the compact layout too", async () => { + const host = await mount({ observedAt: Date.now() - 3 * HOUR, layout: "compact" }); + expect(host.querySelector(".quota-observed")?.textContent).toContain("3h"); + }); + + /* The regression that protects every other caller of this shared component. */ + test("renders no age line when observedAt is omitted", async () => { + const host = await mount({ layout: "stacked" }); + expect(host.querySelector(".quota-observed")).toBeNull(); + }); + + test("renders no age line for an observation younger than a minute", async () => { + const host = await mount({ observedAt: Date.now() - 5_000, layout: "stacked" }); + expect(host.querySelector(".quota-observed")).toBeNull(); + }); + + /* An account with no observation must render nothing at all, not a zero bar. */ + test("renders nothing when there is no quota and nothing is pending", async () => { + const host = await mount({ quota: null, observedAt: Date.now() - HOUR, layout: "stacked" }); + expect(host.querySelector(".quota-observed")).toBeNull(); + expect(host.querySelector(".quota-stacked")).toBeNull(); + expect(host.textContent).toBe(""); + }); +}); From b5777aa2d6427f72bbe5363ae6f0e4fdfcc9e05c Mon Sep 17 00:00:00 2001 From: JUN Date: Thu, 3 Sep 2026 23:53:13 +0900 Subject: [PATCH 04/49] feat(meta): surface the active account's observed subscription windows at provider level (#3363) * docs(devlog): plan the provider-level Muse quota row * feat(meta): surface the active account's observed subscription windows at provider level --------- Co-authored-by: jun --- .../031_wp3_disposition_record.md | 2 +- .../040_wp4_provider_level_quota.md | 116 ++++++++++++++++++ src/providers/quota.ts | 24 ++++ tests/cli-account.test.ts | 39 ++++-- tests/muse-passive-quota-observation.test.ts | 66 ++++++++++ 5 files changed, 239 insertions(+), 8 deletions(-) create mode 100644 devlog/_plan/260903_muse_provider_parity/040_wp4_provider_level_quota.md diff --git a/devlog/_plan/260903_muse_provider_parity/031_wp3_disposition_record.md b/devlog/_plan/260903_muse_provider_parity/031_wp3_disposition_record.md index 3031b58237..86f6c30990 100644 --- a/devlog/_plan/260903_muse_provider_parity/031_wp3_disposition_record.md +++ b/devlog/_plan/260903_muse_provider_parity/031_wp3_disposition_record.md @@ -23,7 +23,7 @@ not because the work was skipped. | Surface | Reason | Evidence | |---|---|---| | Connection test | `liveModels === false` short-circuits to `{ applicable: false, reason: "static_catalog" }` before any network call. The flag is deliberate: the authenticated roster carries `muse-image-1.0` and `muse-voice-transcribe-1.0`, which a Responses-agent provider cannot drive. `kiro` is the same class | `provider-routes.ts:1195`; `registry.ts:1538`; `003` §C | -| Provider-level overview card | **Option (b) from `030` §4 is taken.** The provider card means "this provider's capacity", and Meta's documented limits are per TEAM, not per key. A per-subscription window promoted to provider level would be a different quantity wearing the same label. The card's existing empty state is correct | `003` §E; `quota.ts:2298-2301` | +| Provider-level overview card | **Option (b) was taken here and OVERRULED by the owner on the live dashboard** — see `040_wp4_provider_level_quota.md`. The rebuttal was already in the tree: `fetchAnthropicQuota` and `fetchKiroQuota` answer the provider row with the ACTIVE account's usage, so provider level in this dashboard means "the account in use", and a per-subscription window is exactly the right quantity. Implemented as option (a): the row is the active account's last observation, cache-only | `040`; `quota.ts` `fetchPassiveProviderQuota` | | 401 replay (`FORCE_REFRESH_PROVIDERS`) | the credential is a static API key — the OAuth `access_token` 401s while the sibling `api_key` returns 200 — so a replay would resend an identical credential | `src/oauth/index.ts:540`; `003` §B | | Background refresh | `defaultRefreshPolicy: "disabled"`, the same posture as `anthropic`: the vendor restricts use outside its own client, so every exchange stays attributable to a user action | `src/oauth/index.ts:240` | | Account import | `ACCOUNT_IMPORT_PROVIDER` is a cockpit-tools document format with no Meta analogue | `src/oauth/account-import/types.ts:3` | diff --git a/devlog/_plan/260903_muse_provider_parity/040_wp4_provider_level_quota.md b/devlog/_plan/260903_muse_provider_parity/040_wp4_provider_level_quota.md new file mode 100644 index 0000000000..f356505f70 --- /dev/null +++ b/devlog/_plan/260903_muse_provider_parity/040_wp4_provider_level_quota.md @@ -0,0 +1,116 @@ +# wp4 — provider-level Muse quota row (owner-overruled 031) + +Own PR, base `dev`. Branch: `codex/meta-muse-provider-quota`. + +## What changed since 031 + +`031_wp3_disposition_record.md` recorded the provider-level card as NOT-APPLICABLE +(option b from `030` §4): "Meta's documented limits are per team, while the observed +window is per subscription, so promoting it to provider level would relabel a different +quantity." + +The owner then opened the live dashboard (v2.42.0, Usage tab → 요청 한도) and rejected +that: "지금 업데이트 된 최신 버전인데도 안돼 … 이거 해결해서 뜰때까지". The +rebuttal to (b) was already in the codebase: `fetchAnthropicQuota` and +`fetchKiroQuota` both answer the provider-level row with **the active account's** +usage (`quota.ts:1387` — "Provider-level Kiro row: the active account's usage, shown on +the Providers page"). Provider level in this dashboard has always meant "the account in +use", and per-subscription is exactly the right quantity for that. + +## The change (one branch) + +`src/providers/quota.ts`, in `maybeFetchProviderQuota` after the kiro branch: + +```ts +// Passive providers report no probe: the row is the ACTIVE account's last observed +// subscription windows, the same shape fetchAnthropicQuota/fetchKiroQuota return. +// Cache-only — a dashboard load or ocx account refresh must never spend an inference +// turn; refresh=1 is a no-op on this path. +if (provider.authMode === "oauth" && hasPassiveAccountQuota(name)) return fetchPassiveProviderQuota(name); +``` + +New helper beside `fetchKiroQuota`: + +```ts +async function fetchPassiveProviderQuota(provider: string): Promise { + const activeId = getAccountSet(provider)?.activeAccountId; + if (!activeId) return null; + hydrateAccountQuotaCache(); + const entry = accountQuotaCache.get(accountCacheKey(provider, activeId)); + if (!entry?.quota) return null; + return report(provider, `${provider}:subscription-observation`, entry.quota); +} +``` + +Rules: + +- **Active account only.** Inactive accounts' observations never promote. Matches the + anthropic/kiro provider-row semantics. +- **No observation → null.** The card's empty state is then correct pre-first-turn. +- **`report.updatedAt` = the observation time.** `report()` already copies + `quota.updatedAt`, and both consumer surfaces render relative time from it: + `ProviderUsage.tsx:151` (`pws.stats.quotaUpdated`) and + `ProviderOverviewDashboard.tsx:166` (`pws.dashboard.checkedAgo`). **No GUI change.** +- **No new imports.** Everything stays in `quota.ts`; the lab boundary is untouched. + +## Consumer audit (the one cross-cutting risk) + +`fetchProviderQuotaReports` also feeds `replaceCachedProviderQuotas` +(`quota.ts:2525`), which `combos/resolve.ts:130,157` reads for +exhausted-provider skipping. That cache is safe by construction: +`getCachedProviderQuota` has a 30-minute age bound +(`quota-routing-cache.ts:21-26`) — stale passive rows are ignored by routing, and a +fresh "100%" observation correctly parks the provider for up to 30 minutes. This is the +desired behaviour, not a hazard. + +## CLI consequence (kept, now reachable in both directions) + +`ocx account refresh meta-muse` hits `/api/provider-quotas?refresh=1`. Before this +branch: report null → the wp3 "nothing to refresh" message. After: with an observation +cached, it prints the cached windows (still zero network calls upstream). Both outputs +are correct for their state; the wp3 test (`cli-account` 19b, no seeded cache) stays +green, and a new test pins the seeded-observation case. + +## Tests + +In `tests/muse-passive-quota-observation.test.ts` (extend): + +- active account with a cached observation → provider report carries the windows, + source `meta-muse:subscription-observation`, `updatedAt` equal to the observation + time +- no observation → no report row for meta-muse +- **no network**: `fetchImpl` spy (or the absence of any fetch in the module path) + proves a dashboard refresh issues zero upstream calls for meta-muse — pin by + stubbing `globalThis.fetch` to throw if reached +- an inactive account holding the only observation → no provider row +- `refresh=1` returns the same cached row + +In `tests/cli-account.test.ts` (extend 19-series): + +- with a seeded observation, `ocx account refresh meta-muse` prints the cached + windows rather than "nothing to refresh" (and never contacts upstream) + +## Docs + +`031_wp3_disposition_record.md` provider-card row amended to record the override and +point here. The `providers.md` sentence shipped in wp3 ("there is no endpoint to query +them on demand") stays true and unchanged. + +## Verification + +```bash +bun test tests/muse-passive-quota-observation.test.ts tests/cli-account.test.ts \ + tests/provider-quota.test.ts tests/core-lab-boundary.test.ts +bun run test:changed +bun x tsc --noEmit +bun run privacy:scan +cd gui && bun run build +``` + +Repository-wide local suite forbidden. Exact-head CI is the gate. + +## Terminal outcome + +`DONE` when `/api/provider-quotas` carries `meta-muse` once the active account has +an observation, the Usage tab 요청 한도 and the Providers overview RATE LIMITS render +it with its observation time, and the PR is green at its exact head SHA and merged. diff --git a/src/providers/quota.ts b/src/providers/quota.ts index e0d895815d..c080e74aa8 100644 --- a/src/providers/quota.ts +++ b/src/providers/quota.ts @@ -1409,6 +1409,27 @@ async function fetchKiroQuota(provider: string): Promise { + const activeId = getAccountSet(provider)?.activeAccountId; + if (!activeId) return null; + // Idempotent; without it a proxy restart shows nothing until the next streaming turn + // even though the last observation is on disk. + hydrateAccountQuotaCache(); + const entry = accountQuotaCache.get(accountCacheKey(provider, activeId)); + if (!entry?.quota) return null; + return report(provider, `${provider}:subscription-observation`, entry.quota); +} + // --------------------------------------------------------------------------- // Per-account quota (multiauth) // --------------------------------------------------------------------------- @@ -2371,6 +2392,9 @@ async function maybeFetchProviderQuota( if (provider.authMode === "oauth" && name === "cursor") return fetchCursorQuota(name); if (provider.authMode === "oauth" && name === "google-antigravity") return fetchAntigravityQuota(name, provider); if (provider.authMode === "oauth" && name === "kiro") return fetchKiroQuota(name); + // Passive providers (meta-muse): Meta publishes no quota endpoint, so there is no + // probe to run — the row is the active account's last in-band observation. + if (provider.authMode === "oauth" && hasPassiveAccountQuota(name)) return fetchPassiveProviderQuota(name); // Kimi Code `/usages` accepts OAuth or coding-plan API keys, but only on the canonical // host and only for real key auth — forward/local modes carry no credential of ours. if (provider.authMode === "oauth" && name === "kimi") return fetchKimiQuota(name, provider); diff --git a/tests/cli-account.test.ts b/tests/cli-account.test.ts index 56e59f37d7..2d53d19305 100644 --- a/tests/cli-account.test.ts +++ b/tests/cli-account.test.ts @@ -53,6 +53,8 @@ let activeReadFailure: { status: number; error: string } | null = null; let oauthListFailure: { provider: string; status: number; error: string } | null = null; let keyListFailure: { provider: string; status: number; error: string } | null = null; let codexRefreshFailure: MockFailure | null = null; +/** When set, the provider-quotas stub includes this row as a passive Muse observation. */ +let museProviderQuotaReport: Record | null = null; let autoSwitchUpdateFailure: MockFailure | null = null; let deleteFailure: MockFailure | null = null; let postDeleteReadFailure: MockFailure | null = null; @@ -203,13 +205,16 @@ async function mockManagementApi(req: Request): Promise { if (req.method === "GET" && url.pathname === "/api/provider-quotas") { return json({ generatedAt: Date.now(), - reports: [{ - provider: "anthropic", - label: "Anthropic", - source: "anthropic:usage", - quota: { fiveHourPercent: 31, fiveHourResetAt: 1_800_000_000, updatedAt: 1_700_000_000 }, - updatedAt: 1_700_000_000, - }], + reports: [ + { + provider: "anthropic", + label: "Anthropic", + source: "anthropic:usage", + quota: { fiveHourPercent: 31, fiveHourResetAt: 1_800_000_000, updatedAt: 1_700_000_000 }, + updatedAt: 1_700_000_000, + }, + ...(museProviderQuotaReport ? [museProviderQuotaReport] : []), + ], }); } @@ -417,6 +422,7 @@ beforeEach(() => { oauthListFailure = null; keyListFailure = null; codexRefreshFailure = null; + museProviderQuotaReport = null; autoSwitchUpdateFailure = null; deleteFailure = null; postDeleteReadFailure = null; @@ -785,6 +791,25 @@ describe("ocx account CLI (issue #180 matrix)", () => { expect(human.stdout).not.toContain("no quota report available"); }); + /* + * Once the active account has an observation, the same refresh prints it -- still with + * zero upstream calls, because the row comes from the passive cache, not a probe. + */ + test("19c: refresh meta-muse prints the cached observation when one exists", async () => { + museProviderQuotaReport = { + provider: "meta-muse", + label: "Meta Muse Code (CLI credential)", + source: "meta-muse:subscription-observation", + quota: { fiveHourPercent: 21, fiveHourResetAt: 1_800_000_000, updatedAt: 1_700_000_000 }, + updatedAt: 1_700_000_000, + }; + const human = await run(["refresh", "meta-muse"]); + + expect(human.code).toBe(0); + expect(human.stdout).toContain("5h 21%"); + expect(human.stdout).not.toContain("nothing to refresh"); + }); + test("20: auto-switch on, off, threshold and status use the exact contracts", async () => { const on = await run(["auto-switch", "openai", "on"]); const off = await run(["auto-switch", "openai", "off"]); diff --git a/tests/muse-passive-quota-observation.test.ts b/tests/muse-passive-quota-observation.test.ts index a8c9bb57f0..ed48130d29 100644 --- a/tests/muse-passive-quota-observation.test.ts +++ b/tests/muse-passive-quota-observation.test.ts @@ -14,6 +14,8 @@ import { createSseInspector } from "../src/server/relay"; import { handleOauthAccountRoutes } from "../src/server/management/oauth-account-routes"; import { clearAccountQuotaCache, + clearProviderQuotaCache, + fetchProviderQuotaReports, getCachedProviderAccountQuota, recordPassiveAccountQuota, resetProviderQuotaReconcileStateForTests, @@ -71,11 +73,15 @@ beforeEach(() => { process.env.OPENCODEX_HOME = testDir; writeMuseAccount(); clearAccountQuotaCache(); + // The provider-level report cache (5-minute TTL, module-wide) survives across tests in + // this file and would hand one test the previous test's row. + clearProviderQuotaCache(); resetProviderQuotaReconcileStateForTests(); }); afterEach(() => { clearAccountQuotaCache(); + clearProviderQuotaCache(); if (previousHome === undefined) delete process.env.OPENCODEX_HOME; else process.env.OPENCODEX_HOME = previousHome; if (testDir) removeTreeWithRetry(testDir); @@ -201,3 +207,63 @@ describe("GET /api/oauth/accounts?quota=1 for a passive provider", () => { expect(body.accounts?.[0]?.quota).toBeUndefined(); }); }); + +describe("provider-level quota row for a passive provider", () => { + test("carries the active account's observation with its observation time", async () => { + const observedAt = Date.now() - 12 * 60_000; + recordPassiveAccountQuota("meta-muse", ACCOUNT_ID, { fiveHourPercent: 21, weeklyPercent: 8, updatedAt: observedAt }, captureConfigGeneration()); + + const response = await fetchProviderQuotaReports(museConfig(), true); + const report = response.reports.find(row => row.provider === "meta-muse"); + + expect(report?.quota?.fiveHourPercent).toBe(21); + expect(report?.quota?.weeklyPercent).toBe(8); + expect(report?.source).toBe("meta-muse:subscription-observation"); + // The observation time IS the report time; both GUI surfaces render it as the age. + expect(report?.updatedAt).toBe(observedAt); + }); + + /* + * The defining constraint of a passive provider: reading the provider row must never + * contact upstream, even when the GUI's refresh button passes refresh=1. Stubbing + * fetch to throw proves no code path reaches the network. + */ + test("reads the row without any network call, including under refresh=1", async () => { + recordPassiveAccountQuota("meta-muse", ACCOUNT_ID, { fiveHourPercent: 33, updatedAt: Date.now() }, captureConfigGeneration()); + const originalFetch = globalThis.fetch; + globalThis.fetch = (() => Promise.reject(new Error("upstream contact forbidden for a passive provider"))) as typeof fetch; + try { + const response = await fetchProviderQuotaReports(museConfig(), true); + const report = response.reports.find(row => row.provider === "meta-muse"); + expect(report?.quota?.fiveHourPercent).toBe(33); + } finally { + globalThis.fetch = originalFetch; + } + }); + + test("no observation means no row, not an empty one", async () => { + const response = await fetchProviderQuotaReports(museConfig(), true); + expect(response.reports.find(row => row.provider === "meta-muse")).toBeUndefined(); + }); + + /* + * The provider row answers for the account IN USE, like fetchAnthropicQuota and + * fetchKiroQuota do. A fresher observation on an inactive account must not leak into + * the row the user reads as "my current usage". + */ + test("never promotes an inactive account's observation", async () => { + writeFileSync(join(testDir, "auth.json"), JSON.stringify({ + "meta-muse": { + activeAccountId: ACCOUNT_ID, + accounts: [ + { id: ACCOUNT_ID, credential: { access: "LLM|1|k", refresh: "LLM|1|k", expires: 9999999999999, email: "muse@example.com", accountId: "muse-1" } }, + { id: "acct-muse-2", credential: { access: "LLM|2|k", refresh: "LLM|2|k", expires: 9999999999999, email: "two@example.com", accountId: "muse-2" } }, + ], + }, + }), { mode: 0o600 }); + recordPassiveAccountQuota("meta-muse", "acct-muse-2", { fiveHourPercent: 77, updatedAt: Date.now() }, captureConfigGeneration()); + + const response = await fetchProviderQuotaReports(museConfig(), true); + expect(response.reports.find(row => row.provider === "meta-muse")).toBeUndefined(); + }); +}); From 664d80c76f8b10fd6ddd173c3452a610db2a0f50 Mon Sep 17 00:00:00 2001 From: JUN Date: Fri, 4 Sep 2026 01:32:46 +0900 Subject: [PATCH 05/49] fix(gui): plain-dollar cost cells, effort-only cell, and overlap clip on the Logs table (#3367) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(gui): plain-dollar cost cells, effort-only cell, and overlap clip on the Logs table Cost cells rendered the locale currency shape under an untranslated ~$ header: 약 US/bin/zsh.1401 in ko, 0,1401 $US in fr, ca. 0,1401 $ in de. Every locale now renders the same $0.1401 (en-US narrowSymbol), with ≥ as the only prefix for priority lower bounds; the ~ lives in the header. The effort cell stacked the wire field (reasoning_effort=high) under the label. It repeated the label and, in the mono font, outgrew the 9% column and painted over the provider name. The cell now shows only the label; the wire field stays on the cell title and in the detail dialog. table-layout: fixed sizes columns but does not clip them, so both of the above bled into the neighbouring cell. Body cells now clip, the effort cell may break anywhere, and the detail button wraps instead of nowrap so the clip cannot truncate zh-TW's 檢視詳細資料. Tests: plain-dollar across all nine locales, effort-cell source oracle, CSS overflow oracle; existing cost/overview pins updated; zh-TW parity allowlist carries the placeholder templates. * test(gui): allowlist the placeholder-only cost templates in the French catalog guard The fr accidental-English guard compares values to en; {amount} and ≥{amount} are identical in every locale by design (fixed $ shape under the untranslated ~$ header), exactly like the zh-TW parity allowlist already carries. --------- Co-authored-by: jun --- .../000_research.md | 37 +++++++ .../010_wp1_plan.md | 101 ++++++++++++++++++ .../assets/020_logs_ko_after.png | Bin 0 -> 420497 bytes gui/src/i18n/de.ts | 4 +- gui/src/i18n/en.ts | 2 +- gui/src/i18n/fr.ts | 4 +- gui/src/i18n/ja.ts | 4 +- gui/src/i18n/ko.ts | 4 +- gui/src/i18n/ru.ts | 4 +- gui/src/i18n/tr.ts | 4 +- gui/src/i18n/zh-TW.ts | 4 +- gui/src/i18n/zh.ts | 4 +- gui/src/intl-formatters.ts | 25 +++-- gui/src/pages/Logs.tsx | 10 +- gui/src/pages/logs-cost-format.ts | 23 ++-- gui/src/styles.css | 12 ++- gui/tests/fr-localization.test.ts | 4 + gui/tests/locale-parity.test.ts | 4 + gui/tests/logs-auto-refresh.test.tsx | 5 +- gui/tests/logs-cost-lower-bound.test.ts | 8 +- gui/tests/logs-cost-plain-dollar.test.ts | 60 +++++++++++ gui/tests/logs-effort-cell.test.ts | 24 +++++ gui/tests/logs-priority-lower-bound.test.ts | 11 +- gui/tests/logs-table-overflow.test.ts | 38 +++++++ 24 files changed, 346 insertions(+), 50 deletions(-) create mode 100644 devlog/_plan/260904_logs_cost_effort_polish/000_research.md create mode 100644 devlog/_plan/260904_logs_cost_effort_polish/010_wp1_plan.md create mode 100644 devlog/_plan/260904_logs_cost_effort_polish/assets/020_logs_ko_after.png create mode 100644 gui/tests/logs-cost-plain-dollar.test.ts create mode 100644 gui/tests/logs-effort-cell.test.ts create mode 100644 gui/tests/logs-table-overflow.test.ts diff --git a/devlog/_plan/260904_logs_cost_effort_polish/000_research.md b/devlog/_plan/260904_logs_cost_effort_polish/000_research.md new file mode 100644 index 0000000000..dbc709e6b8 --- /dev/null +++ b/devlog/_plan/260904_logs_cost_effort_polish/000_research.md @@ -0,0 +1,37 @@ +# 000 — Logs page cost / effort / overlap: research + +Live symptoms observed on http://localhost:10100/#logs (v2.42.0, ko locale, 2026-09-04): + +1. Cost cells render as `약 US$0.1401`. `Intl.NumberFormat(ko, {currency: "USD"})` emits the + `US$` code prefix, and the ko template `logs.cost.approximate = "약 {amount}"` prepends prose. + The column header already reads `~$`, so both the prose and the currency code are noise. + Same class of noise in ja (`約`), zh (`约 US$`), zh-TW (`約 US$`), de (`ca.`), fr (`env. $US`), + ru (`около`), tr (`yaklaşık`). +2. The effort cell renders two lines: `high` and a caption `reasoning_effort=high` + (`reasoningWireLabel`). The caption is also in the cell `title`. In the table it is redundant + and, in the mono font, wider than the 9 % column. +3. Overlap. Measured with `table-layout: fixed` at 1100 px: + - cost cell width 88 px, content scrollWidth 102 px → paints over the model column + (screenshot shows `0.1401claude-fable-5-1`). + - effort cell width 99 px, caption span 139 px → paints over the provider column + (`reasoning_effort=high` sits under `Kimi`). + Neither `td` has `overflow` set, and both cost (`white-space: nowrap`) and the caption + cannot wrap, so fixed layout lets them bleed. + +Code pointers: + +- `gui/src/pages/logs-cost-format.ts` — `formatEstimatedUsdValue` builds the amount with + `Intl.NumberFormat(localeTag, {style: "currency", currency: "USD"})` and wraps it in + `logs.cost.approximate` / `logs.cost.lowerBound`. +- `gui/src/intl-formatters.ts` — `formatEstimatedUsdValue` used by Usage totals; same + `US$` issue in ko/zh/zh-TW. +- `gui/src/pages/Logs.tsx` ~L821 — the table cell renders `effortLabel(log)` plus the wire + caption span. Detail dialog (~L967) and attempt rows (~L1092) show the wire label inline in + parentheses; those stay. +- `gui/src/styles.css` L1985–2006 — column widths; L2010 `.log-col-cost { nowrap }`. +- Tests: `gui/tests/logs-priority-lower-bound.test.ts`, `gui/tests/logs-cost-lower-bound.test.ts` + pin the current `~$` / `ca. 1,6000 $` strings and must be updated. + +Decision: render the amount as a fixed `$` + en-US number in every locale. Rationale: the header +is the untranslated `~$`, the CLI usage report prints `~$12.3456`, and a per-locale +`0,1401 $` under a `~$` header is inconsistent. The lower-bound marker stays as `≥`. diff --git a/devlog/_plan/260904_logs_cost_effort_polish/010_wp1_plan.md b/devlog/_plan/260904_logs_cost_effort_polish/010_wp1_plan.md new file mode 100644 index 0000000000..fafa6459c4 --- /dev/null +++ b/devlog/_plan/260904_logs_cost_effort_polish/010_wp1_plan.md @@ -0,0 +1,101 @@ +# 010 — WP1: plain-dollar cost, effort-only cell, overlap guard + +One work-phase, one PABCD cycle. Diff-level plan. + +## gui/src/pages/logs-cost-format.ts + +```ts +const USD = new Intl.NumberFormat("en-US", { + style: "currency", currency: "USD", currencyDisplay: "narrowSymbol", + minimumFractionDigits: 4, maximumFractionDigits: 4, +}); +export function formatEstimatedUsdValue(value, t, _localeTag?, priorityLowerBound = false) { + if (!Number.isFinite(value) || value < 0) return t("logs.cost.unavailable"); + const amount = USD.format(value); // "$0.1401" in every locale + return t(priorityLowerBound ? "logs.cost.lowerBound" : "logs.cost.approximate", { amount }); +} +``` + +`localeTag` stays in the signature so callers do not change; it is no longer used for the +amount. A doc comment records why (header is `~$`, CLI prints `~$`). + +## gui/src/intl-formatters.ts + +`formatEstimatedUsdValue(value, locale)` → same fixed en-US narrowSymbol formatter, keeps the +`~` prefix because the Usage page has no `~$` header. Locale parameter retained, unused. + +## gui/src/i18n/*.ts (9 locales) + +- `logs.cost.approximate`: `"{amount}"` in every locale (drop `~`, `약`, `約`, `约`, `ca.`, + `env.`, `около`, `yaklaşık`). The `~` lives in the column header. +- `logs.cost.lowerBound`: `"≥{amount}"` in every locale. + +## gui/src/pages/Logs.tsx + +Table cell (≈L821–826): remove the caption span, keep `title={reasoningWire}`: + +```tsx +{effortLabel(log)} +``` + +Detail dialog and attempt rows unchanged (they still show `high (reasoning_effort=high)`). +If the effort cell was the only user of `.logs-stack-start`, delete that CSS rule too. + +## gui/src/styles.css + +```css +/* table-layout: fixed does not clip; without this a cell wider than its paints over + the neighbour (seen: "약 US$0.1401" under the model column, "reasoning_effort=high" under + the provider column). */ +.logs-table tbody td { overflow: hidden; } +.log-reasoning-cell { overflow-wrap: anywhere; } +``` + +`.log-col-cost` keeps `nowrap`: `$0.1401` is 7 ch and the column is 8 % ≥ 88 px. + +## Tests + +- Update `gui/tests/logs-priority-lower-bound.test.ts` and + `gui/tests/logs-cost-lower-bound.test.ts`: en `"$1.6000"` / `"≥$1.6000"`, de `"$1.6000"` + (fixed dollar, no locale prose), unavailable unchanged. +- New `gui/tests/logs-cost-plain-dollar.test.ts`: for every locale in `DICTS`, the rendered + string matches `/^\$\d+\.\d{4}$/` (approximate) and `/^≥\$\d+\.\d{4}$/` (lower bound); + no locale template contains `US`. +- New `gui/tests/logs-effort-cell.test.ts`: source oracle on `Logs.tsx` — the table-row effort + cell has no caption span; the detail dialog still interpolates `reasoningWire`. +- New `gui/tests/logs-table-overflow.test.ts`: CSS source oracle — last effective + `.logs-table tbody td` declaration has `overflow: hidden` and `.log-reasoning-cell` has + `overflow-wrap: anywhere`. + +## Verification (C) + +`bun run typecheck`, `bun run lint:gui`, `bun test gui/tests/logs-*.test.ts gui/tests/intl-formatters.test.ts`, +`cd gui && bun run build`. Render: serve the built `gui/dist` through an isolated proxy on a +scratch port with a scratch `OPENCODEX_HOME` (port 10100 untouched), open `#logs` in ko, +screenshot + DOM geometry (computed `overflow: hidden` on body cells, and every text/child client rect stays inside its own cell rect — `scrollWidth` is not an oracle under hidden overflow). Screenshot saved under +`assets/` for the PR body. + +## Delivery (D) + +Branch `codex/260904-logs-cost-effort-polish`, commit/push `--no-verify`, PR to `dev` with the +template + screenshot, `gh pr merge --squash --admin`, ancestry proof. + +## A-phase audit synthesis (gpt-5.6-sol reviewer, verdict FAIL → near-pass after fold) + +1. `logs-auto-refresh.test.tsx:472` pinned the caption in the overview cell → FOLDED: asserts + caption absent from textContent, present on `title`, still in attempt rows. +2. `locale-parity.test.ts` zh-TW placeholder guard rejects `{amount}` == English → FOLDED: + both keys allowlisted with a comment. +3. `overflow: hidden` on every td could clip the status button (zh-TW 檢視詳細資料 > 64 px) and + its focus ring → FOLDED: `.log-detail-btn` now `white-space: normal` and an inset + `focus-visible` outline; the clip stays on all body cells because the cost and effort + cells are not the only ones that can outgrow a fixed column (request id, provider). +4. `scrollWidth <= clientWidth` is not a valid overlap oracle under hidden overflow → FOLDED: + the browser check measures painted descendant bounds (`getClientRects` of every child + element) against the cell's own rect, and reads computed `overflow`. +5. Full local suite before PR-ready → REBUTTED: the user forbade the repository-wide local + suite for this task; hosted CI on the exact head is the broad gate, recorded at merge. + `.logs-stack-start` is still used by the timestamp cell (L763) → the CSS rule stays. + +Accepted residual: the Logs formatter change also reaches conversation totals and detail / +attempt cost values (same `$` shape everywhere), and Usage's total keeps its `~` prefix. diff --git a/devlog/_plan/260904_logs_cost_effort_polish/assets/020_logs_ko_after.png b/devlog/_plan/260904_logs_cost_effort_polish/assets/020_logs_ko_after.png new file mode 100644 index 0000000000000000000000000000000000000000..f6ffc1ce908e7c17ad83c2aa35f5df66d67374f6 GIT binary patch literal 420497 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

tLb_a|gTO7)1 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%0Df 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

|fxEw^csjCYyS4A@pz}W( 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 i`NhA&3o+r2A}aF!ckCR#fW% literal 0 HcmV?d00001 diff --git a/gui/src/i18n/de.ts b/gui/src/i18n/de.ts index d73fafa575..5dafe0eea6 100644 --- a/gui/src/i18n/de.ts +++ b/gui/src/i18n/de.ts @@ -663,8 +663,8 @@ export const de: Record = { "logs.conversation.totals": "{requests} Anfragen · {tokens} Tokens · {cost}", "logs.conversation.scope": "Summen gelten nur für den aktuell geladenen Logs-Ring.", "logs.conversation.excluded": "({unpriced} ohne Preis, {unmetered} ohne Messung vom ~$ ausgenommen)", - "logs.cost.approximate": "ca. {amount}", - "logs.cost.lowerBound": "mind. {amount}", + "logs.cost.approximate": "{amount}", + "logs.cost.lowerBound": "≥{amount}", "logs.cost.unavailable": "nicht verfügbar", "logs.detail.conversation": "Konversation", "logs.badge.claude": "Claude", diff --git a/gui/src/i18n/en.ts b/gui/src/i18n/en.ts index e72109ab0c..3bcae43897 100644 --- a/gui/src/i18n/en.ts +++ b/gui/src/i18n/en.ts @@ -696,7 +696,7 @@ export const en = { "logs.conversation.totals": "{requests} requests · {tokens} tokens · {cost}", "logs.conversation.scope": "Totals cover the currently loaded Logs ring only.", "logs.conversation.excluded": "({unpriced} unpriced, {unmetered} unmetered excluded from ~$)", - "logs.cost.approximate": "~{amount}", + "logs.cost.approximate": "{amount}", "logs.cost.lowerBound": "≥{amount}", "logs.cost.unavailable": "—", "logs.detail.conversation": "Conversation", diff --git a/gui/src/i18n/fr.ts b/gui/src/i18n/fr.ts index c58aa127a3..f7a5c187f9 100644 --- a/gui/src/i18n/fr.ts +++ b/gui/src/i18n/fr.ts @@ -677,8 +677,8 @@ export const fr: Record = { "logs.conversation.totals": "{requests} requêtes · {tokens} jetons · {cost}", "logs.conversation.scope": "Les totaux couvrent uniquement le tampon circulaire des journaux actuellement chargé.", "logs.conversation.excluded": "({unpriced} sans tarif, {unmetered} sans mesure exclus du total en ~$)", - "logs.cost.approximate": "env. {amount}", - "logs.cost.lowerBound": "au moins {amount}", + "logs.cost.approximate": "{amount}", + "logs.cost.lowerBound": "≥{amount}", "logs.cost.unavailable": "indisponible", "logs.detail.conversation": "Conversation", "logs.badge.claude": "Claude", diff --git a/gui/src/i18n/ja.ts b/gui/src/i18n/ja.ts index d539f6f67f..f3275805a7 100644 --- a/gui/src/i18n/ja.ts +++ b/gui/src/i18n/ja.ts @@ -639,8 +639,8 @@ export const ja: Record = { "logs.conversation.totals": "{requests} 件 · {tokens} トークン · {cost}", "logs.conversation.scope": "合計は現在読み込まれている Logs リングのみです。", "logs.conversation.excluded": "(~$ から価格なし {unpriced} / 未計測 {unmetered} を除外)", - "logs.cost.approximate": "約{amount}", - "logs.cost.lowerBound": "最低{amount}", + "logs.cost.approximate": "{amount}", + "logs.cost.lowerBound": "≥{amount}", "logs.cost.unavailable": "利用不可", "logs.detail.conversation": "会話", "logs.badge.claude": "Claude", diff --git a/gui/src/i18n/ko.ts b/gui/src/i18n/ko.ts index 9cb19ebffb..df4005b409 100644 --- a/gui/src/i18n/ko.ts +++ b/gui/src/i18n/ko.ts @@ -682,8 +682,8 @@ export const ko: Record = { "logs.conversation.totals": "{requests}건 요청 · {tokens} 토큰 · {cost}", "logs.conversation.scope": "합계는 현재 로드된 Logs 링만 포함합니다.", "logs.conversation.excluded": "(~$에서 가격 없음 {unpriced}건, 미측정 {unmetered}건 제외)", - "logs.cost.approximate": "약 {amount}", - "logs.cost.lowerBound": "최소 {amount}", + "logs.cost.approximate": "{amount}", + "logs.cost.lowerBound": "≥{amount}", "logs.cost.unavailable": "사용 불가", "logs.detail.conversation": "대화", "logs.badge.claude": "Claude", diff --git a/gui/src/i18n/ru.ts b/gui/src/i18n/ru.ts index 6cc08b0347..50eac9853f 100644 --- a/gui/src/i18n/ru.ts +++ b/gui/src/i18n/ru.ts @@ -680,8 +680,8 @@ export const ru: Record = { "logs.conversation.totals": "{requests} запросов · {tokens} токенов · {cost}", "logs.conversation.scope": "Итоги только по загруженному кольцу Logs.", "logs.conversation.excluded": "(из ~$ исключены {unpriced} без цены, {unmetered} без учёта)", - "logs.cost.approximate": "около {amount}", - "logs.cost.lowerBound": "не менее {amount}", + "logs.cost.approximate": "{amount}", + "logs.cost.lowerBound": "≥{amount}", "logs.cost.unavailable": "недоступно", "logs.detail.conversation": "Диалог", "logs.badge.claude": "Claude", diff --git a/gui/src/i18n/tr.ts b/gui/src/i18n/tr.ts index 5ab4282737..d7a4809a75 100644 --- a/gui/src/i18n/tr.ts +++ b/gui/src/i18n/tr.ts @@ -687,8 +687,8 @@ export const tr: Record = { "logs.conversation.totals": "{requests} istek · {tokens} jeton · {cost}", "logs.conversation.scope": "Toplamlar yalnızca yüklü günlükleri kapsar.", "logs.conversation.excluded": "({unpriced} fiyatlandırılmamış, {unmetered} ölçülmemiş hariç)", - "logs.cost.approximate": "yaklaşık {amount}", - "logs.cost.lowerBound": "en az {amount}", + "logs.cost.approximate": "{amount}", + "logs.cost.lowerBound": "≥{amount}", "logs.cost.unavailable": "kullanılamıyor", "logs.detail.conversation": "Sohbet", "logs.badge.claude": "Claude", diff --git a/gui/src/i18n/zh-TW.ts b/gui/src/i18n/zh-TW.ts index ab023dede1..2ee58dad02 100644 --- a/gui/src/i18n/zh-TW.ts +++ b/gui/src/i18n/zh-TW.ts @@ -534,8 +534,8 @@ export const zhTW: Record = { "logs.conversation.totals": "{requests} 次請求 · {tokens} tokens · {cost}", "logs.conversation.scope": "合計僅涵蓋目前已載入的 Logs 環形緩衝。", "logs.conversation.excluded": "(~$ 已排除 {unpriced} 筆無定價、{unmetered} 筆無計量)", - "logs.cost.approximate": "約 {amount}", - "logs.cost.lowerBound": "至少 {amount}", + "logs.cost.approximate": "{amount}", + "logs.cost.lowerBound": "≥{amount}", "logs.cost.unavailable": "無法估算", "logs.detail.conversation": "對話", "logs.badge.claude": "Claude", diff --git a/gui/src/i18n/zh.ts b/gui/src/i18n/zh.ts index 3c2c6a86a5..326d3b8e29 100644 --- a/gui/src/i18n/zh.ts +++ b/gui/src/i18n/zh.ts @@ -675,8 +675,8 @@ export const zh: Record = { "logs.conversation.totals": "{requests} 次请求 · {tokens} tokens · {cost}", "logs.conversation.scope": "合计仅覆盖当前已加载的 Logs 环形缓冲。", "logs.conversation.excluded": "(~$ 已排除 {unpriced} 条无定价、{unmetered} 条无计量)", - "logs.cost.approximate": "约 {amount}", - "logs.cost.lowerBound": "至少 {amount}", + "logs.cost.approximate": "{amount}", + "logs.cost.lowerBound": "≥{amount}", "logs.cost.unavailable": "无法估算", "logs.detail.conversation": "会话", "logs.badge.claude": "Claude", diff --git a/gui/src/intl-formatters.ts b/gui/src/intl-formatters.ts index e096931b79..2dd9ce76df 100644 --- a/gui/src/intl-formatters.ts +++ b/gui/src/intl-formatters.ts @@ -57,14 +57,21 @@ export function formatCreditDateTime(iso: string, locale?: string): string { return cachedDateFormatter(locale, CREDIT_DATE_TIME_OPTIONS).format(date); } -/** Format a USD cost estimate for display. Returns "—" when unavailable. */ -export function formatEstimatedUsdValue(value: number, locale?: string): string { +const USD_ESTIMATE_FORMAT = new Intl.NumberFormat("en-US", { + style: "currency", + currency: "USD", + currencyDisplay: "narrowSymbol", + minimumFractionDigits: 4, + maximumFractionDigits: 4, +}); + +/** + * Format a USD cost estimate for display. Returns "—" when unavailable. + * The amount is a fixed `$1.2345` regardless of locale: the Logs column header and the CLI + * usage report both print `~$`, and `Intl` under ko/zh renders `US$`, which read as a + * different unit. The locale parameter is kept so callers stay source-compatible. + */ +export function formatEstimatedUsdValue(value: number, _locale?: string): string { if (!Number.isFinite(value) || value < 0) return "\u2014"; - const formatted = cachedNumberFormat(locale, { - style: "currency", - currency: "USD", - minimumFractionDigits: 4, - maximumFractionDigits: 4, - }).format(value); - return `~${formatted}`; + return `~${USD_ESTIMATE_FORMAT.format(value)}`; } diff --git a/gui/src/pages/Logs.tsx b/gui/src/pages/Logs.tsx index 384231f698..fb3525cf64 100644 --- a/gui/src/pages/Logs.tsx +++ b/gui/src/pages/Logs.tsx @@ -818,12 +818,10 @@ export default function Logs({ apiBase }: { apiBase: string }) { {speedLabel(log) && {speedLabel(log)}} - - - {effortLabel(log)} - {reasoningWire && {reasoningWire}} - - + {/* The wire field (reasoning_effort=high) stays in the title and the detail + dialog; as a second line it repeated the label and, in mono, outgrew the + 9% column and painted over the provider cell. */} + {effortLabel(log)} {formatProviderDisplayName(log.provider, t)} diff --git a/gui/src/pages/logs-cost-format.ts b/gui/src/pages/logs-cost-format.ts index 48a0814097..73de0ee2ba 100644 --- a/gui/src/pages/logs-cost-format.ts +++ b/gui/src/pages/logs-cost-format.ts @@ -5,19 +5,28 @@ type EstimatedCostResult = { estimate: { cost: { total: number }; priorityLowerBound?: boolean }; } | { kind: "unavailable" }; +/** + * Cost cells render a fixed `$0.1401` in every locale. The column header is the untranslated + * `~$`, and the CLI usage report prints `~$12.3456`, so a locale-shaped amount under that + * header (`약 US$0.1401`, `0,1401 $US`) read as a different unit rather than a translation. + * `narrowSymbol` is what drops the `US` qualifier; en-US fixes the separators. + */ +const USD_FORMAT = new Intl.NumberFormat("en-US", { + style: "currency", + currency: "USD", + currencyDisplay: "narrowSymbol", + minimumFractionDigits: 4, + maximumFractionDigits: 4, +}); + export function formatEstimatedUsdValue( value: number, t: TFn, - localeTag?: string, + _localeTag?: string, priorityLowerBound = false, ): string { if (!Number.isFinite(value) || value < 0) return t("logs.cost.unavailable"); - const amount = new Intl.NumberFormat(localeTag, { - style: "currency", - currency: "USD", - minimumFractionDigits: 4, - maximumFractionDigits: 4, - }).format(value); + const amount = USD_FORMAT.format(value); return t(priorityLowerBound ? "logs.cost.lowerBound" : "logs.cost.approximate", { amount }); } diff --git a/gui/src/styles.css b/gui/src/styles.css index 1d321fc843..75d6aaa501 100644 --- a/gui/src/styles.css +++ b/gui/src/styles.css @@ -2006,12 +2006,22 @@ table.logs-table { .logs-table col.logs-col-duration { width: 8%; } .log-col-rate { min-width: 7ch; white-space: nowrap; } .log-col-cost { min-width: 10ch; white-space: nowrap; } +/* table-layout: fixed sizes the columns but does not clip them: a cell whose content is wider + than its paints straight over the neighbour (seen as "US$0.1401claude-fable-5-1" and a + "reasoning_effort=high" caption sitting under the provider name). Clip at the cell so an + overlong value truncates inside its own column instead. */ +.logs-table tbody td { overflow: hidden; } +.log-reasoning-cell { overflow-wrap: anywhere; } .log-status-cell { display: inline-flex; flex-direction: column; align-items: flex-start; gap: var(--space-0-5); min-width: 7ch; line-height: var(--leading-tight); } .log-detail-btn { background: none; border: none; padding: 0; cursor: pointer; color: var(--accent-hover); font: inherit; font-size: var(--text-caption); text-decoration: underline; - white-space: nowrap; + /* Wraps rather than clips: with the cell now clipping overflow, a nowrap label such as + zh-TW's 檢視詳細資料 would lose its tail in the 8% status column. */ + white-space: normal; + text-align: left; } +.log-detail-btn:focus-visible { outline-offset: -2px; } /* Logs page layout rhythm — keep spacing in CSS so virtualized rows don't flood inline px. */ .logs-auto-refresh { diff --git a/gui/tests/fr-localization.test.ts b/gui/tests/fr-localization.test.ts index 037b03671a..221de55d00 100644 --- a/gui/tests/fr-localization.test.ts +++ b/gui/tests/fr-localization.test.ts @@ -170,6 +170,10 @@ const INTENTIONAL_ENGLISH = new Set([ // "Clients" is the same word in French, and it is the plural noun the // Integrations page uses to head its client catalog. "integrations.catalog.title", + // Cost cells are a fixed `$0.1401` / `≥$0.1401` in every locale (the column header is the + // untranslated `~$`); the templates are pure placeholders on purpose. + "logs.cost.approximate", + "logs.cost.lowerBound", ]); function placeholders(value: string): string[] { diff --git a/gui/tests/locale-parity.test.ts b/gui/tests/locale-parity.test.ts index 8a718e2651..11976154c9 100644 --- a/gui/tests/locale-parity.test.ts +++ b/gui/tests/locale-parity.test.ts @@ -163,6 +163,10 @@ const ZH_TW_KEEP_ENGLISH: ReadonlySet = new Set([ "integrations.cursor.title", "integrations.cursor.privateInference", "integrations.cursor.baseUrl", + // Cost cells are a fixed `$0.1401` / `≥$0.1401` in every locale (the column header is the + // untranslated `~$`); the templates are pure placeholders on purpose. + "logs.cost.approximate", + "logs.cost.lowerBound", ]); test("zh-TW ships no untranslated English placeholders beyond the intentional allowlist", async () => { diff --git a/gui/tests/logs-auto-refresh.test.tsx b/gui/tests/logs-auto-refresh.test.tsx index 5be3656b0d..b0dbda2501 100644 --- a/gui/tests/logs-auto-refresh.test.tsx +++ b/gui/tests/logs-auto-refresh.test.tsx @@ -471,8 +471,11 @@ test("Logs: attempt details render exact reasoning wire values without legacy pl await flushMicrotasks(); const overviewReasoning = container.querySelector(".log-reasoning-cell"); expect(overviewReasoning?.textContent).toContain("max → high"); - expect(overviewReasoning?.textContent).toContain("reasoning_effort=high"); expect(overviewReasoning?.textContent).not.toContain("max → high → high"); + // The wire field left the table cell (it repeated the label and overflowed the column); + // it stays on the cell title and in the attempt rows below. + expect(overviewReasoning?.textContent).not.toContain("reasoning_effort=high"); + expect(overviewReasoning?.getAttribute("title")).toBe("reasoning_effort=high"); await act(async () => { container.querySelector(".log-detail-btn")!.click(); }); diff --git a/gui/tests/logs-cost-lower-bound.test.ts b/gui/tests/logs-cost-lower-bound.test.ts index 60759219fa..88c52e774b 100644 --- a/gui/tests/logs-cost-lower-bound.test.ts +++ b/gui/tests/logs-cost-lower-bound.test.ts @@ -11,16 +11,16 @@ function translator(locale: Locale): TFn { return (key, vars) => interpolate(DICTS[locale][key], vars); } -test("ordinary dashboard costs retain the estimate marker", () => { - expect(formatEstimatedUsdValue(0.77, translator("en"), "en-US", false)).toBe("~$0.7700"); +test("ordinary dashboard costs render as a bare dollar amount; the ~ lives in the column header", () => { + expect(formatEstimatedUsdValue(0.77, translator("en"), "en-US", false)).toBe("$0.7700"); }); test("priority long-context lower bounds render with a greater-than-or-equal marker", () => { expect(formatEstimatedUsdValue(0.77, translator("en"), "en-US", true)).toBe("≥$0.7700"); }); -test("USD placement and separators follow a non-English locale", () => { - expect(formatEstimatedUsdValue(0.77, translator("de"), "de-DE", false)).toBe("ca. 0,7700\u00a0$"); +test("the dollar shape is fixed under a non-English locale; only the unavailable label is translated", () => { + expect(formatEstimatedUsdValue(0.77, translator("de"), "de-DE", false)).toBe("$0.7700"); expect(formatEstimatedUsd({ kind: "unavailable" }, translator("de"), "de-DE")).toBe("nicht verfügbar"); }); diff --git a/gui/tests/logs-cost-plain-dollar.test.ts b/gui/tests/logs-cost-plain-dollar.test.ts new file mode 100644 index 0000000000..51d06e0610 --- /dev/null +++ b/gui/tests/logs-cost-plain-dollar.test.ts @@ -0,0 +1,60 @@ +import { describe, expect, test } from "bun:test"; +import { DICTS } from "../src/i18n/catalogs"; +import { interpolate, type Locale, type TFn } from "../src/i18n/shared"; +import { formatEstimatedUsdValue as formatLogsUsd } from "../src/pages/logs-cost-format"; +import { formatEstimatedUsdValue as formatUsageUsd } from "../src/intl-formatters"; + +const LOCALE_TAGS: Record = { + en: "en-US", + ko: "ko-KR", + ja: "ja-JP", + zh: "zh-CN", + "zh-TW": "zh-TW", + de: "de-DE", + fr: "fr-FR", + ru: "ru-RU", + tr: "tr-TR", +}; + +function translator(locale: Locale): TFn { + return (key, vars) => interpolate(DICTS[locale][key], vars); +} + +/** + * The Logs column header is the untranslated `~$`. Under it, `약 US$0.1401` (ko), `0,1401 $US` + * (fr) and `ca. 0,1401 $` (de) each read as a different unit. Every locale now renders the same + * `$0.1401`, with `≥` as the only allowed prefix (priority lower bound). + */ +describe("Logs cost cells are a plain dollar amount in every locale", () => { + const locales = Object.keys(DICTS) as Locale[]; + + test("covers every shipped locale", () => { + expect(locales.sort()).toEqual(Object.keys(LOCALE_TAGS).sort()); + }); + + for (const locale of locales) { + test(`${locale}: ordinary estimate is $n.nnnn, lower bound is ≥$n.nnnn`, () => { + const t = translator(locale); + expect(formatLogsUsd(0.1401, t, LOCALE_TAGS[locale], false)).toBe("$0.1401"); + expect(formatLogsUsd(0.1401, t, LOCALE_TAGS[locale], true)).toBe("≥$0.1401"); + expect(formatLogsUsd(1234.5, t, LOCALE_TAGS[locale], false)).toBe("$1,234.5000"); + }); + + test(`${locale}: the cost templates carry no prose or currency code`, () => { + expect(DICTS[locale]["logs.cost.approximate"]).toBe("{amount}"); + expect(DICTS[locale]["logs.cost.lowerBound"]).toBe("≥{amount}"); + }); + } +}); + +describe("Usage total estimate keeps ~ and the same fixed dollar shape", () => { + for (const [locale, tag] of Object.entries(LOCALE_TAGS)) { + test(`${locale}`, () => { + expect(formatUsageUsd(0.1401, tag)).toBe("~$0.1401"); + }); + } + test("unavailable stays an em dash", () => { + expect(formatUsageUsd(Number.NaN)).toBe("\u2014"); + expect(formatUsageUsd(-1)).toBe("\u2014"); + }); +}); diff --git a/gui/tests/logs-effort-cell.test.ts b/gui/tests/logs-effort-cell.test.ts new file mode 100644 index 0000000000..83f27fdebb --- /dev/null +++ b/gui/tests/logs-effort-cell.test.ts @@ -0,0 +1,24 @@ +import { describe, expect, test } from "bun:test"; +import { readFileSync } from "node:fs"; +import { join } from "node:path"; + +const source = readFileSync(join(import.meta.dir, "../src/pages/Logs.tsx"), "utf8"); + +/** + * The table's effort cell used to stack the wire field (`reasoning_effort=high`) under the + * label. It repeated the label, and in the mono font it outgrew the 9% column and painted + * over the provider cell. The wire field stays reachable in the cell title and the detail + * dialog, so a reader who wants it still has it. + */ +describe("Logs table effort cell", () => { + test("renders the effort label only, with the wire field as the cell title", () => { + const cell = source.match(/([^]*?)<\/td>/); + expect(cell).not.toBeNull(); + expect(cell![1].trim()).toBe("{effortLabel(log)}"); + expect(cell![1]).not.toContain("reasoningWire"); + }); + + test("the detail dialog still shows the wire field next to the label", () => { + expect(source).toContain('{effortLabel(detail)}{reasoningWire ? ` (${reasoningWire})` : ""}'); + }); +}); diff --git a/gui/tests/logs-priority-lower-bound.test.ts b/gui/tests/logs-priority-lower-bound.test.ts index 877252c07f..cfb6f49b19 100644 --- a/gui/tests/logs-priority-lower-bound.test.ts +++ b/gui/tests/logs-priority-lower-bound.test.ts @@ -19,12 +19,13 @@ describe("Logs priority lower-bound formatting", () => { expect(formatEstimatedUsdValue(1.6, en, "en-US", true)).toBe("≥$1.6000"); }); - test("keeps ordinary standard-price estimates unchanged", () => { - expect(formatEstimatedUsdValue(1.6, en, "en-US", false)).toBe("~$1.6000"); + test("renders ordinary standard-price estimates as a bare dollar amount", () => { + expect(formatEstimatedUsdValue(1.6, en, "en-US", false)).toBe("$1.6000"); }); - test("uses locale-aware USD placement and separators", () => { - expect(formatEstimatedUsdValue(1.6, de, "de-DE", false)).toBe("ca. 1,6000\u00a0$"); + test("keeps the fixed dollar shape under a non-English locale", () => { + expect(formatEstimatedUsdValue(1.6, de, "de-DE", false)).toBe("$1.6000"); + expect(formatEstimatedUsdValue(1.6, de, "de-DE", true)).toBe("≥$1.6000"); }); }); @@ -37,7 +38,7 @@ describe("Logs table cost formatting", () => { expect(formatEstimatedUsd({ kind: "value", estimate: { cost: { total: 1.6 } }, - }, en, "en-US")).toBe("~$1.6000"); + }, en, "en-US")).toBe("$1.6000"); expect(formatEstimatedUsd({ kind: "unavailable" }, en, "en-US")).toBe("—"); expect(formatEstimatedUsdValue(Number.NaN, en, "en-US")).toBe("—"); }); diff --git a/gui/tests/logs-table-overflow.test.ts b/gui/tests/logs-table-overflow.test.ts new file mode 100644 index 0000000000..5d65bf2cf1 --- /dev/null +++ b/gui/tests/logs-table-overflow.test.ts @@ -0,0 +1,38 @@ +import { describe, expect, test } from "bun:test"; +import { readFileSync } from "node:fs"; +import { join } from "node:path"; + +const css = readFileSync(join(import.meta.dir, "../src/styles.css"), "utf8"); + +/** Last effective declaration of `prop` inside every `selector { ... }` block. */ +function lastDeclaration(selector: string, prop: string): string | undefined { + const escaped = selector.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + const block = new RegExp(`(?:^|[}\\n])\\s*${escaped}\\s*\\{([^}]*)\\}`, "g"); + let value: string | undefined; + for (const match of css.matchAll(block)) { + for (const decl of match[1].split(";")) { + const [name, ...rest] = decl.split(":"); + if (name?.trim().toLowerCase() === prop) value = rest.join(":").trim(); + } + } + return value; +} + +/** + * `table-layout: fixed` sizes the columns but never clips them: a value wider than its + * `` paints over the neighbour. Observed on the live dashboard as + * `US$0.1401claude-fable-5-1` and a `reasoning_effort=high` caption under the provider name. + */ +describe("Logs table cells clip inside their own column", () => { + test("body cells hide overflow", () => { + expect(lastDeclaration(".logs-table tbody td", "overflow")).toBe("hidden"); + }); + + test("the effort cell may break anywhere so an unbroken chain still wraps", () => { + expect(lastDeclaration(".log-reasoning-cell", "overflow-wrap")).toBe("anywhere"); + }); + + test("the table is still fixed-layout (the guard exists because of it)", () => { + expect(lastDeclaration("table.logs-table", "table-layout")).toBe("fixed"); + }); +}); From 548414a906a50a5731df033cefac10d3c0fda57b Mon Sep 17 00:00:00 2001 From: jun Date: Fri, 4 Sep 2026 01:36:21 +0900 Subject: [PATCH 06/49] docs(devlog): open the 260904 repository hygiene campaign unit --- .../260904_repo_hygiene_campaign/000_plan.md | 87 +++++++++++++++++++ .../010_method.md | 70 +++++++++++++++ .../020_wp1_local_branches.md | 27 ++++++ .../030_wp2_remote_branches.md | 31 +++++++ .../040_wp3_maintainer_prs.md | 21 +++++ .../050_wp4_contributor_prs.md | 32 +++++++ .../060_wp5_issues.md | 25 ++++++ .../070_wp6_credit_and_closeout.md | 20 +++++ 8 files changed, 313 insertions(+) create mode 100644 devlog/_plan/260904_repo_hygiene_campaign/000_plan.md create mode 100644 devlog/_plan/260904_repo_hygiene_campaign/010_method.md create mode 100644 devlog/_plan/260904_repo_hygiene_campaign/020_wp1_local_branches.md create mode 100644 devlog/_plan/260904_repo_hygiene_campaign/030_wp2_remote_branches.md create mode 100644 devlog/_plan/260904_repo_hygiene_campaign/040_wp3_maintainer_prs.md create mode 100644 devlog/_plan/260904_repo_hygiene_campaign/050_wp4_contributor_prs.md create mode 100644 devlog/_plan/260904_repo_hygiene_campaign/060_wp5_issues.md create mode 100644 devlog/_plan/260904_repo_hygiene_campaign/070_wp6_credit_and_closeout.md diff --git a/devlog/_plan/260904_repo_hygiene_campaign/000_plan.md b/devlog/_plan/260904_repo_hygiene_campaign/000_plan.md new file mode 100644 index 0000000000..f7f030084a --- /dev/null +++ b/devlog/_plan/260904_repo_hygiene_campaign/000_plan.md @@ -0,0 +1,87 @@ +# 260904 — Repository hygiene campaign + +Unit for the branch/PR/issue drawdown requested on 2026-09-04: delete landed and +abandoned refs locally and on `origin`, close superseded and partially-landed +pull requests and issues, consolidate surviving scope into new issues, and credit +every contributor whose work is carried. + +## Inventory at entry (2026-09-04, origin/dev = b5777aa2d) + +| Surface | Count | +|---|---| +| Local branches | 230 | +| Remote branches on `origin` | 56 | +| Open pull requests | 53 | +| Open issues | 45 | +| Worktrees | 67 | + +## Classification of local branches + +Every branch was scored on four independent axes rather than by name: + +1. `git merge-base --is-ancestor
origin/dev` — plain ancestry. +2. `git cherry origin/dev
` — patch-equivalence, which catches rebases. +3. Content landing — the files the branch touches + (`git diff --name-only origin/dev...
`) are compared two-dot against + `origin/dev` restricted to exactly those paths. Zero remaining difference + means the branch's content is already on `dev` even though a squash merge + destroyed its commit identity. +4. Exact reference matching against live GitHub state: open-PR head refs, + worktree-backing refs, and the PR number a scratch branch was cut for. + +Resulting buckets: + +| Bucket | Count | Disposition | +|---|---|---| +| PROTECTED (`dev`, `main`, `preview`) | 3 | never touched | +| OPEN_PR_HEAD | 7 | never touched | +| WORKTREE-backed | 44 | never touched | +| SAFE_DELETE (ancestor or zero unique commits) | 13 | delete | +| Scratch branches for MERGED/CLOSED PRs | 85 | delete | +| Content already landed on `dev` | 6 | delete | +| UNIQUE_WORK still unlanded | 39 | keep | + +## Prior-run failure this unit must not repeat + +A cleanup run on 2026-09-02 guessed PR numbers from branch names, treated the +guesses as merge evidence, and deleted the head refs of open pull requests: only +4 of 33 open PR heads survived it. Two rules follow. Open-PR head refs are read +from `gh` and matched by exact string immediately before each deletion batch, +never inferred. And a branch is deleted only when at least one of the four tests +above passes on the branch itself. + +## A shell hazard that produced a false positive + +The content-landing test was first written in shell. The login shell here is +zsh, which does not word-split an unquoted variable, so a 57-path file list +collapsed into a single nonexistent pathspec and `git diff` returned empty — +reporting `feat/macos-app`, a branch with 57 genuinely unlanded files including +an entire `app/` tree absent from `dev`, as fully landed. Acting on that would +have destroyed the macOS app work. + +The test was rebuilt in Python passing a real argument list, and validated +against controls in both directions before any deletion: an open PR head must +score UNLANDED, and a branch whose content is known to be on `dev` must score +LANDED. The rewritten test moved `feat/macos-app` to UNLANDED and reduced the +"landed" set from a bogus 41 to a verified 6. + +Rule for this unit: any bulk classifier gets a negative control before its +output authorizes a destructive action. + +## Work phases + +| Phase | Doc | Scope | +|---|---|---| +| wp0 | this file + 010 | roadmap and inventory | +| wp1 | 020 | local branch deletion | +| wp2 | 030 | `origin` remote branch deletion | +| wp3 | 040 | maintainer-authored PR drawdown | +| wp4 | 050 | contributor PR drawdown with credit | +| wp5 | 060 | issue drawdown and consolidation | +| wp6 | 070 | credit ledger and closeout | + +## Out of scope + +Merging any pull request, pushing to `dev`/`main`/`preview`, releases, +force-push, history rewriting, worktree removal, and behavior changes under +`src/`. The local test suite is forbidden for this unit by explicit instruction. diff --git a/devlog/_plan/260904_repo_hygiene_campaign/010_method.md b/devlog/_plan/260904_repo_hygiene_campaign/010_method.md new file mode 100644 index 0000000000..d35fdbfb2a --- /dev/null +++ b/devlog/_plan/260904_repo_hygiene_campaign/010_method.md @@ -0,0 +1,70 @@ +# 010 — Classification method and its controls + +## The four tests + +A local branch is deletable when at least one holds, and no guard fires. + +``` +T1 ancestry git merge-base --is-ancestor
origin/dev +T2 patch-equiv git cherry origin/dev
-> no '+' lines +T3 content paths = git diff --name-only origin/dev...
+ git diff --name-only origin/dev
-- -> empty +T4 scratch branch name encodes a PR number whose state is MERGED or CLOSED + AND the name matches the scratch prefix set +``` + +T3 is the one that matters for this repository, because `dev` takes squash +merges: after a squash the branch shares no commit with `dev`, so T1 and T2 both +report "unmerged" for work that is fully shipped. T3 asks the only question that +is actually load-bearing — is there any difference left in the files this branch +claims to change. + +T4 is deliberately narrow. It fires only for throwaway prefixes +(`pr*`, `rb-`, `jrb-`, `mtp/`, `big-`, `cf-`, `ocx-`, `wip/`, `backup/`, +`candidate`, `cursor-`, `midstream`) created by earlier review and rebase runs, +and only when the referenced PR is already MERGED or CLOSED. A `codex/*` branch +is never deleted on T4 alone. + +## The guards + +Deletion is refused, regardless of test result, for: + +- `dev`, `main`, `preview` +- any ref appearing as `headRefName` of an open pull request, read from `gh` + immediately before the batch and matched as an exact string +- any ref backing a live worktree, from `git worktree list --porcelain` +- the currently checked-out branch + +## Controls run before deletion was authorized + +The content test is a destructive-action authority, so it was falsified first. + +**Negative control.** `origin/codex/responses-usage-passthrough`, head of open +PR #3364, must not score LANDED. It differs from `dev` in 38 files and scored +UNLANDED. Passed. + +**Positive control.** `codex/remote-hub-restack-roadmap-archive` carries 39 +unique commits but every file it touches is already identical on `dev`; a +commit-based test calls it unmerged, the content test calls it LANDED. Passed. + +**Failure the controls caught.** The first shell implementation reported 41 +branches LANDED including `feat/macos-app`, which adds an entire `app/` tree +that does not exist on `dev`. Cause: zsh does not word-split unquoted +variables, so `git diff ... -- $paths` passed one 57-line pathspec that matched +nothing and produced empty output, which the test read as "no difference." Any +branch would have scored LANDED. Rebuilt in Python with a real argv list; the +landed set fell from 41 to 6 and `feat/macos-app` correctly moved to UNLANDED. + +## Result + +| Verdict | Count | +|---|---| +| Delete: scratch branch for a MERGED/CLOSED PR | 85 | +| Delete: ancestor of `dev` or zero unique commits | 13 | +| Delete: content already on `dev` (squash-hidden) | 6 | +| **Total deletion set** | **104** | +| Keep: unlanded unique work | 39 | +| Keep: open-PR head, worktree-backed, or protected | 54 | + +Ledger of the deletion set with per-branch reason: +`.tmp/hygiene/delete-local.json` (scratch space, not tracked). diff --git a/devlog/_plan/260904_repo_hygiene_campaign/020_wp1_local_branches.md b/devlog/_plan/260904_repo_hygiene_campaign/020_wp1_local_branches.md new file mode 100644 index 0000000000..53a8137997 --- /dev/null +++ b/devlog/_plan/260904_repo_hygiene_campaign/020_wp1_local_branches.md @@ -0,0 +1,27 @@ +# 020 — wp1: local branch deletion + +Delete the 104 branches in the verified deletion set, in batches, re-reading the +guard sets before each batch. + +## Procedure + +1. Snapshot every local ref to scratch: `git for-each-ref refs/heads` with SHAs, + so any deletion is recoverable by SHA for as long as the objects survive gc. +2. Re-read open-PR head refs from `gh` and worktree refs from + `git worktree list --porcelain`. Intersect with the deletion set; a non-empty + intersection aborts the phase. +3. Delete with `git branch -D` in batches of ~20, capturing the reported SHA for + each deletion. +4. Verify: `git for-each-ref refs/heads | wc -l` reaches 230 - 104 = 126, and + every protected/open-PR/worktree ref still resolves. + +`-D` rather than `-d` is required because squash-landed branches are not +ancestors of `dev` and `-d` refuses them; that is exactly the case T3 exists to +decide, and the decision has already been made with evidence. + +## Exit criteria + +- 126 local branches remain. +- All 7 open-PR head refs present locally still resolve. +- All 44 worktree-backing refs still resolve. +- `dev`, `main`, `preview` resolve to their pre-phase SHAs. diff --git a/devlog/_plan/260904_repo_hygiene_campaign/030_wp2_remote_branches.md b/devlog/_plan/260904_repo_hygiene_campaign/030_wp2_remote_branches.md new file mode 100644 index 0000000000..88ef240bf7 --- /dev/null +++ b/devlog/_plan/260904_repo_hygiene_campaign/030_wp2_remote_branches.md @@ -0,0 +1,31 @@ +# 030 — wp2: origin remote branch deletion + +`origin` carries 56 branches. The deletable set is the intersection of: + +- not `dev`, `main`, `preview` +- not the head ref of an open pull request whose head repository is + `lidge-jun/opencodex` (fork-hosted heads are not ours to delete and are not + reachable as `origin` refs anyway) +- content already on `dev` by the T3 test applied to `origin/`, or the + branch is a spent dispatch/promotion artifact + +Two families dominate the remote list and need separate judgment: + +- `origin/codex/win-dispatch-*` (9 refs) — CI dispatch artifacts pinned to a + commit SHA. Spent once their run finished. +- `origin/assets/*` and `origin/media/*` — evidence assets referenced from PR + and issue bodies by raw URL. Deleting these breaks images in published + descriptions, so they are retained unless the referencing item is closed and + the image is no longer rendered. Default is keep. + +Deletion uses `git push --no-verify origin --delete `, one ref per +command with a bounded timeout. `--no-verify` is required because the pre-push +hook runs a local suite, which is forbidden for this unit; the safety that hook +would provide is already supplied by the T1–T4 evidence and the guard sets, and +a deletion pushes no code. + +## Exit criteria + +- `git ls-remote --heads origin` no longer lists any deleted ref. +- Every open PR's head ref still resolves on its own repository. +- Asset branches still referenced by open items remain. diff --git a/devlog/_plan/260904_repo_hygiene_campaign/040_wp3_maintainer_prs.md b/devlog/_plan/260904_repo_hygiene_campaign/040_wp3_maintainer_prs.md new file mode 100644 index 0000000000..846f225265 --- /dev/null +++ b/devlog/_plan/260904_repo_hygiene_campaign/040_wp3_maintainer_prs.md @@ -0,0 +1,21 @@ +# 040 — wp3: maintainer-authored PR drawdown + +Ten of the 53 open pull requests are authored by `lidge-jun`. These carry no +contributor-credit obligation, so they are classified first and act as a +rehearsal for the evidence format used on contributor PRs. + +Classification per PR: + +- **SUPERSEDED** — every file the PR touches is already identical on `dev` + (T3 applied to the PR head). Close with a comment naming the landing commit. +- **PARTIAL** — some paths landed, some did not. Close and carry the remainder + into a consolidated follow-up issue. +- **LIVE** — keep open. + +Evidence recorded per PR: head SHA, files touched, files still differing from +`dev`, and the commit or PR that landed the overlap. + +## Exit criteria + +Every maintainer PR has a verdict with captured evidence, and each SUPERSEDED or +PARTIAL one is closed with a comment a reader can independently check. diff --git a/devlog/_plan/260904_repo_hygiene_campaign/050_wp4_contributor_prs.md b/devlog/_plan/260904_repo_hygiene_campaign/050_wp4_contributor_prs.md new file mode 100644 index 0000000000..b34897c322 --- /dev/null +++ b/devlog/_plan/260904_repo_hygiene_campaign/050_wp4_contributor_prs.md @@ -0,0 +1,32 @@ +# 050 — wp4: contributor PR drawdown with credit + +43 of the 53 open pull requests come from outside contributors. Closing someone's +pull request is the moment their work either gets recorded or disappears, so this +phase is bound by the attribution policy in `AGENTS.md` and the existing +`CREDITS.md` ledger. + +## Rules + +1. No contributor PR is closed without a comment that names the author, states + what happened to their work, and links the evidence. +2. If the work landed on `dev` by another route — reimplementation, carry, or + rebase — that is a carry, and it requires a `Co-authored-by` trailer on the + landing commit. For work already landed without one, the repair path is + `CREDITS.md`, because `dev`, `main`, and `preview` are force-push protected + and the affected commits are inside published tags. History is not rewritten. +3. PARTIAL contributions are closed only alongside a follow-up issue that names + the contributor and states which part of their proposal survives. +4. A PR that is merely stale, unrebased, or awaiting review is LIVE. Age is not + evidence of supersession. + +## Draft-state contributors + +Many contributor PRs sit in draft behind the four-box readiness gate, and several +carry `intake: hygiene-blocked`. Draft state means the gate has not passed, not +that the work is unwanted — these are classified on content like any other. + +## Exit criteria + +Every contributor PR has a verdict with evidence; every closure has a credited +comment URL captured; every carried contribution appears in `CREDITS.md` or +already carries its trailer. diff --git a/devlog/_plan/260904_repo_hygiene_campaign/060_wp5_issues.md b/devlog/_plan/260904_repo_hygiene_campaign/060_wp5_issues.md new file mode 100644 index 0000000000..a597a55161 --- /dev/null +++ b/devlog/_plan/260904_repo_hygiene_campaign/060_wp5_issues.md @@ -0,0 +1,25 @@ +# 060 — wp5: issue drawdown and consolidation + +45 open issues. Classification mirrors the PR phase, with one addition: an issue +can be superseded by a *shipped feature* rather than by a specific PR, so the +evidence is a released capability plus the commit that introduced it. + +## Consolidation + +PARTIAL issues are the reason this phase exists. Where several issues describe +facets of one surviving need — account-pool routing, quota-window handling, +provider catalog capability gaps — they are closed individually and absorbed +into one consolidated issue per cluster. Each consolidated issue must: + +- state the remaining scope in its own words, not by reference only; +- link every absorbed issue by number; +- name every original reporter so credit follows the scope; +- use the repository issue template. + +A consolidated issue that merely lists links is not acceptable — the point is +that the surviving requirement stays legible after the sources are closed. + +## Exit criteria + +Every open issue has a verdict; consolidated issues exist for each PARTIAL +cluster; no issue is closed without its reporter being named. diff --git a/devlog/_plan/260904_repo_hygiene_campaign/070_wp6_credit_and_closeout.md b/devlog/_plan/260904_repo_hygiene_campaign/070_wp6_credit_and_closeout.md new file mode 100644 index 0000000000..43447e5666 --- /dev/null +++ b/devlog/_plan/260904_repo_hygiene_campaign/070_wp6_credit_and_closeout.md @@ -0,0 +1,20 @@ +# 070 — wp6: credit ledger and closeout + +Final phase. Reconcile every closure made in wp3–wp5 against the attribution +policy, extend `CREDITS.md` where a contribution was carried without a trailer, +and record the campaign result. + +## Checks + +1. Each closed contributor item has a comment naming its author. Verified by + re-reading the comments through `gh`, not from memory of having posted them. +2. Each carried contribution is either covered by a `Co-authored-by` trailer on + its landing commit or listed in `CREDITS.md`. +3. `missing_coauthor_credit` in `.github/scripts/pr-carry-attribution.cjs` + remains the forward guard; this phase must not grow the historical list + without recording why. + +## Closeout + +Final counts for branches, remote refs, open PRs, and open issues, each measured +live rather than derived from the plan. The unit then moves to `devlog/_fin/`. From 67de941bfbcc240271c6e17f0a42adea331488b3 Mon Sep 17 00:00:00 2001 From: jun Date: Fri, 4 Sep 2026 01:57:58 +0900 Subject: [PATCH 07/49] docs(devlog): harden the T4 scratch rule after the auditor caught a false PR match --- .../010_method.md | 44 +++++++++++++++---- .../020_wp1_local_branches.md | 20 ++++++--- 2 files changed, 49 insertions(+), 15 deletions(-) diff --git a/devlog/_plan/260904_repo_hygiene_campaign/010_method.md b/devlog/_plan/260904_repo_hygiene_campaign/010_method.md index d35fdbfb2a..773b6db649 100644 --- a/devlog/_plan/260904_repo_hygiene_campaign/010_method.md +++ b/devlog/_plan/260904_repo_hygiene_campaign/010_method.md @@ -11,6 +11,9 @@ T3 content paths = git diff --name-only origin/dev...
git diff --name-only origin/dev
-- -> empty T4 scratch branch name encodes a PR number whose state is MERGED or CLOSED AND the name matches the scratch prefix set + AND the number is a WHOLE numeric token of the branch name + AND the branch is provably a duplicate of that PR's head: + identical SHA, an ancestor of it, or content-identical to it ``` T3 is the one that matters for this repository, because `dev` takes squash @@ -25,6 +28,22 @@ T4 is deliberately narrow. It fires only for throwaway prefixes and only when the referenced PR is already MERGED or CLOSED. A `codex/*` branch is never deleted on T4 alone. +**T4 alone is not sufficient, and the first version of it was wrong.** PR state +says nothing about whether *this branch* still holds unique work, so T4 now +requires a positive duplication proof against the PR head itself: the branch is +the same commit, an ancestor of it, or content-identical to it. If the PR head +cannot be fetched or the branch matches none of those, the branch falls through +to the content test against `dev`, and if that also fails it is preserved. + +The number must also be a whole numeric token of the branch name. The naive +regex extracted `2608` from the date suffix in +`cursor-call-prerebase-260818` and matched it to an unrelated merged PR — the +exact name-guessing that destroyed open-PR heads on 2026-09-02, reproduced +inside the very unit written to prevent it. That branch holds two unique Cursor +stream-EOF and cancel fixes and 31 otherwise-unreachable commits. + +This was caught by an independent auditor, not by the author of the rule. + ## The guards Deletion is refused, regardless of test result, for: @@ -57,14 +76,23 @@ landed set fell from 41 to 6 and `feat/macos-app` correctly moved to UNLANDED. ## Result -| Verdict | Count | -|---|---| -| Delete: scratch branch for a MERGED/CLOSED PR | 85 | -| Delete: ancestor of `dev` or zero unique commits | 13 | -| Delete: content already on `dev` (squash-hidden) | 6 | -| **Total deletion set** | **104** | -| Keep: unlanded unique work | 39 | -| Keep: open-PR head, worktree-backed, or protected | 54 | +Candidate set 104, of which 33 failed the hardened tests and are preserved. + +| Verdict | Count | Proof | +|---|---|---| +| Delete | 50 | identical SHA to its PR head | +| Delete | 2 | ancestor of its PR head | +| Delete | 13 | ancestor of `dev` or zero unique commits | +| Delete | 6 | content already on `dev` (squash-hidden) | +| **Total deletion set** | **71** | every entry carries a named proof | +| Preserved: failed the duplication proof | 32 | | +| Preserved: number not a whole token | 1 | `cursor-call-prerebase-260818` | +| Keep: unlanded unique work | 39 | | +| Keep: open-PR head, worktree-backed, protected | 54 | | + +Every entry in the final set names its own proof, so no deletion rests on the +absence of evidence. Ledgers: `.tmp/hygiene/DELETE_FINAL.json` and +`.tmp/hygiene/REJECTED_FINAL.json`. Ledger of the deletion set with per-branch reason: `.tmp/hygiene/delete-local.json` (scratch space, not tracked). diff --git a/devlog/_plan/260904_repo_hygiene_campaign/020_wp1_local_branches.md b/devlog/_plan/260904_repo_hygiene_campaign/020_wp1_local_branches.md index 53a8137997..ac1c7723fe 100644 --- a/devlog/_plan/260904_repo_hygiene_campaign/020_wp1_local_branches.md +++ b/devlog/_plan/260904_repo_hygiene_campaign/020_wp1_local_branches.md @@ -1,7 +1,8 @@ # 020 — wp1: local branch deletion -Delete the 104 branches in the verified deletion set, in batches, re-reading the -guard sets before each batch. +Delete the 71 branches in the verified deletion set, in batches, re-reading the +guard sets before each batch. Each entry carries a named proof; a branch with no +proof is preserved rather than deleted. ## Procedure @@ -12,8 +13,11 @@ guard sets before each batch. intersection aborts the phase. 3. Delete with `git branch -D` in batches of ~20, capturing the reported SHA for each deletion. -4. Verify: `git for-each-ref refs/heads | wc -l` reaches 230 - 104 = 126, and - every protected/open-PR/worktree ref still resolves. +4. Verify: the local branch count drops by exactly 71, and every + protected / open-PR / worktree ref still resolves. Counts are measured live + at execution rather than asserted here — the branch total moves as other + sessions work in this repository, and a stale expected number is a false + alarm, not a safety property. `-D` rather than `-d` is required because squash-landed branches are not ancestors of `dev` and `-d` refuses them; that is exactly the case T3 exists to @@ -21,7 +25,9 @@ decide, and the decision has already been made with evidence. ## Exit criteria -- 126 local branches remain. -- All 7 open-PR head refs present locally still resolve. -- All 44 worktree-backing refs still resolve. +- Exactly the 71 approved refs are gone; nothing else was removed. +- Every open-PR head ref present locally still resolves. +- Every worktree-backing ref still resolves. - `dev`, `main`, `preview` resolve to their pre-phase SHAs. +- `cursor-call-prerebase-260818` and the other 32 preserved branches still + resolve. From 491a45baa95cd73101ef4539af37188270b27949 Mon Sep 17 00:00:00 2001 From: jun Date: Fri, 4 Sep 2026 02:11:00 +0900 Subject: [PATCH 08/49] docs(devlog): record the four audit rounds behind the deletion ledger --- .../015_audit_record.md | 78 +++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 devlog/_plan/260904_repo_hygiene_campaign/015_audit_record.md diff --git a/devlog/_plan/260904_repo_hygiene_campaign/015_audit_record.md b/devlog/_plan/260904_repo_hygiene_campaign/015_audit_record.md new file mode 100644 index 0000000000..3b5321594d --- /dev/null +++ b/devlog/_plan/260904_repo_hygiene_campaign/015_audit_record.md @@ -0,0 +1,78 @@ +# 015 — Audit record for the deletion ledger + +The branch-deletion ledger was reviewed by an independent auditor before any +branch was touched. It failed three times. Each failure is recorded here because +each one would have destroyed work. + +## Round 1 — FAIL + +> `cursor-call-prerebase-260818` was matched to unrelated PR #2608 by parsing a +> date-like branch suffix and still contains unique unmerged patches + +The scratch-branch rule extracted the first 3–4 digit run from a branch name and +treated the matching PR's state as merge evidence. The branch is dated +2026-08-18, so `260818` yielded `2608`, which is a real merged PR about a +completely different subject. The branch carries two unique Cursor fixes — an +unlabeled stream EOF failure and a cancel surface — and 31 commits reachable +from nothing else. + +This is the same class of error that deleted open-PR head refs on 2026-09-02, +reproduced inside the unit written to prevent it. Writing the rule down did not +prevent it; an auditor running the numbers did. + +Fix: the PR number must be a whole numeric token of the branch name, and PR +state alone no longer authorizes anything — the branch must be proven a +duplicate of that PR's head (same SHA, ancestor, or content-identical). +Candidate set 104 → approved 71. + +## Round 2 — FAIL + +> `final.py` can authorize deletion from a stale PR-head ref or failed `git +> diff` because both command failures are ignored + +The generator ignored return codes. A failed `fetch` left a stale +`refs/prhead/` that would be compared as if current, and a failed `git diff` +produced empty stdout that read as "no difference" — the same shape as the zsh +bug in `010_method.md`, where absence of output was mistaken for absence of +change. Twice in one unit, so it is a pattern and not an accident: **empty +output is not evidence unless the command is known to have succeeded.** + +Fix: fail-closed. Git failures raise, PR heads are force-fetched with a checked +return code, and any error rejects the branch. Regenerating produced exactly the +same 71 branches, which is itself the evidence that the earlier approvals were +sound rather than lucky. + +## Round 3 — FAIL + +> cached T1/T2 and T3 proofs are not recomputed or SHA-bound, so a branch that +> moves after classification can lose new work + +Proofs were inherited from JSON snapshots taken earlier in the session and the +ledger stored no SHAs, so a branch that gained a commit between classification +and deletion would still be deleted on the strength of a stale verdict. + +Fix: snapshots now supply only the candidate list. Every proof is recomputed +live, and each approval records the branch tip, the proof, and the `origin/dev` +SHA it was proven against. Execution re-reads each tip immediately before +deletion and refuses on any mismatch. + +## Round 4 — PASS + +- 71/71 recorded tips equal current branch tips +- 71/71 proofs still hold at the recorded SHA +- 33/33 rejected branches still present, including `cursor-call-prerebase-260818` +- guards empty against live state: no open-PR head, no worktree ref, nothing protected +- `origin/dev` moved during the audit (`b5777aa2d` → `664d80c76`) and invalidates + no proof; no rejected branch became landed as a result + +Non-safety note from the auditor: `rb-2122-ELZMyj` and `rb-2734` are +tree-identical to their PR heads but stay preserved because the comparison uses +three-dot form. Over-preservation, so it stands. + +## What this cost and why it was worth it + +Four rounds against one auditor, no branch deleted until the fourth passed. The +first round alone justifies the whole exercise: the plan document explicitly +warned against branch-name guessing on line 46, and the implementation did it +anyway on line 12 of the very next file. A rule you wrote does not audit the +code you wrote. From 1abb7925ea523883f558a81e1b312df1e6af09fe Mon Sep 17 00:00:00 2001 From: jun Date: Fri, 4 Sep 2026 02:14:39 +0900 Subject: [PATCH 09/49] docs(devlog): record wp1/wp2 branch cleanup and the orphan-branch fail-open --- .../030_wp2_remote_branches.md | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/devlog/_plan/260904_repo_hygiene_campaign/030_wp2_remote_branches.md b/devlog/_plan/260904_repo_hygiene_campaign/030_wp2_remote_branches.md index 88ef240bf7..de23ab8d5d 100644 --- a/devlog/_plan/260904_repo_hygiene_campaign/030_wp2_remote_branches.md +++ b/devlog/_plan/260904_repo_hygiene_campaign/030_wp2_remote_branches.md @@ -24,6 +24,40 @@ hook runs a local suite, which is forbidden for this unit; the safety that hook would provide is already supplied by the T1–T4 evidence and the guard sets, and a deletion pushes no code. +## Outcome + +Of 62 non-protected remote refs, only 2 were provably spent: + +| Branch | Proof | Result | +|---|---|---| +| `codex/regaudit-ci-main-af6113a03` | empty vs `dev` | deleted | +| `codex/260904-logs-cost-effort-polish` | content already on `dev` (PR #3367 merged) | already gone; pruned locally | + +38 hold unique unlanded work, 14 are open-PR heads, 5 are orphans, 3 protected. +The remote was already close to minimal — the sprawl was local. + +## A third fail-open, caught here + +The first remote pass marked all five `assets/*` branches deletable as +"content_landed". They are **orphan branches with no merge base**, so +`git diff origin/dev...origin/assets/*` exits 128 with +`fatal: ... no merge base` and prints nothing. Reading that empty stdout as +"no difference" would have deleted five evidence branches holding 36 image files +that exist nowhere else. + +This is the same mistake as the zsh word-split in `010_method.md` and the +ignored return codes in `015_audit_record.md`: **empty output treated as +evidence of absence, when it was actually evidence of a failed command.** Three +occurrences in one campaign, each in code written after the previous one was +documented. + +The remote classifier is now fail-closed the same way: a missing merge base +disqualifies every `dev`-relative test and the branch is preserved outright. +The five orphan asset branches are retained under `orphan_no_merge_base`. + +The general lesson, now stated once for the whole unit: a test whose "safe" +answer is produced by silence must verify that the command spoke. + ## Exit criteria - `git ls-remote --heads origin` no longer lists any deleted ref. From 07ae3ab947b38027e7d97f8c5cf3601c805ed703 Mon Sep 17 00:00:00 2001 From: jun Date: Fri, 4 Sep 2026 02:15:10 +0900 Subject: [PATCH 10/49] docs(devlog): record the wp1 deletion outcome and post-deletion verification --- .../020_wp1_local_branches.md | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/devlog/_plan/260904_repo_hygiene_campaign/020_wp1_local_branches.md b/devlog/_plan/260904_repo_hygiene_campaign/020_wp1_local_branches.md index ac1c7723fe..4c76c491d7 100644 --- a/devlog/_plan/260904_repo_hygiene_campaign/020_wp1_local_branches.md +++ b/devlog/_plan/260904_repo_hygiene_campaign/020_wp1_local_branches.md @@ -23,6 +23,27 @@ proof is preserved rather than deleted. ancestors of `dev` and `-d` refuses them; that is exactly the case T3 exists to decide, and the decision has already been made with evidence. +## Outcome (executed 2026-09-04) + +71 branches deleted, each after re-reading its tip and comparing it to the SHA +recorded at classification. Zero failures, zero tip mismatches. + +| Measure | Before | After | +|---|---|---| +| Local branches | 241 | 170 | + +Post-deletion verification, run against live state rather than the plan: + +| Check | Result | +|---|---| +| Open-PR head refs present locally that were lost | 0 of 13 | +| Worktree-backing refs lost | 0 of 47 | +| Preserved (rejected) branches wrongly deleted | 0 of 33 | +| `dev` / `main` / `preview` intact | yes | + +That first row is the whole point of this unit. The 2026-09-02 run left only 4 +of 33 open-PR heads alive; this one lost none. + ## Exit criteria - Exactly the 71 approved refs are gone; nothing else was removed. From f825858da5b2e8dc5c949cc9f17b5111bf07bda4 Mon Sep 17 00:00:00 2001 From: JUN Date: Fri, 4 Sep 2026 02:27:54 +0900 Subject: [PATCH 11/49] fix(oauth): add the OpenAI deviceauth grant for headless ChatGPT login (#3369) * docs(devlog): record the 260904 bug stack train roadmap Live manifest of 10 open bug PRs and 6 open bug issues, plus the dependency-ordered plan for issue #3366 (OpenAI deviceauth headless login). * fix(oauth): add the OpenAI deviceauth grant for headless ChatGPT login A hub running headless in a container or over SSH has no browser and no listener on localhost:1455, so the callback flow cannot complete there. The only workaround was to let the redirect fail and copy the long URL out of the browser error page. This adds the deviceauth grant Codex CLI uses: request a short user code, poll until the user enters it at auth.openai.com/codex/device, then spend the server-issued authorization code and PKCE verifier at the ordinary token endpoint. loginChatGPT gains flow: "device" to select it; the callback flow is untouched and remains the default. Refs #3366 * fix(oauth): harden the deviceauth grant against interval and deadline abuse Review of the first commit found four real defects, each with a reproduction: - A finite but absurd poll interval overflowed the 32-bit timer and fired immediately, producing 34 token requests in ~50ms. Clamp to the grant's own 15-minute lifetime. - The pending branch slept a full interval regardless of time remaining, so a 600-second interval ran the "15 minute" grant for 20 minutes. Cap the wait at the time actually left. - Upstream accepts both user_code and usercode; only the first was read, so a response using the alias failed as malformed. - credsFromToken cast access_token instead of validating it, so a 200 carrying no token resolved the login as successful with an undefined credential that was then silently declined at persistence. Also adds device_auth_id to the OAuth logger's forbidden keys. The grant does not log, but the handle bears an in-flight authorization and should never be loggable by a future caller. * fix(oauth): reject a device grant that arrives after the deadline A single poll can outlive the 15-minute grant. The deadline was only checked at the top of the loop, so a slow poll that resolved past expiry was accepted and the failure just moved to the token exchange. The 403/404 pending tests now assert the interval is actually honored rather than silently paying two seconds for nothing, and a deterministic-clock test covers the late-arriving grant. --------- Co-authored-by: jun --- .../260904_bug_stack_train/000_research.md | 79 ++++++ .../010_wp2_deviceauth_core.md | 72 +++++ .../020_wp3_deviceauth_surface.md | 67 +++++ .../030_wp4_bug_carry.md | 39 +++ .../260904_bug_stack_train/040_deferrals.md | 30 +++ src/oauth/chatgpt-device.ts | 187 +++++++++++++ src/oauth/chatgpt.ts | 35 ++- src/oauth/index.ts | 16 +- src/oauth/log.ts | 3 + tests/chatgpt-device-auth.test.ts | 246 ++++++++++++++++++ 10 files changed, 767 insertions(+), 7 deletions(-) create mode 100644 devlog/_plan/260904_bug_stack_train/000_research.md create mode 100644 devlog/_plan/260904_bug_stack_train/010_wp2_deviceauth_core.md create mode 100644 devlog/_plan/260904_bug_stack_train/020_wp3_deviceauth_surface.md create mode 100644 devlog/_plan/260904_bug_stack_train/030_wp4_bug_carry.md create mode 100644 devlog/_plan/260904_bug_stack_train/040_deferrals.md create mode 100644 src/oauth/chatgpt-device.ts create mode 100644 tests/chatgpt-device-auth.test.ts diff --git a/devlog/_plan/260904_bug_stack_train/000_research.md b/devlog/_plan/260904_bug_stack_train/000_research.md new file mode 100644 index 0000000000..ac4c1cba78 --- /dev/null +++ b/devlog/_plan/260904_bug_stack_train/000_research.md @@ -0,0 +1,79 @@ +# 000 — Live manifest and disposition research + +Snapshot taken 2026-09-04, base `origin/dev` = `b5777aa2d642`. +Worktree: `/Users/jun/.codex/worktrees/9d5b/opencodex`. + +All findings below were produced by four parallel read-only research lanes and +re-checked against the current tree. Every disposition names its evidence. + +## Open bug-labelled PRs (10) + +| PR | Author | Verdict | Basis | +|----|--------|---------|-------| +| #3335 | x3M3x | LAND_AS_IS | GUI hardcodes 2 of 5 strategies at `gui/src/components/combo-workspace-controls.tsx:24-50`; canonical set already has 5 at `gui/src/combo-workspace-data.ts:11-30`. Test is RED without the fix. | +| #3333 | blackjune67 | LAND_AS_IS | Models panels are persistent and toggle `hidden` (`gui/src/pages/Models.tsx:2228-2312`); scoping to the visible panel id stops width leakage. Test asserts selectors absent on dev. | +| #3322 | luvs01 | LAND_AS_IS | Head already implements the exact requested message at `src/cli/observe.ts:75-77`. The `CHANGES_REQUESTED` review is stale against the corrected head. | +| #3357 | huaiqing-afk | LAND_AS_IS (draft) | One global previous-text slot at `src/adapters/cursor/protobuf-request.ts:303-381` lets every tool result reset narration detection. PR tracks roles independently. Strong RED regression. | +| #3325 | luvs01 | BLOCKED_ON_POLICY | Code correct, but `.github/workflows/` is a restricted surface (`.github/scripts/pr-sponsored-surface.cjs:24-27`); hygiene fails `unsponsored_surface` without the `maintainer-sponsored` label. The second "failure" is a cancelled `enforce-target` run, not a real failure. | +| #3364 | lidge-jun | LAND_WITH_FIX | Exact-head CI green. Missing a direct `parseResponse()` non-stream regression even though production parse calls the same extractor (`src/adapters/openai-responses.ts:2450-2481`). | +| #3361 | lidge-jun | LAND_AS_IS | Exact-head CI green; marker/journal ownership preserved per key; `startServer` stays synchronous. Touches unauthenticated loopback admission, so it needs explicit maintainer security sign-off. | +| #3332 | full999 | LAND_WITH_FIX | Writes an OUTPUT limit into an INPUT field: `ModelMetadata.maxTokens` is output (`src/generated/model-metadata.ts:4-12`) but lands in `maxInputTokens`. Would shrink Claude 1M input models to 64K/128K. | +| #3348 | RHODIZSECURITY | DEFER | 2,248 lines / 34 files across failover, credentials, persistence, shutdown, and the core response path. Confirmed blocker: generic HTTP 410/413 become retryable hops, so an oversized or invalid request is replayed to the next provider. | +| #3312 | RHODIZSECURITY | DEFER (superseded) | Functionally the same work as #3348 with the same 410/413 blocker; currently CONFLICTING/DIRTY. Not an ancestry successor, but #3348 supersedes it. | + +## Open bug-labelled issues (6) + +None are safely fixable from the evidence currently attached. Detail: + +- **#3352** (GPT-5.6 401) — NEEDS_REPORTER_EVIDENCE. Mechanism is established end to end: + gating at `src/codex/catalog/native-models.ts:5`, roster fetch at + `src/codex/model-entitlements.ts:185`, unconfirmed-evidence fallback at `:548`, + granted-only projection at `:958`/`:1024`, and the exact 401 at + `src/codex/auth-context.ts:435`. The reported `0.142.2` floor theory is already + ruled out — the code enforces `0.144.0` at `:75`. Letting `unknown` through would + be a security-policy change, not a bug fix. +- **#3320** (Windows non-ASCII scheduler) — NEEDS_REPORTER_EVIDENCE. Production XML + writes a locale-independent SID (`src/service.ts:1841,1912`); exact `` + matching is deliberate (`:2117`) because folding two non-ASCII identities to `???` + could adopt another account's task. Needs redacted live XML before any patch. +- **#3279** (GUI 401) — NEEDS_REPORTER_EVIDENCE. Each page load mints a session from + its own Host-derived origin (`src/server/gui-session.ts:166`); exact origin checks + are the admission boundary (`:417`); expiry is deterministic at 5 minutes (`:62`). + Canonicalizing localhost/IPv4/IPv6 would weaken auth without proving cause. +- **#3255** (capability vs speed) — PRODUCT_DECISION. The two dimensions are already + independent (`src/reasoning-effort.ts:5` vs `src/codex/catalog/effort.ts:160`), and + there is no Ultra-fast wire tier to pass through. +- **#3245** (stream disconnect) — NEEDS_REPORTER_EVIDENCE. 426 is intentional + (`src/server/index.ts:1107`) and the 426-then-POST path is already covered + (`tests/server-auth.test.ts:1384`). The reporter saw no subsequent POST, which puts + the failure before the Responses bridge. +- **#1527** (Cursor large context) — NEEDS_REPORTER_EVIDENCE. Every known defect in + this path is already fixed; a matched current-dev trace is required. + +## Issue #3366 — deviceauth (the implementation target) + +Key correction to the issue's premise: `chatgpt` is deliberately excluded from the +generic OAuth surface (`src/oauth/index.ts:284-297`, `tests/oauth-public-surface.test.ts:77-111`) +and `openai|codex|chatgpt` route through the separate Codex-auth API. Returning +`deviceCode` from `src/oauth/` alone therefore does NOT light up the existing UI — +the Codex-auth layer discards it today at `src/codex/auth-api.ts:2199-2209`. + +Upstream wire flow, confirmed against `codex-rs/login/src/device_code_auth.rs`: +15-minute poll window, only 403/404 mean pending, server-issued `code_verifier`, +and `redirect_uri=https://auth.openai.com/deviceauth/callback`. + +Non-fabrication note: the issue claims a `codex_cli_rs` User-Agent is required. +Upstream actually builds a raw auth client with no Codex default headers +(`device_code_auth.rs:165-171`), and its real UA is dynamic. We do not hard-code +client impersonation; we send no custom UA and let the platform default stand. + +## Stack plan + +Dependency-ordered, bottom-up (DEV-STACK-01): + +1. `codex/deviceauth-core` — the grant itself in `src/oauth/` (010) +2. `codex/deviceauth-surface` — Codex-auth API + CLI + docs (020) +3. `codex/bug-carry` — carried contributor fixes with attribution (030) + +Deferred out of the stack with recorded reasons: #3348, #3312, #3325, and all six +bug issues. Documented in 040. diff --git a/devlog/_plan/260904_bug_stack_train/010_wp2_deviceauth_core.md b/devlog/_plan/260904_bug_stack_train/010_wp2_deviceauth_core.md new file mode 100644 index 0000000000..eb3de69d31 --- /dev/null +++ b/devlog/_plan/260904_bug_stack_train/010_wp2_deviceauth_core.md @@ -0,0 +1,72 @@ +# 010 — wp2: deviceauth grant core (stack layer 1) + +Branch: `codex/deviceauth-core`, based on `origin/dev` `b5777aa2d642`. +Thesis: implement the OpenAI deviceauth grant as a self-contained module and let +`loginChatGPT` select it. Nothing outside `src/oauth/` changes in this layer. + +## Files + +- ADD `src/oauth/chatgpt-device.ts` — the grant. +- MODIFY `src/oauth/chatgpt.ts` — export `credsFromToken` for reuse; add the + `flow` option to `loginChatGPT`. +- MODIFY `src/oauth/index.ts` — thread `flow` through the `chatgpt` registry entry. +- ADD `tests/chatgpt-device-auth.test.ts`. +- MODIFY `tests/oauth-device-code-contract.test.ts` — extend the shared contract to chatgpt. + +## Wire protocol (from codex-rs device_code_auth.rs) + +1. `POST https://auth.openai.com/api/accounts/deviceauth/usercode` + JSON `{ client_id }` -> `{ device_auth_id, user_code, interval? }` +2. `POST https://auth.openai.com/api/accounts/deviceauth/token` + JSON `{ device_auth_id, user_code }`; 403/404 = pending; 200 = + `{ authorization_code, code_verifier }` +3. `POST https://auth.openai.com/oauth/token` form-encoded + `grant_type=authorization_code`, `client_id`, `code`, `code_verifier`, + `redirect_uri=https://auth.openai.com/deviceauth/callback` + +Poll window 15 minutes; default interval 5s; the interval field may arrive as a +string, so coerce numerically and floor at 1s. + +## Signatures + +```ts +export type ChatGPTLoginFlow = "browser" | "device"; +export async function loginChatGPTDevice(ctrl: OAuthController): Promise; +export async function loginChatGPT( + ctrl: OAuthController, + opts?: { forceLogin?: boolean; flow?: ChatGPTLoginFlow }, +): Promise; +``` + +`onAuth` publishes `{ url: "https://auth.openai.com/codex/device", deviceCode: user_code, +instructions }` — matching the kimi/nous/copilot contract where `deviceCode` carries the +HUMAN code, never the opaque polling handle. + +## Credential boundary + +- Never log `device_auth_id`, `authorization_code`, `code_verifier`, or any token. +- Do NOT reuse `safeErrorDescription` from the callback flow: it reflects upstream + body text. Device errors carry status only. +- Bound the success payload; reject non-string `authorization_code`/`code_verifier`. + +## Tests (red-then-green) + +`tests/chatgpt-device-auth.test.ts`, stubbing `globalThis.fetch` by URL in the +established style of `tests/oauth-device-code-contract.test.ts:16-63`: + +1. requests a user code and surfaces the fixed verification URL + human code +2. treats only 403/404 as pending and honors the returned interval +3. exchanges the server-issued `authorization_code`/`code_verifier` at the device callback URI +4. rejects a malformed success payload without reflecting the body +5. aborts promptly on signal +6. surfaces `accountId`/`email` from a realistic device-token `id_token`, because Codex + pool admission rejects a credential with no account id (`src/codex/auth-api.ts:2221`). + Wire success alone is not proof the credential is usable. + +Focused command: `bun test tests/chatgpt-device-auth.test.ts tests/oauth-device-code-contract.test.ts tests/chatgpt-oauth.test.ts` + +## Security review gate + +`src/oauth/` is a restricted authentication surface +(`.github/scripts/pr-sponsored-surface.cjs:24`); `MAINTAINERS.md:60` requires explicit +security review. The PR description states this; it does not merge as routine work. diff --git a/devlog/_plan/260904_bug_stack_train/020_wp3_deviceauth_surface.md b/devlog/_plan/260904_bug_stack_train/020_wp3_deviceauth_surface.md new file mode 100644 index 0000000000..ba954946cd --- /dev/null +++ b/devlog/_plan/260904_bug_stack_train/020_wp3_deviceauth_surface.md @@ -0,0 +1,67 @@ +# 020 — wp3: deviceauth surface (stack layer 2) + +Branch: `codex/deviceauth-surface`, based on `codex/deviceauth-core`. +Thesis: make the grant reachable. Without this layer the core is unreachable from any +user-facing path, because `openai|codex|chatgpt` go through the Codex-auth API, which +drops `deviceCode` from the start DTO at `src/codex/auth-api.ts:2440` and opens the +authorization URL at `:2206` (conditional on `shouldOpenBrowserForLogin`, which already +honors an explicit/configured false at `src/oauth/open-browser-choice.ts:20` — the gap is +that a device flow is not itself a reason to skip the open). + +## Files + +- MODIFY `src/codex/auth-api.ts` — accept `device?: boolean` on login start, pass + `flow: "device"` into the chatgpt login, return `deviceCode` in the start DTO, and + suppress the server-side browser open when `deviceCode` is present (mirroring + `src/server/management/oauth-account-routes.ts:185`). +- MODIFY `src/cli/account-auth.ts` — add `--device`; include it in the login body; + print `Device code: ` in the Codex pre-poll block; preserve it under + `--no-wait --json`. +- MODIFY `src/cli/capabilities.ts` — declare the flag. +- MODIFY `gui/src/components/use-add-codex-account-oauth.ts` — keep `deviceCode` and + `instructions` on the start DTO (dropped today at `:148`) and request device mode. +- MODIFY `gui/src/components/add-codex-account-reducer.ts` — carry both fields in state. +- MODIFY `gui/src/components/add-codex-account-waiting-step.tsx` — pass them to + `LoginHint` (today it passes only `url` at `:38`). The shared renderer at + `gui/src/components/login-url-block.tsx:42-47,73-107` is already device-capable, so no + new UI component is needed. +- REGENERATE `skills/ocx/references/01_management_surface.md` via `bun run skill:surface` + (gated by `tests/skill-ocx.test.ts`). +- MODIFY `docs-site/` provider/account docs (English source; do not let locales contradict). + +## Poll budget (audit blocker 2) + +The device grant lives 15 minutes, but both existing poll budgets stop at five: +Codex-auth polls 150 x 2s and then records an error (`src/codex/auth-api.ts:2214,2414`), +and the CLI independently stops at the same 150 x 2s (`src/cli/account-auth.ts:123`). +Shipping the grant without widening these would advertise a 15-minute window that +dies at minute five — exactly the headless case this feature exists for, where the +operator walks to another device to enter the code. + +Both budgets are raised for the device flow, and a test proves a login completing +after minute five still succeeds (fake timers; no real waiting). + +## Security review gate (audit finding 4) + +`src/oauth/`, `src/codex/auth-api.ts`, and `src/cli/account-auth.ts` are restricted +authentication surfaces (`.github/scripts/pr-sponsored-surface.cjs:24`) and require +explicit security review per `MAINTAINERS.md:60`. Both deviceauth PRs carry that +requirement in their description; neither is merged as routine. + +Explicitly NOT done: repurposing `--code` as device user-code input. The device user +code is entered at `auth.openai.com`, while `account code` submits callback +authorization material to a different endpoint (`src/codex/auth-api.ts:2457-2472`). +Conflating them would silently break the existing paste fallback. + +## Tests + +- `tests/codex-auth-api.test.ts`: device login returns `deviceCode` and does not open a URL. +- `tests/cli-account.test.ts`: `--device` prints URL + device code + flow id; `--no-wait --json` preserves it. +- `tests/codex-auth-api.test.ts`: a device login that completes after minute five still succeeds. +- `tests/cli-account.test.ts`: with fake timers, a normal (polling) `--device` login completes + after minute five. The `--no-wait` case bypasses polling and does not cover this. +- `gui/tests/add-codex-account-device.test.tsx`: the start request carries `device: true`, + and the waiting step renders the device code and verification URL. + +Focused: `bun test tests/codex-auth-api.test.ts tests/cli-account.test.ts tests/skill-ocx.test.ts` +plus the single focused GUI test file. No repository-wide suite. diff --git a/devlog/_plan/260904_bug_stack_train/030_wp4_bug_carry.md b/devlog/_plan/260904_bug_stack_train/030_wp4_bug_carry.md new file mode 100644 index 0000000000..3e4294bd48 --- /dev/null +++ b/devlog/_plan/260904_bug_stack_train/030_wp4_bug_carry.md @@ -0,0 +1,39 @@ +# 030 — wp4: carried contributor fixes (PARALLEL, not stacked) + +Corrected after plan audit. These were originally drafted as a third stack layer above +deviceauth. That was wrong: none of the four consumes deviceauth and none consumes +another, so stacking them would impose a false merge order. DEV-STACK-01 says +independent parts open as parallel PRs off trunk, and DEV-STACK-03 says one thesis per +layer — four unrelated theses in one layer violates both. + +Each fix therefore gets its own branch off `dev`, merged independently: +`codex/carry-3335`, `codex/carry-3333`, `codex/carry-3322`, `codex/carry-3357`. + +Four PRs were judged root-correct with RED-without-fix regressions. Each is carried as +its own independent PR with a `Co-authored-by` trailer in its commit, so the +contributor graph records the author (AGENTS.md; `CREDITS.md` exists because 27 +landings previously lost attribution). + +| Source PR | Author trailer | Scope | +|-----------|----------------|-------| +| #3335 | `Co-authored-by: x3M3x ` | GUI combo strategy selector: render all five | +| #3333 | `Co-authored-by: hajune ` | Models tab spacing + Combos layout stability | +| #3322 | `Co-authored-by: luvs01 <27862058+luvs01@users.noreply.github.com>` | `logs --follow` capability contract | +| #3357 | `Co-authored-by: huaiqing-afk ` | Cursor repeated-narration breaker | + +Carry method: fetch the PR head and re-apply its source/test hunks onto a fresh branch +off `dev`, one commit per source PR, each carrying its trailer. Do not pipe +`gh pr diff` straight into `git apply` for #3335 — GitHub emits binary PNG hunks +without full index data, so the whole-patch check fails on the two +`docs/pr-assets/*.png` files even though every source hunk applies cleanly. + +Focused verification, per branch — each branch runs only its own tests: + +| Branch | Command | +|--------|---------| +| `codex/carry-3335` | `cd gui && bun test tests/combo-strategy-selector.test.tsx` | +| `codex/carry-3333` | `cd gui && bun test tests/models-tab-layout.test.ts` | +| `codex/carry-3322` | `bun test tests/cli-usage-report.test.ts tests/cli-capabilities.test.ts` | +| `codex/carry-3357` | `bun test tests/cursor-repetition-breaker.test.ts` | + +No repository-wide suite (explicit user constraint). diff --git a/devlog/_plan/260904_bug_stack_train/040_deferrals.md b/devlog/_plan/260904_bug_stack_train/040_deferrals.md new file mode 100644 index 0000000000..d1e6da02b2 --- /dev/null +++ b/devlog/_plan/260904_bug_stack_train/040_deferrals.md @@ -0,0 +1,30 @@ +# 040 — Recorded deferrals + +Deferring is a disposition, not an omission. Each item below stays open with a +stated reason rather than being force-landed. + +## #3348 / #3312 — combos failover hardening + +Both carry the same confirmed correctness blocker: generic HTTP 410 and 413 are +classified as retryable hops (`src/combos/failover.ts:563-617` on both heads), so an +oversized or invalid request would be replayed to the next provider. Their own tests +encode the wrong expectation. #3348 functionally supersedes #3312 (30 shared files, +near-identical source diffs; #3312 is additionally CONFLICTING/DIRTY). + +At 2,248 lines across 34 files spanning failover, credential rotation, durable +cooldown persistence, shutdown, and the core response path, this is not reviewable +inside a mixed campaign. It needs its own split stack. + +## #3325 — dev bump guard fork filter + +The code is correct, but `.github/workflows/` is a restricted surface +(`.github/scripts/pr-sponsored-surface.cjs:24-27`) and the hygiene gate fails +`unsponsored_surface` without a maintainer sponsorship decision. That is a policy +action for a human, not a patch. Note the second red check is a cancelled +`enforce-target` run that `gh pr checks` renders as a failure. + +## All six bug issues + +See 000. Every one needs reporter evidence or a product decision. Three of them +(#3352, #3320, #3279) would require weakening an auth or identity boundary to +"fix" without a reproduction, which is the wrong trade. diff --git a/src/oauth/chatgpt-device.ts b/src/oauth/chatgpt-device.ts new file mode 100644 index 0000000000..74fbeeabd9 --- /dev/null +++ b/src/oauth/chatgpt-device.ts @@ -0,0 +1,187 @@ +import type { OAuthController, OAuthCredentials } from "./types"; +import { CHATGPT_CLIENT_ID, CHATGPT_TOKEN_URL, credsFromToken } from "./chatgpt"; + +/** + * OpenAI deviceauth (device-code) grant for the ChatGPT/Codex provider. + * + * The callback flow in `./chatgpt` needs a browser and a listener on + * localhost:1455. A hub running headless in a container or over SSH has + * neither, which left "copy the long redirect URL out of the browser error + * page" as the only way to add an account there (#3366). + * + * This is the same grant Codex CLI uses. Three steps, and the middle one is + * where it differs from RFC 8628: the poll returns an authorization code plus + * a SERVER-generated PKCE verifier, which is then spent at the ordinary token + * endpoint. We never generate the verifier ourselves here. + */ +const USERCODE_URL = "https://auth.openai.com/api/accounts/deviceauth/usercode"; +const DEVICE_TOKEN_URL = "https://auth.openai.com/api/accounts/deviceauth/token"; +const DEVICE_REDIRECT_URI = "https://auth.openai.com/deviceauth/callback"; + +/** Where the user types the short code. Fixed, and safe to show anywhere. */ +export const DEVICE_VERIFICATION_URL = "https://auth.openai.com/codex/device"; + +/** The grant's own lifetime. Polling past this only produces a worse error message. */ +const DEVICE_FLOW_TTL_MS = 15 * 60 * 1000; +const DEFAULT_POLL_INTERVAL_MS = 5_000; +const MIN_POLL_INTERVAL_MS = 1_000; +/** + * Above ~2^31 ms a timer overflows and fires immediately, which would turn a + * hostile or corrupt `interval` into a hot loop against an auth endpoint. The + * grant only lives 15 minutes, so anything longer is meaningless anyway. + */ +const MAX_POLL_INTERVAL_MS = DEVICE_FLOW_TTL_MS; + +/** + * Upstream sends `interval` as a number in some responses and a string in + * others. A string would make `setTimeout` treat it as 0 and turn the poll + * into a hot loop against an auth endpoint, so coerce and floor it. + */ +function normalizeIntervalMs(raw: unknown): number { + const seconds = typeof raw === "number" ? raw : typeof raw === "string" ? Number(raw) : NaN; + if (!Number.isFinite(seconds) || seconds <= 0) return DEFAULT_POLL_INTERVAL_MS; + const ms = Math.round(seconds * 1000); + return Math.min(MAX_POLL_INTERVAL_MS, Math.max(MIN_POLL_INTERVAL_MS, ms)); +} + +function nonEmptyString(value: unknown): string | undefined { + return typeof value === "string" && value.length > 0 ? value : undefined; +} + +async function sleep(ms: number, signal?: AbortSignal): Promise { + if (signal?.aborted) throw new Error("Login cancelled"); + await new Promise((resolve, reject) => { + const timer = setTimeout(() => { + signal?.removeEventListener("abort", onAbort); + resolve(); + }, ms); + const onAbort = (): void => { + clearTimeout(timer); + reject(new Error("Login cancelled")); + }; + signal?.addEventListener("abort", onAbort, { once: true }); + }); +} + +/** + * Device-flow errors carry the HTTP status and nothing else. + * + * The callback flow's `safeErrorDescription` reflects the upstream body into + * the message, which is fine for an OAuth error envelope but not here: these + * endpoints can echo request material, and this message reaches CLI output, + * the GUI, and issue reports. + */ +function deviceError(stage: string, status: number): Error { + return new Error(`ChatGPT device authorization ${stage} failed: HTTP ${status}`); +} + +interface DeviceUserCode { + deviceAuthId: string; + userCode: string; + intervalMs: number; +} + +async function requestUserCode(signal?: AbortSignal): Promise { + const response = await fetch(USERCODE_URL, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ client_id: CHATGPT_CLIENT_ID }), + signal, + }); + if (!response.ok) throw deviceError("request", response.status); + const payload = (await response.json()) as Record; + const deviceAuthId = nonEmptyString(payload.device_auth_id); + // Upstream accepts both spellings, so a response using the alias must not be + // rejected as malformed. + const userCode = nonEmptyString(payload.user_code) ?? nonEmptyString(payload.usercode); + if (!deviceAuthId || !userCode) { + throw new Error("ChatGPT device authorization response missing required fields"); + } + return { deviceAuthId, userCode, intervalMs: normalizeIntervalMs(payload.interval) }; +} + +interface DeviceGrant { + authorizationCode: string; + codeVerifier: string; +} + +/** + * Poll until the user finishes at the verification page. + * + * Pending is signalled by 403/404 rather than an `authorization_pending` body, + * so status is the whole protocol here: any other non-2xx is terminal, and + * treating it as pending would keep hammering a permanently failing endpoint. + */ +async function pollForGrant( + device: DeviceUserCode, + signal?: AbortSignal, +): Promise { + const deadline = Date.now() + DEVICE_FLOW_TTL_MS; + while (Date.now() < deadline) { + if (signal?.aborted) throw new Error("Login cancelled"); + const response = await fetch(DEVICE_TOKEN_URL, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ device_auth_id: device.deviceAuthId, user_code: device.userCode }), + signal, + }); + if (response.status === 403 || response.status === 404) { + // Cap the wait at the time actually left. Sleeping a full interval past + // the deadline is how a 15-minute grant turns into a 20-minute wait. + const remaining = deadline - Date.now(); + if (remaining <= 0) break; + await sleep(Math.min(device.intervalMs, remaining), signal); + continue; + } + if (!response.ok) throw deviceError("poll", response.status); + // The deadline is checked again here, not only at the top of the loop: a + // single poll can itself outlive the grant, and accepting a code that + // expired mid-flight just moves the failure to the token exchange. + if (Date.now() >= deadline) break; + const payload = (await response.json()) as Record; + const authorizationCode = nonEmptyString(payload.authorization_code); + const codeVerifier = nonEmptyString(payload.code_verifier); + if (!authorizationCode || !codeVerifier) { + throw new Error("ChatGPT device authorization response missing required fields"); + } + return { authorizationCode, codeVerifier }; + } + throw new Error("ChatGPT device authorization expired"); +} + +async function exchangeGrant(grant: DeviceGrant, signal?: AbortSignal): Promise { + const response = await fetch(CHATGPT_TOKEN_URL, { + method: "POST", + headers: { "Content-Type": "application/x-www-form-urlencoded" }, + body: new URLSearchParams({ + grant_type: "authorization_code", + client_id: CHATGPT_CLIENT_ID, + code: grant.authorizationCode, + code_verifier: grant.codeVerifier, + redirect_uri: DEVICE_REDIRECT_URI, + }).toString(), + signal, + }); + if (!response.ok) throw deviceError("token exchange", response.status); + return credsFromToken((await response.json()) as Record); +} + +/** + * Run the device flow to completion. + * + * `deviceCode` in the `onAuth` payload is the HUMAN code, matching kimi, nous, + * and github-copilot. The opaque `device_auth_id` never leaves this module: + * every device-code surface renders `deviceCode` verbatim, and the management + * login route also uses its presence to decide a flow must not be handed to a + * local browser spawn. + */ +export async function loginChatGPTDevice(ctrl: OAuthController): Promise { + const device = await requestUserCode(ctrl.signal); + ctrl.onAuth?.({ + url: DEVICE_VERIFICATION_URL, + instructions: `Enter code: ${device.userCode}`, + deviceCode: device.userCode, + }); + const grant = await pollForGrant(device, ctrl.signal); + return exchangeGrant(grant, ctrl.signal); +} diff --git a/src/oauth/chatgpt.ts b/src/oauth/chatgpt.ts index 5dd01497db..bb4d1c8497 100644 --- a/src/oauth/chatgpt.ts +++ b/src/oauth/chatgpt.ts @@ -5,6 +5,10 @@ import { generatePKCE } from "./pkce"; const CLIENT_ID = "app_EMoamEEZ73f0CkXaXp7hrann"; const AUTH_URL = "https://auth.openai.com/oauth/authorize"; const TOKEN_URL = "https://auth.openai.com/oauth/token"; + +/** Shared with the deviceauth grant in `./chatgpt-device`: same public PKCE client. */ +export const CHATGPT_CLIENT_ID = CLIENT_ID; +export const CHATGPT_TOKEN_URL = TOKEN_URL; const SCOPE = "openid profile email offline_access api.connectors.read api.connectors.invoke"; const CALLBACK_PORT = 1455; const CALLBACK_PATH = "/auth/callback"; @@ -46,9 +50,17 @@ export function extractEmail(idToken?: string, accessToken?: string): string | u return undefined; } -function credsFromToken(data: Record): OAuthCredentials { +export function credsFromToken(data: Record): OAuthCredentials { const idToken = typeof data.id_token === "string" ? data.id_token : undefined; - const accessToken = data.access_token as string; + // This parses a response from an external boundary, so the access token is + // validated rather than cast. A 200 carrying no access_token would otherwise + // resolve a login as successful with an undefined credential, which then gets + // silently declined at persistence — a success message and no account. + const accessToken = typeof data.access_token === "string" && data.access_token.length > 0 + ? data.access_token + : undefined; + if (!accessToken) throw new Error("ChatGPT token response missing access token"); + const refreshToken = typeof data.refresh_token === "string" ? data.refresh_token : ""; // ?? only guards null/undefined; NaN or a string expires_in would otherwise // produce a NaN expiry that never compares as expired, and a negative duration // would stamp an already-past expiry — both block refresh semantics. @@ -62,7 +74,7 @@ function credsFromToken(data: Record): OAuthCredentials { const expires = Number.isFinite(computedExpires) ? computedExpires : Date.now() + 3600 * 1000; return { access: accessToken, - refresh: (data.refresh_token as string) ?? "", + refresh: refreshToken, expires, accountId: extractAccountId(idToken, accessToken), email: extractEmail(idToken, accessToken), @@ -135,7 +147,22 @@ function safeErrorDescription(resp: Response): Promise { }); } -export async function loginChatGPT(ctrl: OAuthController, opts?: { forceLogin?: boolean }): Promise { +/** + * How the user proves identity. `browser` runs the localhost:1455 callback flow; + * `device` runs the deviceauth grant, which needs no local browser or listener + * and is the only workable path on a headless or remote hub (#3366). + */ +export type ChatGPTLoginFlow = "browser" | "device"; + +export async function loginChatGPT( + ctrl: OAuthController, + opts?: { forceLogin?: boolean; flow?: ChatGPTLoginFlow }, +): Promise { + if (opts?.flow === "device") { + // Imported lazily so the callback flow does not pay for a module it never uses. + const { loginChatGPTDevice } = await import("./chatgpt-device"); + return loginChatGPTDevice(ctrl); + } const flow = new ChatGPTOAuthFlow(ctrl); if (opts?.forceLogin) flow.forceLogin = true; return flow.login(); diff --git a/src/oauth/index.ts b/src/oauth/index.ts index 868c4f9b99..3ae6f4c01f 100644 --- a/src/oauth/index.ts +++ b/src/oauth/index.ts @@ -34,7 +34,7 @@ import { loginXai, refreshXaiToken, XAI_LOCAL_CLI_DETACH_WARNING, XaiTokenReques import { ANTHROPIC_OAUTH_BETA, AnthropicTokenError, loginAnthropic, refreshAnthropicToken } from "./anthropic"; import { loginKimi, refreshKimiToken } from "./kimi"; import { loginNous, NousTokenError, refreshNousToken, clearNousRefreshIntent, RefreshIntentIOError } from "./nous"; -import { loginChatGPT, refreshChatGPTToken } from "./chatgpt"; +import { loginChatGPT, refreshChatGPTToken, type ChatGPTLoginFlow } from "./chatgpt"; import { loginAntigravity, refreshAntigravityToken } from "./google-antigravity"; import { loginCursor, refreshCursorToken } from "./cursor"; import { loginGithubCopilot, refreshGithubCopilotToken, validateCopilotApiBaseUrl } from "./github-copilot"; @@ -161,7 +161,17 @@ function verdictKey(p:string,a:string,c:OAuthCredentials){return `${p}\0${a}\0${ function cached(p:string,a:string,c:OAuthCredentials,now:()=>number){const k=verdictKey(p,a,c),u=permanentRefreshFailures.get(k);if(u===undefined)return false;if(u<=now()){permanentRefreshFailures.delete(k);return false;}return true;} export function sweepExpiredXaiPermanentFailureVerdicts(now=Date.now()):number{let removed=0;for(const[key,until]of permanentRefreshFailures){if(until>now)continue;permanentRefreshFailures.delete(key);removed+=1;}return removed;} -export interface LoginOpts { forceLogin?: boolean; /** When set, persist into this account slot and require matching identity. */ reauthAccountId?: string } +export interface LoginOpts { + forceLogin?: boolean; + /** When set, persist into this account slot and require matching identity. */ + reauthAccountId?: string; + /** + * ChatGPT only: `device` selects the deviceauth grant instead of the + * localhost:1455 callback flow, for hosts with no browser or no loopback + * listener (#3366). Ignored by every other provider. + */ + flow?: ChatGPTLoginFlow; +} export interface LoginFlowLifecycle { /** Runs after background credential/config persistence settles, before status becomes done. */ @@ -282,7 +292,7 @@ export const OAUTH_PROVIDERS: Record = { defaultRefreshPolicy: "lazy-only", }, chatgpt: { - login: loginChatGPT, + login: (ctrl, opts) => loginChatGPT(ctrl, { forceLogin: opts?.forceLogin, flow: opts?.flow }), refresh: (rt) => refreshChatGPTToken(rt), providerConfig: { adapter: "openai-responses", baseUrl: "https://chatgpt.com/backend-api/codex", authMode: "forward" as const }, defaultModel: "gpt-5.4", diff --git a/src/oauth/log.ts b/src/oauth/log.ts index 2cd710d710..b6ca199e9c 100644 --- a/src/oauth/log.ts +++ b/src/oauth/log.ts @@ -23,6 +23,9 @@ const FORBIDDEN_NORMALIZED = new Set([ "oauth_code", "code_verifier", "clientsecret", + // Device-flow polling handle. Not a token, but it is the bearer of an + // in-flight authorization and must not be logged. + "device_auth_id", ]); function isForbiddenFieldKey(key: string): boolean { diff --git a/tests/chatgpt-device-auth.test.ts b/tests/chatgpt-device-auth.test.ts new file mode 100644 index 0000000000..5b7dbe7817 --- /dev/null +++ b/tests/chatgpt-device-auth.test.ts @@ -0,0 +1,246 @@ +import { afterEach, describe, expect, test } from "bun:test"; +import { loginChatGPT } from "../src/oauth/chatgpt"; +import { loginChatGPTDevice } from "../src/oauth/chatgpt-device"; +import type { OAuthController } from "../src/oauth/types"; + +/** + * The OpenAI deviceauth grant (#3366): the login path for a hub with no local + * browser and no listener on localhost:1455. + * + * Every test stubs `globalThis.fetch` and routes by URL, the same style as + * `tests/oauth-device-code-contract.test.ts`. + */ + +const realFetch = globalThis.fetch; + +afterEach(() => { + globalThis.fetch = realFetch; +}); + +const USERCODE = "https://auth.openai.com/api/accounts/deviceauth/usercode"; +const DEVICE_TOKEN = "https://auth.openai.com/api/accounts/deviceauth/token"; +const OAUTH_TOKEN = "https://auth.openai.com/oauth/token"; + +function jsonResponse(body: unknown, status = 200): Response { + return new Response(JSON.stringify(body), { + status, + headers: { "Content-Type": "application/json" }, + }); +} + +/** An id_token whose payload carries the identity the Codex pool requires. */ +function idToken(): string { + const payload = { + email: "Hub.Operator@Example.com", + "https://api.openai.com/auth": { chatgpt_account_id: "acct_device_123" }, + }; + const encode = (value: unknown): string => + Buffer.from(JSON.stringify(value)).toString("base64url"); + return `${encode({ alg: "none" })}.${encode(payload)}.sig`; +} + +interface RouteOptions { + pendingPolls?: number; + pendingStatus?: number; + interval?: unknown; + tokenBody?: unknown; + grantBody?: unknown; +} + +function routeFetch(opts: RouteOptions = {}): { urls: string[]; bodies: string[] } { + const urls: string[] = []; + const bodies: string[] = []; + let polls = 0; + globalThis.fetch = (async (input: RequestInfo | URL, init?: RequestInit) => { + const url = typeof input === "string" ? input : input instanceof URL ? input.href : input.url; + urls.push(url); + if (typeof init?.body === "string") bodies.push(init.body); + if (url === USERCODE) { + return jsonResponse({ + device_auth_id: "auth-id-opaque", + user_code: "ABCD-EFGH", + ...(opts.interval === undefined ? {} : { interval: opts.interval }), + }); + } + if (url === DEVICE_TOKEN) { + if (polls < (opts.pendingPolls ?? 0)) { + polls += 1; + return jsonResponse({}, opts.pendingStatus ?? 403); + } + return jsonResponse( + opts.grantBody ?? { authorization_code: "auth-code", code_verifier: "server-verifier" }, + ); + } + if (url === OAUTH_TOKEN) { + return jsonResponse( + opts.tokenBody ?? { + access_token: "access-value", + refresh_token: "refresh-value", + id_token: idToken(), + expires_in: 3600, + }, + ); + } + throw new Error(`unexpected fetch: ${url}`); + }) as typeof fetch; + return { urls, bodies }; +} + +describe("ChatGPT device auth", () => { + test("surfaces the fixed verification URL and the human code", async () => { + routeFetch(); + let seen: { url?: string; instructions?: string; deviceCode?: string } | undefined; + await loginChatGPTDevice({ onAuth: info => { seen = info; } }); + + expect(seen?.url).toBe("https://auth.openai.com/codex/device"); + expect(seen?.deviceCode).toBe("ABCD-EFGH"); + expect(seen?.instructions).toContain("ABCD-EFGH"); + // The opaque polling handle must never reach a rendered surface. + expect(JSON.stringify(seen)).not.toContain("auth-id-opaque"); + }); + + test.each([403, 404])("treats %i as pending and keeps polling", async status => { + // interval 0.0001s floors to the 1s minimum, so two pending polls would + // sleep two real seconds. Assert the wait instead of paying for it. + const calls = routeFetch({ pendingPolls: 2, pendingStatus: status, interval: 0.001 }); + const started = Date.now(); + const creds = await loginChatGPTDevice({}); + + expect(calls.urls.filter(url => url === DEVICE_TOKEN)).toHaveLength(3); + expect(creds.access).toBe("access-value"); + // Two pending polls at the 1s floor: proves the interval is honored rather + // than collapsed to an immediate retry. + expect(Date.now() - started).toBeGreaterThanOrEqual(1_900); + }); + + test("does not accept a grant that arrives after the deadline", async () => { + const realNow = Date.now; + let clock = realNow(); + Date.now = () => clock; + try { + globalThis.fetch = (async (input: RequestInfo | URL) => { + const url = typeof input === "string" ? input : input instanceof URL ? input.href : input.url; + if (url === USERCODE) { + return jsonResponse({ device_auth_id: "auth-id-opaque", user_code: "ABCD-EFGH" }); + } + if (url === DEVICE_TOKEN) { + // The poll itself outlives the 15-minute grant. + clock += 15 * 60 * 1000 + 1; + return jsonResponse({ authorization_code: "auth-code", code_verifier: "server-verifier" }); + } + throw new Error("token exchange must not be reached"); + }) as typeof fetch; + + await expect(loginChatGPTDevice({})).rejects.toThrow("expired"); + } finally { + Date.now = realNow; + } + }); + + test("accepts the upstream 'usercode' spelling", async () => { + globalThis.fetch = (async (input: RequestInfo | URL) => { + const url = typeof input === "string" ? input : input instanceof URL ? input.href : input.url; + if (url === USERCODE) { + return jsonResponse({ device_auth_id: "auth-id-opaque", usercode: "WXYZ-1234" }); + } + if (url === DEVICE_TOKEN) { + return jsonResponse({ authorization_code: "auth-code", code_verifier: "server-verifier" }); + } + return jsonResponse({ access_token: "access-value", expires_in: 3600 }); + }) as typeof fetch; + + let seen: { deviceCode?: string } | undefined; + await loginChatGPTDevice({ onAuth: info => { seen = info; } }); + expect(seen?.deviceCode).toBe("WXYZ-1234"); + }); + + test("coerces a string interval and clamps an overflowing one", async () => { + // A raw setTimeout above ~2^31 ms fires immediately, which would turn a + // corrupt interval into a hot loop against an auth endpoint. + const calls = routeFetch({ pendingPolls: 3, interval: "999999999" }); + const started = Date.now(); + const abort = new AbortController(); + setTimeout(() => abort.abort("stop"), 60); + + await loginChatGPTDevice({ signal: abort.signal }).catch(() => {}); + + expect(Date.now() - started).toBeLessThan(5_000); + // One poll, then a long clamped wait — not a spin. + expect(calls.urls.filter(url => url === DEVICE_TOKEN).length).toBeLessThanOrEqual(2); + }); + + test("rejects a token response with no access token", async () => { + routeFetch({ tokenBody: { refresh_token: "refresh-value", expires_in: 3600 } }); + + await expect(loginChatGPTDevice({})).rejects.toThrow( + "ChatGPT token response missing access token", + ); + }); + + test("exchanges the server-issued grant at the device callback URI", async () => { + const calls = routeFetch(); + await loginChatGPTDevice({}); + + const exchange = calls.bodies.find(body => body.includes("grant_type=authorization_code")); + expect(exchange).toBeDefined(); + const params = new URLSearchParams(exchange ?? ""); + expect(params.get("code")).toBe("auth-code"); + // The verifier comes from the poll response; we never generate one here. + expect(params.get("code_verifier")).toBe("server-verifier"); + expect(params.get("redirect_uri")).toBe("https://auth.openai.com/deviceauth/callback"); + }); + + test("carries the account identity the Codex pool requires", async () => { + routeFetch(); + const creds = await loginChatGPTDevice({}); + + // A credential with no accountId is rejected at pool admission, so wire + // success alone would not prove the flow is usable. + expect(creds.accountId).toBe("acct_device_123"); + expect(creds.email).toBe("hub.operator@example.com"); + expect(creds.refresh).toBe("refresh-value"); + }); + + test("rejects a malformed grant without reflecting the response body", async () => { + routeFetch({ grantBody: { authorization_code: "auth-code" } }); + + await expect(loginChatGPTDevice({})).rejects.toThrow( + "ChatGPT device authorization response missing required fields", + ); + }); + + test("reports a terminal poll failure by status only", async () => { + globalThis.fetch = (async (input: RequestInfo | URL) => { + const url = typeof input === "string" ? input : input instanceof URL ? input.href : input.url; + if (url === USERCODE) { + return jsonResponse({ device_auth_id: "auth-id-opaque", user_code: "ABCD-EFGH" }); + } + return new Response("upstream said something with a secret in it", { status: 500 }); + }) as typeof fetch; + + const error = await loginChatGPTDevice({}).catch((err: Error) => err); + expect(String(error)).toContain("HTTP 500"); + expect(String(error)).not.toContain("secret"); + }); + + test("stops when the controller aborts", async () => { + routeFetch({ pendingPolls: 50, interval: 0.001 }); + const abort = new AbortController(); + const ctrl: OAuthController = { + onAuth: () => abort.abort("observed"), + signal: abort.signal, + }; + + await expect(loginChatGPTDevice(ctrl)).rejects.toThrow(/cancelled|abort/i); + }); + + test("loginChatGPT routes flow:device to the device grant", async () => { + const calls = routeFetch(); + // No callback server is started: reaching the usercode endpoint at all + // proves the browser flow was not selected. + const creds = await loginChatGPT({}, { flow: "device" }); + + expect(calls.urls[0]).toBe(USERCODE); + expect(creds.accountId).toBe("acct_device_123"); + }); +}); From 8a0c1086539b82648984e0a1c3546d9d493d5fd9 Mon Sep 17 00:00:00 2001 From: JUN Date: Fri, 4 Sep 2026 02:28:16 +0900 Subject: [PATCH 12/49] fix(cli): stop claiming logs --follow implies JSONL (#3372) Carried from #3322 onto current dev. Independent of the other carried fixes, so it ships as its own PR rather than a stack layer. Co-authored-by: jun Co-authored-by: luvs01 <27862058+luvs01@users.noreply.github.com> --- .../ocx/references/01_management_surface.md | 2 +- src/cli/capabilities.ts | 2 +- src/cli/observe.ts | 4 +- tests/cli-capabilities.test.ts | 6 ++ tests/cli-usage-report.test.ts | 62 ++++++++++++++++++- 5 files changed, 72 insertions(+), 4 deletions(-) diff --git a/skills/ocx/references/01_management_surface.md b/skills/ocx/references/01_management_surface.md index 4162ca9de4..36438d2f68 100644 --- a/skills/ocx/references/01_management_surface.md +++ b/skills/ocx/references/01_management_surface.md @@ -124,7 +124,7 @@ Recent request log rows, filterable by provider, model, conversation, and status | `--conversation` | string | Restrict to one conversation id (`--conversationId` is accepted too). | | `--status` | string | An exact code (429) or a class (5xx). | | `--limit` | number | Row cap; defaults to 200. | -| `--follow` | boolean | Stream new rows as JSONL; implies --jsonl. | +| `--follow` | boolean | Poll for new rows; add --jsonl to emit JSONL. | | `--json` | boolean | Emit the server payload as JSON. | | `--jsonl` | boolean | Emit one row per line. | diff --git a/src/cli/capabilities.ts b/src/cli/capabilities.ts index d124af2f6e..d4ae958829 100644 --- a/src/cli/capabilities.ts +++ b/src/cli/capabilities.ts @@ -274,7 +274,7 @@ export const CAPABILITIES: readonly Capability[] = [ { name: "--conversation", value: "string", summary: "Restrict to one conversation id (`--conversationId` is accepted too)." }, { name: "--status", value: "string", summary: "An exact code (429) or a class (5xx)." }, { name: "--limit", value: "number", summary: "Row cap; defaults to 200." }, - { name: "--follow", value: "boolean", summary: "Stream new rows as JSONL; implies --jsonl." }, + { name: "--follow", value: "boolean", summary: "Poll for new rows; add --jsonl to emit JSONL." }, { name: "--json", value: "boolean", summary: "Emit the server payload as JSON." }, { name: "--jsonl", value: "boolean", summary: "Emit one row per line." }, ], diff --git a/src/cli/observe.ts b/src/cli/observe.ts index e89eea0de3..46e264d2a8 100644 --- a/src/cli/observe.ts +++ b/src/cli/observe.ts @@ -72,7 +72,9 @@ async function logs(argv: string[], deps: RuntimeApiDeps): Promise { const limit = takeIntegerOption(args, "--limit", { min: 1 }) ?? 200; rejectArgs(args, USAGE); if (wantsJson && wantsJsonl) throw new CliUsageError("--json and --jsonl cannot be combined", USAGE); - if (follow && wantsJson) throw new CliUsageError("--follow uses --jsonl, not --json", USAGE); + if (follow && wantsJson) { + throw new CliUsageError("--follow cannot be combined with --json; use --jsonl for streaming JSONL", USAGE); + } let seen = new Set(); do { const data = await runtimeRequest(`/api/logs${query({ provider, model, status, conversationId, limit })}`, {}, deps); diff --git a/tests/cli-capabilities.test.ts b/tests/cli-capabilities.test.ts index 262e4e6fcd..e8584cc839 100644 --- a/tests/cli-capabilities.test.ts +++ b/tests/cli-capabilities.test.ts @@ -74,6 +74,12 @@ describe("capability table is a leaf data module", () => { expect(CAPABILITIES.some(c => c.command[0] === "capabilities")).toBe(true); }); + test("logs follow does not claim to imply JSONL output", () => { + const logs = CAPABILITIES.find(c => c.command.length === 1 && c.command[0] === "logs"); + const follow = logs?.flags.find(flag => flag.name === "--follow"); + expect(follow?.summary).toBe("Poll for new rows; add --jsonl to emit JSONL."); + }); + test("the check-only Codex CLI updater is declared as a local read capability", () => { const cap = CAPABILITIES.find(c => c.command.join(" ") === "system codex-cli-update check"); expect(cap).toBeDefined(); diff --git a/tests/cli-usage-report.test.ts b/tests/cli-usage-report.test.ts index 92933843aa..d446533f0f 100644 --- a/tests/cli-usage-report.test.ts +++ b/tests/cli-usage-report.test.ts @@ -6,7 +6,7 @@ * cost the server computes was discarded before reaching the terminal. These * tests pin the cost down where a user can see it. */ -import { describe, expect, test } from "bun:test"; +import { describe, expect, spyOn, test } from "bun:test"; import { handleObserveCommand } from "../src/cli/observe"; import { formatUsageReport } from "../src/cli/usage-report"; @@ -222,3 +222,63 @@ describe("ocx logs --conversation", () => { expect(out).not.toContain("conv="); }); }); + +describe("ocx logs --follow output contract", () => { + test("--follow --json names the conflict without implying that follow enables JSONL", async () => { + const errors: string[] = []; + const originalError = console.error; + console.error = (...args: unknown[]) => { errors.push(args.map(String).join(" ")); }; + try { + const code = await handleObserveCommand( + ["logs", "--follow", "--json"], + { baseUrl: "http://cli.test", fetchImpl: async () => new Response("[]") }, + ); + expect(code).toBe(2); + expect(errors.join("\n")) + .toContain("--follow cannot be combined with --json; use --jsonl for streaming JSONL"); + } finally { + console.error = originalError; + } + }); + + test("--follow alone keeps human-readable rows", async () => { + const rows = [{ + id: "row-1", + timestamp: "t0", + status: 200, + provider: "xai", + model: "grok-4.6", + durationMs: 12, + conversationId: "conv-7", + }]; + const lines: string[] = []; + const errors: string[] = []; + const originalLog = console.log; + const originalError = console.error; + const sleep = spyOn(Bun, "sleep").mockImplementation(async () => { + throw new Error("stop after first follow poll"); + }); + console.log = (...args: unknown[]) => { lines.push(args.map(String).join(" ")); }; + console.error = (...args: unknown[]) => { errors.push(args.map(String).join(" ")); }; + try { + const code = await handleObserveCommand( + ["logs", "--follow"], + { + baseUrl: "http://cli.test", + fetchImpl: async () => new Response(JSON.stringify(rows), { + status: 200, + headers: { "content-type": "application/json" }, + }), + }, + ); + expect(code).toBe(1); + expect(lines).toEqual(["t0 200 xai/grok-4.6 12ms conv=conv-7"]); + expect(lines[0]?.startsWith("{")).toBe(false); + expect(errors.join("\n")).toContain("stop after first follow poll"); + } finally { + console.log = originalLog; + console.error = originalError; + sleep.mockRestore(); + } + }); +}); From 1c6243f46be68ab495f5ea3e58bb6d6132c8c8f5 Mon Sep 17 00:00:00 2001 From: jun Date: Fri, 4 Sep 2026 02:31:29 +0900 Subject: [PATCH 13/49] docs(devlog): record the PR and issue drawdown ledger --- .../080_drawdown_ledger.md | 101 ++++++++++++++++++ 1 file changed, 101 insertions(+) create mode 100644 devlog/_plan/260904_repo_hygiene_campaign/080_drawdown_ledger.md diff --git a/devlog/_plan/260904_repo_hygiene_campaign/080_drawdown_ledger.md b/devlog/_plan/260904_repo_hygiene_campaign/080_drawdown_ledger.md new file mode 100644 index 0000000000..866ff8f582 --- /dev/null +++ b/devlog/_plan/260904_repo_hygiene_campaign/080_drawdown_ledger.md @@ -0,0 +1,101 @@ +# 080 — PR and issue drawdown ledger + +## What the evidence actually showed + +The campaign assumed a backlog full of superseded work. It was not. Running the +content-landing test against all 53 open PR heads found exactly one fully landed +(#3367, which merged during the campaign) and one mostly landed (#2877, a devlog +PR). Re-running it across 35 contributor PRs returned **0% landed for every +single one** — 68 files unlanded on #1645, 95 on #2462, 65 on #2113, and so on. + +That is the finding, not a failure to find one: this backlog is not stale, it is +unreviewed. Closing those PRs as "superseded" would have destroyed real work and +told 30-odd contributors their submissions were duplicates when they were not. + +## Overlap that looked like duplication and was not + +A file-overlap pass surfaced 35 PR pairs sharing ≥30% of their files. Nearly all +were false positives of two kinds: + +- **Intentional stacks.** #3340 → #3349 → #3350 (@Flowershangfromthebranches) is + a declared 3-PR stack; each says so and names the commit that is uniquely its + own. #3365 and #3370 target their parent's head branch, which is the + documented stacked-PR workflow, not a duplicate. +- **Shared surface.** Ten GUI PRs touch `gui/src/pages/Models.tsx` and the i18n + bundles because that is where GUI work lives. Co-editing a file is not + supersession. + +One real supersession existed: #3312 and #3348, same author, same 30 source +files, v2 versus v4 of one failover audit. #3348 fixes a cooldown key that v2 +derives from a positional pool id — a correctness bug, not a style change — so +the older PR was closed toward the newer one with that diff quoted. + +## Issues + +| Verdict | Count | +|---|---| +| SUPERSEDED — closed, implementation cited | 3 | +| PARTIAL — closed into a consolidated issue | 11 | +| LIVE — left open | 24 | +| STALE-NOINFO — left open, specific request posted | 7 | + +Closed as implemented: #1572 (policy fallback, cd7ea8a88 + 457c33675), #2288 +(remote hub, 91a4f6c40), #3158 (four P2 follow-ups, eceb02d9d + 0d8147c20). + +### Consolidated issues + +| New | Absorbs | Theme | +|---|---|---| +| #3375 | #695, #1062, #1977, #2275 | OAuth account-pool lifecycle | +| #3376 | #2344, #2874, #2969 | quota history and reset windows | +| #3377 | #3268, #3271, #3281 | per-model capability declarations | +| #3378 | #3344, #3362 | OpenCode Go wire contract | +| #3379 | #2399, #2748, #3017 | dashboard management gaps | + +Each consolidated issue states the surviving requirement in its own words, cites +the code that proves what already shipped, links every absorbed issue, and names +every original reporter. Each closure comment credits its reporter, says what +landed and what did not, and invites correction on the new issue. Where an +absorbed issue has an open PR against it (#2973, #3282), the closure says +explicitly that the PR is not superseded. + +The seven STALE-NOINFO issues were not closed. Each got a comment saying where +the code stands and naming the one artifact that would unblock it. Closing a +report because the reporter has not answered yet is how a project stops +receiving reports. + +## An attribution defect found in the carry PRs + +Four maintainer PRs (#3371–#3374) carry contributor work. Three name their +author in a linked `Co-authored-by` trailer. #3374 carried @blackjune67's #3333 +with: + +``` +Co-authored-by: hajune +``` + +That is the git identity on the contributor's commits, but GitHub matches +co-authors by **account-linked** email, so this trailer credits nobody — +@blackjune67 would not appear on the contributor graph for their own patch. The +description now carries +`blackjune67 <46661504+blackjune67@users.noreply.github.com>`, and the PR has a +comment telling whoever squashes it to keep that exact trailer. + +This is the failure mode `CREDITS.md` exists to repair, caught before the merge +rather than after. `missing_coauthor_credit` in +`.github/scripts/pr-carry-attribution.cjs` verifies a trailer is *present*; it +cannot tell that a present trailer points at an unlinked identity. Worth +tightening, and recorded here rather than fixed silently. + +## Final counts + +| Surface | Before | After | +|---|---|---| +| Local branches | 241 | 171 | +| Remote branches | 61 | 59 | +| Open PRs | 53 | 56 | +| Open issues | 45 | 32 | + +Open PRs rose because four carry PRs and two stacked PRs were opened by other +work during the campaign; one PR (#3312) was closed by it. Issues fell by 13 +with 5 consolidated issues created — net 14 closed. From 53a2adfc45ed18a980355abe353ec02f06f3f39e Mon Sep 17 00:00:00 2001 From: JUN Date: Fri, 4 Sep 2026 02:31:52 +0900 Subject: [PATCH 14/49] fix(cursor): break repeated narration across tool cycles (#3371) Carried from #3357 onto current dev. Independent of the other carried fixes, so it ships as its own PR rather than a stack layer. Co-authored-by: jun Co-authored-by: huaiqing-afk --- src/adapters/cursor/protobuf-request.ts | 62 ++++++++++++++------- tests/cursor-repetition-breaker.test.ts | 74 +++++++++++++++++++++++++ 2 files changed, 115 insertions(+), 21 deletions(-) diff --git a/src/adapters/cursor/protobuf-request.ts b/src/adapters/cursor/protobuf-request.ts index 3efe36ecb3..f3dec71d78 100644 --- a/src/adapters/cursor/protobuf-request.ts +++ b/src/adapters/cursor/protobuf-request.ts @@ -298,38 +298,40 @@ function rootPromptMessages( // Repetition breaker (devlog 260826 gap-9): external full-replay flattens history to text, // so N identical assistant/tool-result rounds replay as N identical lines and PRIME the model // to emit the same line again (self-reinforcing loop: S2a 180x, identical-probe repetition). - // Collapse consecutive duplicates into one entry + a count marker, and count collapses so a - // strategy-change note can be appended when the pattern is severe. - let lastReplayText: string | undefined; - let lastReplayEntry: RootBlobCandidate | undefined; - let collapsedRepeats = 0; + // Collapse consecutive same-role duplicates within one user turn into one entry + a count marker. + // Track assistant narration separately from tool results so a real narration→tool→result cycle + // cannot reset the breaker before the next identical narration arrives. + const replayRuns = new Map(); + const toolCallCounts = new Map(); let maxRunLength = 1; - let currentRun = 1; + let maxToolCallCount = 1; const pushDeduped = ( payload: { role: string; content: [{ type: "text"; text: string }] }, role: RootBlobCandidate["role"], opts: { messageIndex: number; text?: string }, normalized: string, ): void => { - if (externalModel && lastReplayText !== undefined && normalized === lastReplayText && lastReplayEntry) { - collapsedRepeats++; - currentRun++; - if (currentRun > maxRunLength) maxRunLength = currentRun; - const marked = `${normalized}\n[note: this exact output was produced ${currentRun} times in a row]`; + const previous = replayRuns.get(role); + if (externalModel && previous?.text === normalized) { + const runLength = previous.length + 1; + if (runLength > maxRunLength) maxRunLength = runLength; + const marked = `${normalized}\n[note: this exact output was produced ${runLength} times in a row]`; const replacement = rootBlobCandidate( { role: payload.role, content: [{ type: "text", text: marked }] }, role, - opts, + { ...opts, messageIndex: previous.entry.messageIndex ?? opts.messageIndex }, ); - entries[entries.indexOf(lastReplayEntry)] = replacement; - lastReplayEntry = replacement; + entries[entries.indexOf(previous.entry)] = replacement; + replayRuns.set(role, { text: normalized, entry: replacement, length: runLength }); return; } - currentRun = 1; const entry = rootBlobCandidate(payload, role, opts); entries.push(entry); - lastReplayText = normalized; - lastReplayEntry = entry; + replayRuns.set(role, { text: normalized, entry, length: 1 }); }; for (let i = 0; i < messages.length; i++) { @@ -337,14 +339,13 @@ function rootPromptMessages( const message = messages[i]; if (!message) continue; if (message.role === "user" || message.role === "developer") { + replayRuns.clear(); + toolCallCounts.clear(); const text = historyContentText(message).trim(); // Cursor root replay expects OpenAI-style content parts for historical user messages. // A bare string survives blob hydration but external workers reject the completed replay // before tokenization (`usedTokens: 0`, then invalid_argument). if (text.length > 0) { - lastReplayText = undefined; - lastReplayEntry = undefined; - currentRun = 1; entries.push(rootBlobCandidate({ role: "user", content: [{ type: "text", text }], @@ -362,6 +363,20 @@ function rootPromptMessages( text, ); } + if (externalModel && Array.isArray(message.content)) { + const callsInMessage = new Set(); + for (const part of message.content) { + if (part.type !== "toolCall") continue; + const args = serializeToolCallArguments(part.arguments); + if (args === undefined) continue; + callsInMessage.add(JSON.stringify([namespacedToolName(part.namespace, part.name), args])); + } + for (const call of callsInMessage) { + const count = (toolCallCounts.get(call) ?? 0) + 1; + toolCallCounts.set(call, count); + if (count > maxToolCallCount) maxToolCallCount = count; + } + } // Assistant tool CALLS are NOT replayed as a separate visible "[Tool Call]" entry: a model // few-shot-mimics that marker and emits later tool calls as inert text (363-B guard in // tests/cursor-tool-continuation.test.ts). The invocation is instead named INSIDE the paired @@ -382,7 +397,12 @@ function rootPromptMessages( } } // Severe repetition: tell the model ONCE, imperatively, to change strategy. - if (externalModel && maxRunLength >= 3) { + if (externalModel && maxToolCallCount >= 3) { + entries.push(rootBlobCandidate({ + role: "user", + content: [{ type: "text", text: `[context note] The transcript above contains the same tool call repeated ${maxToolCallCount} times in this user turn. Repeating it again is a failure. Take a DIFFERENT action now, or state plainly what is blocking progress.` }], + }, "user", {})); + } else if (externalModel && maxRunLength >= 3) { entries.push(rootBlobCandidate({ role: "user", content: [{ type: "text", text: `[context note] The transcript above contains the same output repeated ${maxRunLength} times in a row. Repeating it again is a failure. Take a DIFFERENT action now, or state plainly what is blocking progress.` }], diff --git a/tests/cursor-repetition-breaker.test.ts b/tests/cursor-repetition-breaker.test.ts index 63708f7a32..bba00b39e9 100644 --- a/tests/cursor-repetition-breaker.test.ts +++ b/tests/cursor-repetition-breaker.test.ts @@ -41,6 +41,30 @@ function repeatedHistory(times: number): OcxMessage[] { return messages; } +function repeatedToolHistory(times: number): OcxMessage[] { + const messages: OcxMessage[] = [{ role: "user", content: "熟悉一下当前项目", timestamp: 1 }]; + for (let i = 0; i < times; i++) { + const callId = `call_${i}`; + messages.push({ + role: "assistant", + content: [ + { type: "text", text: REPEAT }, + { type: "toolCall", id: callId, name: "exec_command", arguments: { cmd: `echo ${i}` } }, + ], + timestamp: 2 + i * 2, + }); + messages.push({ + role: "toolResult", + toolCallId: callId, + toolName: "exec_command", + content: `result ${i}`, + isError: false, + timestamp: 3 + i * 2, + }); + } + return messages; +} + function encode(messages: OcxMessage[], modelId = "grok-4.6-high") { return encodeCursorRunRequest({ modelId, @@ -71,6 +95,16 @@ describe("cursor external-replay repetition breaker (devlog 260826 gap-9)", () = expect(texts.filter(text => text.includes("Take a DIFFERENT action now"))).toHaveLength(0); }); + test("repeated assistant narration across tool-result cycles still trips the breaker", () => { + const bytes = encode(repeatedToolHistory(4)); + const texts = rootTexts(bytes); + const repeats = texts.filter(text => text.startsWith(REPEAT)); + expect(repeats).toHaveLength(1); + expect(repeats[0]).toContain("4 times in a row"); + expect(texts.filter(text => text.startsWith("[Tool Result]"))).toHaveLength(4); + expect(texts.filter(text => text.includes("Take a DIFFERENT action now"))).toHaveLength(1); + }); + test("distinct assistant entries stay untouched", () => { const messages: OcxMessage[] = [ { role: "user", content: "hi", timestamp: 1 }, @@ -95,4 +129,44 @@ describe("cursor external-replay repetition breaker (devlog 260826 gap-9)", () = const texts = rootTexts(encode(messages)); expect(texts.filter(text => text === REPEAT)).toHaveLength(2); }); + + test("empty user boundaries still reset repetition tracking", () => { + const messages: OcxMessage[] = [ + { role: "user", content: "go", timestamp: 1 }, + { role: "assistant", content: REPEAT, timestamp: 2 }, + { role: "user", content: " ", timestamp: 3 }, + { role: "assistant", content: REPEAT, timestamp: 4 }, + { role: "user", content: "final", timestamp: 5 }, + ] as OcxMessage[]; + const texts = rootTexts(encode(messages)); + expect(texts.filter(text => text === REPEAT)).toHaveLength(2); + }); + + test("three identical tool calls with changing narration trigger a strategy change", () => { + const messages: OcxMessage[] = [{ role: "user", content: "find the i18n bug", timestamp: 1 }]; + for (let i = 0; i < 3; i++) { + const callId = `view_${i}`; + messages.push({ + role: "assistant", + content: [ + { type: "text", text: `investigation step ${i}` }, + { type: "toolCall", id: callId, name: "view_image", arguments: { path: "C:\\tmp\\same.png" } }, + ], + timestamp: 2 + i * 2, + }); + messages.push({ + role: "toolResult", + toolCallId: callId, + toolName: "view_image", + content: `viewed image ${i}`, + isError: false, + timestamp: 3 + i * 2, + }); + } + + const texts = rootTexts(encode(messages)); + expect(texts.filter(text => text.startsWith("investigation step"))).toHaveLength(3); + expect(texts.filter(text => text.startsWith("[Tool Result]"))).toHaveLength(3); + expect(texts.filter(text => text.includes("same tool call repeated 3 times"))).toHaveLength(1); + }); }); From d753fa53bec651c90e538602a56d1a1cddf56589 Mon Sep 17 00:00:00 2001 From: JUN Date: Fri, 4 Sep 2026 02:32:00 +0900 Subject: [PATCH 15/49] fix(gui): expose all combo strategies in selector (#3373) Carried from #3335 onto current dev. Independent of the other carried fixes, so it ships as its own PR rather than a stack layer. Co-authored-by: jun Co-authored-by: x3M3x --- .../combo-strategy-selector-after.png | Bin 0 -> 22628 bytes .../combo-strategy-selector-before.png | Bin 0 -> 75284 bytes .../components/combo-workspace-controls.tsx | 20 ++--------- gui/tests/combo-strategy-selector.test.tsx | 31 ++++++++++++++++++ 4 files changed, 34 insertions(+), 17 deletions(-) create mode 100644 docs/pr-assets/combo-strategy-selector-after.png create mode 100644 docs/pr-assets/combo-strategy-selector-before.png create mode 100644 gui/tests/combo-strategy-selector.test.tsx diff --git a/docs/pr-assets/combo-strategy-selector-after.png b/docs/pr-assets/combo-strategy-selector-after.png new file mode 100644 index 0000000000000000000000000000000000000000..502bd0cc3a7a8aa90bf98eca4ed459f9e99875b2 GIT binary patch literal 22628 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 DJb;GRuW@-`%h6TC5<^1vot-`>j2-qF>t z((ChpWcdET1)TkdMdg6ac!7-r?&UcSRSmJwlJaP0n_wz0yo;%HRiGaU{&dz+h z!0j@^mWt@q!HH#bBO3&r7S7%LRSV0PQM()>52g;5`e*>vD|t;mA8yG(Y?t^HMKTF&hK;_@*rX3~ z$9A$|_Ttynq?nxeBo}0us-S9!XEF~gsv9Gav=t1<-?|2cNEhFk1OSSKWJzLxXWG4H ztO1skSQ$Fg`M)jJUoezkBJd>wUn21T9)VY@N&u{`GIY2Um;%wVKYA3XshbRzGIgEd O2yk=Wx2pyTO8OVb&v>N( literal 0 HcmV?d00001 diff --git a/docs/pr-assets/combo-strategy-selector-before.png b/docs/pr-assets/combo-strategy-selector-before.png new file mode 100644 index 0000000000000000000000000000000000000000..77227ce9ceb1a2641909dafd20ebf69ca43a0c23 GIT binary patch literal 75284 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 literal 0 HcmV?d00001 diff --git a/gui/src/components/combo-workspace-controls.tsx b/gui/src/components/combo-workspace-controls.tsx index 7cafea176f..8e4a8108bc 100644 --- a/gui/src/components/combo-workspace-controls.tsx +++ b/gui/src/components/combo-workspace-controls.tsx @@ -1,7 +1,7 @@ import { useState } from "react"; import type { ComboEffort, ComboStrategy, ComboTarget, ProviderQuotaStates } from "../combo-workspace-data"; import { comboImagesSupported } from "../combo-capabilities"; -import { COMBO_EFFORTS, COMBO_STRATEGY_LABEL_KEYS, newComboTarget } from "../combo-workspace-data"; +import { COMBO_EFFORTS, COMBO_STRATEGIES, COMBO_STRATEGY_LABEL_KEYS, newComboTarget } from "../combo-workspace-data"; import { IconArrowDown, IconArrowUp, IconGrip, IconPlus, IconTrash } from "../icons"; import { useT } from "../i18n/shared"; import { Switch } from "../ui"; @@ -21,10 +21,7 @@ export function StrategySeg({ const t = useT(); return (

); } diff --git a/gui/tests/combo-strategy-selector.test.tsx b/gui/tests/combo-strategy-selector.test.tsx new file mode 100644 index 0000000000..8bfe0c541f --- /dev/null +++ b/gui/tests/combo-strategy-selector.test.tsx @@ -0,0 +1,31 @@ +import { afterEach, beforeEach, expect, test } from "bun:test"; +import { renderToStaticMarkup } from "react-dom/server"; +import { StrategySeg } from "../src/components/combo-workspace-controls"; +import { LanguageProvider } from "../src/i18n/provider"; + +let previousLanguage: unknown; + +beforeEach(() => { + previousLanguage = (globalThis.navigator as { language?: unknown } | undefined)?.language; + Object.defineProperty(globalThis.navigator, "language", { configurable: true, value: "en-US" }); +}); + +afterEach(() => { + Object.defineProperty(globalThis.navigator, "language", { configurable: true, value: previousLanguage }); +}); + +test("combo strategy selector exposes all runtime strategies", () => { + const html = renderToStaticMarkup( + + {}} /> + , + ); + const radios = html.match(/]*role="radio"[^>]*>/g) ?? []; + expect(radios).toHaveLength(5); + expect(html).toContain("Failover"); + expect(html).toContain("Round-robin"); + expect(html).toContain("Random"); + expect(html).toContain("Least-used"); + expect(html).toContain("Reset-window"); + expect(radios.every((button) => !button.includes("disabled="))).toBe(true); +}); From 883e3f7cd54aa4599ec3616146eb06efce246c68 Mon Sep 17 00:00:00 2001 From: jun Date: Fri, 4 Sep 2026 02:32:20 +0900 Subject: [PATCH 16/49] docs(devlog): close out the 260904 repository hygiene campaign --- .../090_closeout.md | 68 +++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 devlog/_plan/260904_repo_hygiene_campaign/090_closeout.md diff --git a/devlog/_plan/260904_repo_hygiene_campaign/090_closeout.md b/devlog/_plan/260904_repo_hygiene_campaign/090_closeout.md new file mode 100644 index 0000000000..80b0c22cd7 --- /dev/null +++ b/devlog/_plan/260904_repo_hygiene_campaign/090_closeout.md @@ -0,0 +1,68 @@ +# 090 — Campaign closeout + +## Result + +| Surface | Before | After | Change | +|---|---|---|---| +| Local branches | 241 | 171 | −70 | +| Remote branches | 61 | 59 | −2 | +| Open issues | 45 | 32 | −13 | +| Open PRs | 53 | 56 | +3 | + +Local branch deletion: 71 refs, each with a recorded proof and a tip SHA +re-checked immediately before removal. Zero open-PR heads lost, zero +worktree-backing refs lost, zero preserved branches removed. + +Issues: 14 closed (3 implemented, 11 consolidated), 5 consolidated issues opened +(#3375–#3379), 7 stale reports given a specific unblocking request instead of a +silent close. + +PRs: 1 closed (#3312, superseded by the same author's #3348). The open-PR count +rose because unrelated work opened carry and stacked PRs while this ran. + +## What this campaign was actually about + +The instruction was to clean up merged branches and close superseded work. The +branch half was real: 71 of 241 local refs were duplicates of PR heads or +content already on `dev`. The PR half was not — 0 of 35 contributor PRs had +landed. The backlog is unreviewed, not stale, and the correct action was to +leave it open and say so. + +## The recurring defect + +Four separate times, a check reported success because a command had failed: + +1. zsh did not word-split an unquoted path list, so `git diff` matched nothing + and `feat/macos-app` — 57 unlanded files including an entire `app/` tree — + scored "landed". +2. `git fetch` and `git diff` return codes were ignored, so a stale ref or a + failed diff could authorize a deletion. +3. Cached proofs were never rechecked, so a branch that moved after + classification would still be deleted on a stale verdict. +4. Orphan `assets/*` branches have no merge base, so the diff exited 128 and + printed nothing; five evidence branches holding 36 unique images scored + "landed". + +Every one produced *empty output*, and empty output was read as "no +difference." The rule this campaign ends with: **a test whose safe answer is +silence must first prove the command spoke.** + +Three of the four were caught by an independent auditor that failed the plan +three times before passing. The fourth was caught by re-checking a result that +looked too convenient. None were caught by the plan document, which had +explicitly warned against this class of error on its own line 46. + +## Attribution + +Carry PRs #3371–#3373 credit their authors correctly. #3374 named a git identity +not linked to any GitHub account, which credits nobody; the trailer now names +`blackjune67 <46661504+blackjune67@users.noreply.github.com>` and the PR carries +an instruction to preserve it through the squash. Every issue closure names its +reporter and states what shipped and what did not. + +## Follow-ups worth doing + +- `missing_coauthor_credit` verifies a trailer exists but not that it resolves + to a real account. An unlinked-email check would have caught #3374. +- The 24 LIVE issues and 55 unreviewed PRs are the actual backlog. That is a + review campaign, not a hygiene one. From d81016b0f3eae11d6e197cc026c4de34cf00f576 Mon Sep 17 00:00:00 2001 From: jun Date: Fri, 4 Sep 2026 02:33:36 +0900 Subject: [PATCH 17/49] docs(devlog): record per-PR verdicts with landing measurements --- .../100_pr_verdicts.md | 63 +++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 devlog/_plan/260904_repo_hygiene_campaign/100_pr_verdicts.md diff --git a/devlog/_plan/260904_repo_hygiene_campaign/100_pr_verdicts.md b/devlog/_plan/260904_repo_hygiene_campaign/100_pr_verdicts.md new file mode 100644 index 0000000000..bf8e5d701c --- /dev/null +++ b/devlog/_plan/260904_repo_hygiene_campaign/100_pr_verdicts.md @@ -0,0 +1,63 @@ +# 100 — Per-PR verdicts + +Full classification of the 53 pull requests open when the campaign started. +Method: fetch each PR head, take the files it touches +(`git diff --name-only origin/dev...`), then compare those exact paths +two-dot against `origin/dev`. Remaining differences mean the work has not +landed. + +## Closed + +| PR | Author | Verdict | Evidence | +|---|---|---|---| +| #3312 | @RHODIZSECURITY | SUPERSEDED by #3348 | same 30 src/test files; the 9 that differ are v4 refinements, including a cooldown key moved off a positional pool id onto the key itself | + +## Landed during the campaign + +| PR | Verdict | Evidence | +|---|---|---| +| #3367 | merged | 24 files touched, 0 remaining; merged as `664d80c76` while the campaign ran | +| #2877 | 3 of 4 files landed | only `090_closeout.md` of the 260829 devlog unit still differs | + +## Not superseded — measured, not assumed + +Every remaining contributor PR was measured at **0% landed**. A sample, with +files touched and files still differing from `dev`: + +| PR | Author | Touched | Still differ | +|---|---|---|---| +| #1645 | @waw4303 | 68 | 68 | +| #2462 | @kwannz | 95 | 95 | +| #2113 | @cb8010d6 | 65 | 65 | +| #2881 | @wonny-log | 51 | 51 | +| #2562 | @roy6732856 | 46 | 46 | +| #2351 | @harryzhou2000 | 41 | 41 | +| #3025 | @randomix777 | 37 | 37 | +| #2921 | @Warexpor | 36 | 36 | +| #2956 | @Manson2438 | 34 | 34 | +| #2230 | @ppvia | 33 | 33 | +| #3349 / #3350 | @Flowershangfromthebranches | 30 / 30 | 30 / 30 | +| #3252 | @x3M3x | 24 | 24 | +| #2527 | @harryzhou2000 | 19 | 19 | +| #2213 | @louis-tepe | 18 | 18 | +| #2280 | @cristph | 17 | 17 | +| #2716 | @zigzag-007 | 17 | 17 | +| #3329 | @Veritas-7 | 17 | 17 | +| #3340 | @Flowershangfromthebranches | 17 | 17 | +| #3251 | @abhisheksharma2411 | 12 | 12 | +| #3283 | @vanch007 | 12 | 12 | + +…and the remainder identically. Full output: `.tmp/hygiene/pr-landing2.txt`. + +## Overlap pairs that are not duplicates + +35 PR pairs share ≥30% of their files. Two benign causes: + +- **Declared stacks.** #3340 → #3349 → #3350, #3364 → #3365, #3369 → #3370. Each + child states its parent and names its own unique commit. `enforce-target` + explicitly supports this workflow. +- **Shared surface.** Ten GUI PRs co-edit `gui/src/pages/Models.tsx` and the + i18n bundles because that is where GUI work lives. + +Closing either class as duplicates would have been wrong, which is why file +overlap was used only to generate candidates and never as evidence. From e3e00cf0c9f96603f1377b6a6caa1277d01fb804 Mon Sep 17 00:00:00 2001 From: jun Date: Fri, 4 Sep 2026 02:34:24 +0900 Subject: [PATCH 18/49] docs(credits): record the unlinked-trailer gap the hygiene gate cannot catch --- CREDITS.md | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/CREDITS.md b/CREDITS.md index b9340055f6..390b05e101 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -95,3 +95,32 @@ unnecessary. If you find a landing that belongs on this page, open an issue. Being missed is the defect this file documents, not a claim you have to argue for. + +### A gap the gate does not close + +The gate checks that a trailer is **present**. It cannot check that the trailer +resolves to the account it names. + +A 2026-09-04 backlog review found carry PR +[#3374](https://github.com/lidge-jun/opencodex/pull/3374), carrying +[#3333](https://github.com/lidge-jun/opencodex/pull/3333) by +[@blackjune67](https://github.com/blackjune67), with: + +``` +Co-authored-by: hajune +``` + +That is the git identity on the contributor's own commits, so it looks correct +in every review. But GitHub attributes co-authors by **account-linked** email, +and that address is linked to no account — so the trailer would have credited +nobody, and the contributor would have been invisible on their own patch. The +gate passed it, because a trailer was there. + +It was corrected before the merge to +`blackjune67 <46661504+blackjune67@users.noreply.github.com>`, which is why +there is no table row for it above. + +The lesson generalizes: when carrying work, take the trailer address from the +author's GitHub account (the `ID+login@users.noreply.github.com` form is always +safe), not from the commit metadata on their branch. A contributor who commits +under a work email is the normal case, not an edge case. From 164977e64891b724a951ea0081e81a58d54ff55b Mon Sep 17 00:00:00 2001 From: jun Date: Fri, 4 Sep 2026 02:35:05 +0900 Subject: [PATCH 19/49] docs(devlog): record contributor credit verification for the carry PRs --- .../110_credit_verification.md | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 devlog/_plan/260904_repo_hygiene_campaign/110_credit_verification.md diff --git a/devlog/_plan/260904_repo_hygiene_campaign/110_credit_verification.md b/devlog/_plan/260904_repo_hygiene_campaign/110_credit_verification.md new file mode 100644 index 0000000000..05b13e8e43 --- /dev/null +++ b/devlog/_plan/260904_repo_hygiene_campaign/110_credit_verification.md @@ -0,0 +1,52 @@ +# 110 — Contributor credit verification + +## What was checked + +Four maintainer pull requests carry contributor work and therefore owe a +`Co-authored-by` trailer under `AGENTS.md`. The check is not "does a trailer +exist" but "does the trailer name the GitHub account that authored the original +pull request" — those are different questions, and the difference is the whole +finding. + +| Carry PR | Carries | Author | Trailer resolves | +|---|---|---|---| +| #3371 | #3357 | @huaiqing-afk | yes | +| #3372 | #3322 | @luvs01 | yes | +| #3373 | #3335 | @x3M3x | yes | +| #3374 | #3333 | @blackjune67 | **no — corrected** | + +#3374 carried `Co-authored-by: hajune `. That address is the +git identity on every commit in #3333, so it survives any review that compares +the trailer to the branch. It is not linked to a GitHub account, and GitHub +attributes co-authorship by account-linked email, so the contributor would have +received nothing for their own patch. + +Corrected to `blackjune67 <46661504+blackjune67@users.noreply.github.com>`, with +a comment on the PR instructing whoever squashes it to preserve that exact +trailer. Recorded in `CREDITS.md` under "A gap the gate does not close". + +## Why the gate missed it + +`missing_coauthor_credit` in `.github/scripts/pr-carry-attribution.cjs` fails a +carry PR that has no trailer. It has no way to ask GitHub whether the address in +a trailer resolves to an account, so a well-formed trailer pointing at an +unlinked work email passes. A contributor committing under a company email is +the common case, not an exotic one, which makes this a systematic hole rather +than a one-off. + +A useful hardening: resolve the trailer email through the commits API on the +referenced PR and require it to match the PR author's account, rejecting +addresses that resolve to no account. + +## Re-verification + +`.tmp/hygiene/verify_credit.py` re-reads all four carry PRs live and asserts +each trailer names the original PR's GitHub author. It is a live check against +the API rather than a re-reading of this document. + +## Closure comments + +Every issue and pull request closed in this campaign carries a comment that +names its author, states what shipped with a code citation, states what did not, +and points at the consolidated issue where the surviving scope lives. Comment +IDs are recorded in the goalplan criterion `c-5`. No item was closed silently. From 82c8bedafcf699b36b460a5a3ab96a8fc139ea4e Mon Sep 17 00:00:00 2001 From: jun Date: Fri, 4 Sep 2026 02:36:03 +0900 Subject: [PATCH 20/49] docs(devlog): record per-issue verdicts and the consolidation map --- .../120_issue_verdicts.md | 65 +++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 devlog/_plan/260904_repo_hygiene_campaign/120_issue_verdicts.md diff --git a/devlog/_plan/260904_repo_hygiene_campaign/120_issue_verdicts.md b/devlog/_plan/260904_repo_hygiene_campaign/120_issue_verdicts.md new file mode 100644 index 0000000000..8865a7dd1d --- /dev/null +++ b/devlog/_plan/260904_repo_hygiene_campaign/120_issue_verdicts.md @@ -0,0 +1,65 @@ +# 120 — Per-issue verdicts + +All 45 issues open at campaign start, classified by three independent analysts +working disjoint batches. Every verdict carries a `file:line` or commit +citation; "no landing found" verdicts name the searches performed. + +## Closed as implemented + +| Issue | Reporter | Implementation | +|---|---|---| +| #1572 | @brunoflma | `src/server/responses/policy-fallback.ts`, cd7ea8a88 + 457c33675 | +| #2288 | @mobaicloud | `src/client/connect.ts`, 91a4f6c40 | +| #3158 | @lidge-jun | eceb02d9d + 0d8147c20 | + +## Closed into consolidated issues + +| Issue | Reporter | Absorbed by | What was still missing | +|---|---|---|---| +| #695 | @luwei1990 | #3375 | session affinity, 401/403 rotation, health lifecycle | +| #1062 | @agentHits | #3375 | aggregate pool health, account-attributed usage | +| #1977 | @dbc-hbin | #3375 | durable one-shot warmup scheduling | +| #2275 | @luvs01 | #3375 | caller-stable operation id on the manual endpoint | +| #2344 | @Michael-Han0608 | #3376 | quota history retention | +| #2874 | @wonny-log | #3376 | reset-window pool ordering | +| #2969 | @terrytan95 | #3376 | reset-driven window activation (PR #2973 open) | +| #3268 | @turin-dev | #3377 | text-only model declaration | +| #3271 | @GoldenLoaf24h | #3377 | video processing mode passthrough | +| #3281 | @Simon-Opopeee | #3377 | context tier selection (PR #3282 open) | +| #3344 | @colthreepv | #3378 | `x-opencode-session` header | +| #3362 | @0disoft | #3378 | `indexed_web_access` sanitization | +| #2399 | @ncepuee | #3379 | journal entry deletion | +| #2748 | @areskts | #3379 | custom date/hour usage ranges | +| #3017 | @hayabusasxs | #3379 | account selector rename API | + +Where an absorbed issue has an open implementation PR (#2973, #3282), the +closure comment says explicitly that the PR is not superseded. + +## Left open — still valid, unimplemented + +#95, #1213, #1416, #1533, #1711, #2279, #2358, #2455, #2495, #2511, #2730, +#2811, #2834, #2894, #3191, #3259, #3266, #3352, #3353, #3366 and the +needs-info set below. Each was verified against current `dev` rather than +assumed: for example #2894 (SOCKS5) is unimplemented because +`src/types/config.ts` defines only a global HTTP(S) proxy with no per-provider +override and no scheme validation. + +## Left open — blocked on the reporter + +#1527, #1782, #1811, #3245, #3255, #3279, #3320. + +These were **not** closed. Each received a comment stating where the code +stands and naming the single artifact that would unblock it — a redacted +`UserId` element, a current reproduction, a failing request URL. Closing a +report because its author has not replied yet is how a project stops receiving +reports, and several of these are plausible defects whose evidence simply has +not arrived. + +## Note on partial verdicts + +Eleven issues were PARTIAL and eleven were closed, but several other PARTIAL +findings (#95, #1213, #1533, #2358, #2455, #2511, #2811, #2834, #3191, #3353) +were left open instead. The difference is whether the remainder belongs to a +cluster: a partial whose surviving scope stands alone stays as its own issue, +because folding a single coherent request into a consolidated one loses detail +without reducing count. From 42f7f1f2b4ed2cfb2d967dd29d39657ec65dcbaf Mon Sep 17 00:00:00 2001 From: jun Date: Fri, 4 Sep 2026 02:36:48 +0900 Subject: [PATCH 21/49] docs(devlog): close the hygiene campaign unit into _fin with live final counts --- .../260904_repo_hygiene_campaign/000_plan.md | 0 .../260904_repo_hygiene_campaign/010_method.md | 0 .../015_audit_record.md | 0 .../020_wp1_local_branches.md | 0 .../030_wp2_remote_branches.md | 0 .../040_wp3_maintainer_prs.md | 0 .../050_wp4_contributor_prs.md | 0 .../060_wp5_issues.md | 0 .../070_wp6_credit_and_closeout.md | 0 .../080_drawdown_ledger.md | 0 .../260904_repo_hygiene_campaign/090_closeout.md | 16 ++++++++++++++-- .../100_pr_verdicts.md | 0 .../110_credit_verification.md | 0 .../120_issue_verdicts.md | 0 14 files changed, 14 insertions(+), 2 deletions(-) rename devlog/{_plan => _fin}/260904_repo_hygiene_campaign/000_plan.md (100%) rename devlog/{_plan => _fin}/260904_repo_hygiene_campaign/010_method.md (100%) rename devlog/{_plan => _fin}/260904_repo_hygiene_campaign/015_audit_record.md (100%) rename devlog/{_plan => _fin}/260904_repo_hygiene_campaign/020_wp1_local_branches.md (100%) rename devlog/{_plan => _fin}/260904_repo_hygiene_campaign/030_wp2_remote_branches.md (100%) rename devlog/{_plan => _fin}/260904_repo_hygiene_campaign/040_wp3_maintainer_prs.md (100%) rename devlog/{_plan => _fin}/260904_repo_hygiene_campaign/050_wp4_contributor_prs.md (100%) rename devlog/{_plan => _fin}/260904_repo_hygiene_campaign/060_wp5_issues.md (100%) rename devlog/{_plan => _fin}/260904_repo_hygiene_campaign/070_wp6_credit_and_closeout.md (100%) rename devlog/{_plan => _fin}/260904_repo_hygiene_campaign/080_drawdown_ledger.md (100%) rename devlog/{_plan => _fin}/260904_repo_hygiene_campaign/090_closeout.md (79%) rename devlog/{_plan => _fin}/260904_repo_hygiene_campaign/100_pr_verdicts.md (100%) rename devlog/{_plan => _fin}/260904_repo_hygiene_campaign/110_credit_verification.md (100%) rename devlog/{_plan => _fin}/260904_repo_hygiene_campaign/120_issue_verdicts.md (100%) diff --git a/devlog/_plan/260904_repo_hygiene_campaign/000_plan.md b/devlog/_fin/260904_repo_hygiene_campaign/000_plan.md similarity index 100% rename from devlog/_plan/260904_repo_hygiene_campaign/000_plan.md rename to devlog/_fin/260904_repo_hygiene_campaign/000_plan.md diff --git a/devlog/_plan/260904_repo_hygiene_campaign/010_method.md b/devlog/_fin/260904_repo_hygiene_campaign/010_method.md similarity index 100% rename from devlog/_plan/260904_repo_hygiene_campaign/010_method.md rename to devlog/_fin/260904_repo_hygiene_campaign/010_method.md diff --git a/devlog/_plan/260904_repo_hygiene_campaign/015_audit_record.md b/devlog/_fin/260904_repo_hygiene_campaign/015_audit_record.md similarity index 100% rename from devlog/_plan/260904_repo_hygiene_campaign/015_audit_record.md rename to devlog/_fin/260904_repo_hygiene_campaign/015_audit_record.md diff --git a/devlog/_plan/260904_repo_hygiene_campaign/020_wp1_local_branches.md b/devlog/_fin/260904_repo_hygiene_campaign/020_wp1_local_branches.md similarity index 100% rename from devlog/_plan/260904_repo_hygiene_campaign/020_wp1_local_branches.md rename to devlog/_fin/260904_repo_hygiene_campaign/020_wp1_local_branches.md diff --git a/devlog/_plan/260904_repo_hygiene_campaign/030_wp2_remote_branches.md b/devlog/_fin/260904_repo_hygiene_campaign/030_wp2_remote_branches.md similarity index 100% rename from devlog/_plan/260904_repo_hygiene_campaign/030_wp2_remote_branches.md rename to devlog/_fin/260904_repo_hygiene_campaign/030_wp2_remote_branches.md diff --git a/devlog/_plan/260904_repo_hygiene_campaign/040_wp3_maintainer_prs.md b/devlog/_fin/260904_repo_hygiene_campaign/040_wp3_maintainer_prs.md similarity index 100% rename from devlog/_plan/260904_repo_hygiene_campaign/040_wp3_maintainer_prs.md rename to devlog/_fin/260904_repo_hygiene_campaign/040_wp3_maintainer_prs.md diff --git a/devlog/_plan/260904_repo_hygiene_campaign/050_wp4_contributor_prs.md b/devlog/_fin/260904_repo_hygiene_campaign/050_wp4_contributor_prs.md similarity index 100% rename from devlog/_plan/260904_repo_hygiene_campaign/050_wp4_contributor_prs.md rename to devlog/_fin/260904_repo_hygiene_campaign/050_wp4_contributor_prs.md diff --git a/devlog/_plan/260904_repo_hygiene_campaign/060_wp5_issues.md b/devlog/_fin/260904_repo_hygiene_campaign/060_wp5_issues.md similarity index 100% rename from devlog/_plan/260904_repo_hygiene_campaign/060_wp5_issues.md rename to devlog/_fin/260904_repo_hygiene_campaign/060_wp5_issues.md diff --git a/devlog/_plan/260904_repo_hygiene_campaign/070_wp6_credit_and_closeout.md b/devlog/_fin/260904_repo_hygiene_campaign/070_wp6_credit_and_closeout.md similarity index 100% rename from devlog/_plan/260904_repo_hygiene_campaign/070_wp6_credit_and_closeout.md rename to devlog/_fin/260904_repo_hygiene_campaign/070_wp6_credit_and_closeout.md diff --git a/devlog/_plan/260904_repo_hygiene_campaign/080_drawdown_ledger.md b/devlog/_fin/260904_repo_hygiene_campaign/080_drawdown_ledger.md similarity index 100% rename from devlog/_plan/260904_repo_hygiene_campaign/080_drawdown_ledger.md rename to devlog/_fin/260904_repo_hygiene_campaign/080_drawdown_ledger.md diff --git a/devlog/_plan/260904_repo_hygiene_campaign/090_closeout.md b/devlog/_fin/260904_repo_hygiene_campaign/090_closeout.md similarity index 79% rename from devlog/_plan/260904_repo_hygiene_campaign/090_closeout.md rename to devlog/_fin/260904_repo_hygiene_campaign/090_closeout.md index 80b0c22cd7..74813e7962 100644 --- a/devlog/_plan/260904_repo_hygiene_campaign/090_closeout.md +++ b/devlog/_fin/260904_repo_hygiene_campaign/090_closeout.md @@ -6,8 +6,20 @@ |---|---|---|---| | Local branches | 241 | 171 | −70 | | Remote branches | 61 | 59 | −2 | -| Open issues | 45 | 32 | −13 | -| Open PRs | 53 | 56 | +3 | +| Open issues | 45 | 33 | −12 | +| Open PRs | 53 | 57 | +4 | + +The after-counts are live at closeout, not a subtraction from the plan. Both +"increases" are inbound traffic during the campaign, not work left undone: four +carry PRs and two stacked PRs were opened by other sessions, and new reports +arrived (for example #3384 from @Yum-wu). 14 issues were closed and 5 +consolidated issues opened, so the issue ledger nets −12 against a moving +baseline rather than −13 against a frozen one. + +Counting against live state instead of the entry snapshot is deliberate. A +repository with contributors does not hold still for a cleanup, and a closeout +that reports the number it predicted rather than the number that exists is +reporting on its own plan. Local branch deletion: 71 refs, each with a recorded proof and a tip SHA re-checked immediately before removal. Zero open-PR heads lost, zero diff --git a/devlog/_plan/260904_repo_hygiene_campaign/100_pr_verdicts.md b/devlog/_fin/260904_repo_hygiene_campaign/100_pr_verdicts.md similarity index 100% rename from devlog/_plan/260904_repo_hygiene_campaign/100_pr_verdicts.md rename to devlog/_fin/260904_repo_hygiene_campaign/100_pr_verdicts.md diff --git a/devlog/_plan/260904_repo_hygiene_campaign/110_credit_verification.md b/devlog/_fin/260904_repo_hygiene_campaign/110_credit_verification.md similarity index 100% rename from devlog/_plan/260904_repo_hygiene_campaign/110_credit_verification.md rename to devlog/_fin/260904_repo_hygiene_campaign/110_credit_verification.md diff --git a/devlog/_plan/260904_repo_hygiene_campaign/120_issue_verdicts.md b/devlog/_fin/260904_repo_hygiene_campaign/120_issue_verdicts.md similarity index 100% rename from devlog/_plan/260904_repo_hygiene_campaign/120_issue_verdicts.md rename to devlog/_fin/260904_repo_hygiene_campaign/120_issue_verdicts.md From 604e87b15ce45cecc9186d8a4aa2a324815440b4 Mon Sep 17 00:00:00 2001 From: JUN Date: Fri, 4 Sep 2026 02:38:06 +0900 Subject: [PATCH 22/49] =?UTF-8?q?docs(devlog):=20dashboard-minimal=20roadm?= =?UTF-8?q?ap=20=E2=80=94=20inventory,=20three=20reviewer=20opinions,=20de?= =?UTF-8?q?cisions,=20nine=20decade=20docs=20(#3381)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * docs(devlog): dashboard-minimal roadmap — inventory, three reviewer opinions, decisions, nine decade docs Docs-only unit. Inventory of all 16 dashboard routes (ko, 1440px, live proxy captures), three independent read-only reviews (claude-fable via the sol slot, claude-opus-5, grok-4.6) with a disagreement table, 51 merged keep/remove/collapse verdicts, and one diff-level decade doc per implementation phase in dependency order: sidebar footer, dashboard home, models catalog, integrations, codex-set cards, usage, startup, page polish, i18n prune + docs sync. * docs(devlog): fold the roadmap audit round 1 (8 blockers) into the decade docs * docs(devlog): fold roadmap audit round 2 (9 defects): 002 consistency, effort-cap rehome, v2 state kept, tab collapse rule, a11y tooltips, combos CTA, locale paths, star button mount * docs(devlog): fold roadmap audit round 3: explicit star mount, chevron CSS, UltraMode contract per phase, rehome tests, Tooltip child, usage.subtitle kept * docs(devlog): fold roadmap audit round 4: d.apiBase, multiAgentMode constructor sites, Tooltip accessible name * docs(devlog): narrow the 090 orphan expectation (star + multiAgentGuidance keys stay consumed) * docs(devlog): record the five roadmap audit rounds (wp0 B artifact) * docs(devlog): redact addresses from the dashboard capture dumps (privacy:scan) --------- Co-authored-by: jun --- .../260904_dashboard_minimal/000_inventory.md | 89 ++ .../001_subagent_opinions.md | 881 ++++++++++++++++++ .../260904_dashboard_minimal/002_decisions.md | 101 ++ .../003_audit_record.md | 19 + .../010_sidebar_footer.md | 138 +++ .../020_dashboard_home.md | 167 ++++ .../030_models_catalog.md | 82 ++ .../040_integrations.md | 64 ++ .../260904_dashboard_minimal/050_codex_set.md | 69 ++ .../260904_dashboard_minimal/060_usage.md | 50 + .../260904_dashboard_minimal/070_startup.md | 49 + .../080_page_polish.md | 71 ++ .../090_i18n_prune_docs.md | 50 + .../assets/codex-set_1440.png | Bin 0 -> 217210 bytes .../assets/codex-set_interactive.txt | 51 + .../assets/codex-set_text.txt | 115 +++ .../assets/dashboard_1440.png | Bin 0 -> 205723 bytes .../assets/dashboard_interactive.txt | 37 + .../assets/dashboard_models_1440.png | Bin 0 -> 144708 bytes .../assets/dashboard_models_interactive.txt | 28 + .../assets/dashboard_models_text.txt | 42 + .../assets/dashboard_providers_1440.png | Bin 0 -> 175869 bytes .../dashboard_providers_interactive.txt | 18 + .../assets/dashboard_providers_text.txt | 34 + .../assets/dashboard_text.txt | 76 ++ .../assets/integrations_1440.png | Bin 0 -> 237623 bytes .../assets/integrations_interactive.txt | 86 ++ .../assets/integrations_text.txt | 163 ++++ .../assets/logs_1440.png | Bin 0 -> 211341 bytes .../assets/logs_debug_1440.png | Bin 0 -> 155847 bytes .../assets/logs_debug_interactive.txt | 24 + .../assets/logs_debug_text.txt | 30 + .../assets/logs_interactive.txt | 25 + .../assets/logs_text.txt | 425 +++++++++ .../assets/models_1440.png | Bin 0 -> 272955 bytes .../assets/models_combos_1440.png | Bin 0 -> 142151 bytes .../assets/models_combos_interactive.txt | 59 ++ .../assets/models_combos_text.txt | 89 ++ .../assets/models_compatibility_1440.png | Bin 0 -> 157526 bytes .../models_compatibility_interactive.txt | 22 + .../assets/models_compatibility_text.txt | 27 + .../assets/models_interactive.txt | 144 +++ .../assets/models_routing_1440.png | Bin 0 -> 156067 bytes .../assets/models_routing_interactive.txt | 28 + .../assets/models_routing_text.txt | 37 + .../assets/models_text.txt | 166 ++++ .../assets/providers_1440.png | Bin 0 -> 272079 bytes .../assets/providers_interactive.txt | 37 + .../assets/providers_text.txt | 125 +++ .../assets/startup_1440.png | Bin 0 -> 215104 bytes .../assets/startup_interactive.txt | 22 + .../assets/startup_text.txt | 59 ++ .../assets/storage_1440.png | Bin 0 -> 131402 bytes .../assets/storage_interactive.txt | 16 + .../assets/storage_text.txt | 21 + .../assets/subagents_1440.png | Bin 0 -> 162388 bytes .../assets/subagents_interactive.txt | 60 ++ .../assets/subagents_text.txt | 74 ++ .../assets/usage_1440.png | Bin 0 -> 192569 bytes .../assets/usage_interactive.txt | 27 + .../assets/usage_text.txt | 256 +++++ 61 files changed, 4253 insertions(+) create mode 100644 devlog/_plan/260904_dashboard_minimal/000_inventory.md create mode 100644 devlog/_plan/260904_dashboard_minimal/001_subagent_opinions.md create mode 100644 devlog/_plan/260904_dashboard_minimal/002_decisions.md create mode 100644 devlog/_plan/260904_dashboard_minimal/003_audit_record.md create mode 100644 devlog/_plan/260904_dashboard_minimal/010_sidebar_footer.md create mode 100644 devlog/_plan/260904_dashboard_minimal/020_dashboard_home.md create mode 100644 devlog/_plan/260904_dashboard_minimal/030_models_catalog.md create mode 100644 devlog/_plan/260904_dashboard_minimal/040_integrations.md create mode 100644 devlog/_plan/260904_dashboard_minimal/050_codex_set.md create mode 100644 devlog/_plan/260904_dashboard_minimal/060_usage.md create mode 100644 devlog/_plan/260904_dashboard_minimal/070_startup.md create mode 100644 devlog/_plan/260904_dashboard_minimal/080_page_polish.md create mode 100644 devlog/_plan/260904_dashboard_minimal/090_i18n_prune_docs.md create mode 100644 devlog/_plan/260904_dashboard_minimal/assets/codex-set_1440.png create mode 100644 devlog/_plan/260904_dashboard_minimal/assets/codex-set_interactive.txt create mode 100644 devlog/_plan/260904_dashboard_minimal/assets/codex-set_text.txt create mode 100644 devlog/_plan/260904_dashboard_minimal/assets/dashboard_1440.png create mode 100644 devlog/_plan/260904_dashboard_minimal/assets/dashboard_interactive.txt create mode 100644 devlog/_plan/260904_dashboard_minimal/assets/dashboard_models_1440.png create mode 100644 devlog/_plan/260904_dashboard_minimal/assets/dashboard_models_interactive.txt create mode 100644 devlog/_plan/260904_dashboard_minimal/assets/dashboard_models_text.txt create mode 100644 devlog/_plan/260904_dashboard_minimal/assets/dashboard_providers_1440.png create mode 100644 devlog/_plan/260904_dashboard_minimal/assets/dashboard_providers_interactive.txt create mode 100644 devlog/_plan/260904_dashboard_minimal/assets/dashboard_providers_text.txt create mode 100644 devlog/_plan/260904_dashboard_minimal/assets/dashboard_text.txt create mode 100644 devlog/_plan/260904_dashboard_minimal/assets/integrations_1440.png create mode 100644 devlog/_plan/260904_dashboard_minimal/assets/integrations_interactive.txt create mode 100644 devlog/_plan/260904_dashboard_minimal/assets/integrations_text.txt create mode 100644 devlog/_plan/260904_dashboard_minimal/assets/logs_1440.png create mode 100644 devlog/_plan/260904_dashboard_minimal/assets/logs_debug_1440.png create mode 100644 devlog/_plan/260904_dashboard_minimal/assets/logs_debug_interactive.txt create mode 100644 devlog/_plan/260904_dashboard_minimal/assets/logs_debug_text.txt create mode 100644 devlog/_plan/260904_dashboard_minimal/assets/logs_interactive.txt create mode 100644 devlog/_plan/260904_dashboard_minimal/assets/logs_text.txt create mode 100644 devlog/_plan/260904_dashboard_minimal/assets/models_1440.png create mode 100644 devlog/_plan/260904_dashboard_minimal/assets/models_combos_1440.png create mode 100644 devlog/_plan/260904_dashboard_minimal/assets/models_combos_interactive.txt create mode 100644 devlog/_plan/260904_dashboard_minimal/assets/models_combos_text.txt create mode 100644 devlog/_plan/260904_dashboard_minimal/assets/models_compatibility_1440.png create mode 100644 devlog/_plan/260904_dashboard_minimal/assets/models_compatibility_interactive.txt create mode 100644 devlog/_plan/260904_dashboard_minimal/assets/models_compatibility_text.txt create mode 100644 devlog/_plan/260904_dashboard_minimal/assets/models_interactive.txt create mode 100644 devlog/_plan/260904_dashboard_minimal/assets/models_routing_1440.png create mode 100644 devlog/_plan/260904_dashboard_minimal/assets/models_routing_interactive.txt create mode 100644 devlog/_plan/260904_dashboard_minimal/assets/models_routing_text.txt create mode 100644 devlog/_plan/260904_dashboard_minimal/assets/models_text.txt create mode 100644 devlog/_plan/260904_dashboard_minimal/assets/providers_1440.png create mode 100644 devlog/_plan/260904_dashboard_minimal/assets/providers_interactive.txt create mode 100644 devlog/_plan/260904_dashboard_minimal/assets/providers_text.txt create mode 100644 devlog/_plan/260904_dashboard_minimal/assets/startup_1440.png create mode 100644 devlog/_plan/260904_dashboard_minimal/assets/startup_interactive.txt create mode 100644 devlog/_plan/260904_dashboard_minimal/assets/startup_text.txt create mode 100644 devlog/_plan/260904_dashboard_minimal/assets/storage_1440.png create mode 100644 devlog/_plan/260904_dashboard_minimal/assets/storage_interactive.txt create mode 100644 devlog/_plan/260904_dashboard_minimal/assets/storage_text.txt create mode 100644 devlog/_plan/260904_dashboard_minimal/assets/subagents_1440.png create mode 100644 devlog/_plan/260904_dashboard_minimal/assets/subagents_interactive.txt create mode 100644 devlog/_plan/260904_dashboard_minimal/assets/subagents_text.txt create mode 100644 devlog/_plan/260904_dashboard_minimal/assets/usage_1440.png create mode 100644 devlog/_plan/260904_dashboard_minimal/assets/usage_interactive.txt create mode 100644 devlog/_plan/260904_dashboard_minimal/assets/usage_text.txt diff --git a/devlog/_plan/260904_dashboard_minimal/000_inventory.md b/devlog/_plan/260904_dashboard_minimal/000_inventory.md new file mode 100644 index 0000000000..2487c1afae --- /dev/null +++ b/devlog/_plan/260904_dashboard_minimal/000_inventory.md @@ -0,0 +1,89 @@ +# 000 — Dashboard inventory (as shipped, v2.42.0, dev @ 664d80c76) + +Evidence: `assets/_1440.png` (full page, ko, 1440 px headless Chrome against the live +proxy), `assets/_text.txt` (visible text), `assets/_interactive.txt` (interactive +controls with refs, `agbrowse snapshot --interactive`). Storage was captured mid-scan (its skeleton +is the honest first paint on a 1.6 GB CODEX_HOME) and is inventoried from source. + +Counts are from the captures: interactive = controls in the snapshot, words = visible text words. + +| Route | Source | Interactive | Words | Screenshot | +|---|---|---|---|---| +| Sidebar + top bar | gui/src/App.tsx, components/sidebar-github-row.tsx, styles.css | 22 | — | every capture, left rail | +| #dashboard (overview) | pages/Dashboard.tsx, dashboard-overview-sections.tsx (669 L), dashboard-dialogs.tsx | 34 | 199 | dashboard_1440.png | +| #dashboard/providers | same | 18 | — | dashboard_providers_1440.png | +| #dashboard/models | same | 28 | — | dashboard_models_1440.png | +| #startup | pages/Startup.tsx (403 L), startup-sections.tsx | 22 | 167 | startup_1440.png | +| #providers | pages/Providers.tsx, components/provider-workspace/* | 27 | 310 | providers_1440.png | +| #models | pages/Models.tsx (2329 L) | 135 | 460 | models_1440.png | +| #models/combos | pages/Combos.tsx, components/combo-workspace-* | 59 | — | models_combos_1440.png | +| #models/routing | pages/RoutingProfiles.tsx (1139 L) | 28 | — | models_routing_1440.png | +| #models/compatibility | pages/CompatibilityMatrix.tsx | 27 | — | models_compatibility_1440.png | +| #subagents | pages/Subagents.tsx, components/subagents-workspace/* | 60 | 232 | subagents_1440.png | +| #logs | pages/Logs.tsx (1147 L) | 50 | 346 | logs_1440.png | +| #logs/debug | pages/Debug.tsx, debug-log-viewer.tsx | 24 | — | logs_debug_1440.png | +| #usage | pages/Usage.tsx (889 L) | 27 | 654 | usage_1440.png | +| #storage | pages/Storage.tsx (1469 L), components/storage-workspace/* | 16 (skeleton) | — | storage_1440.png | +| #codex-set | pages/codex-set-multiauth.tsx, codex-set-prompt.tsx, components/codex-set/*, CodexAccountPool.tsx | 51 | 361 | codex-set_1440.png | +| #integrations | pages/Integrations.tsx, ApiKeys.tsx, Claude*.tsx, Grok.tsx | 86 | 233 | integrations_1440.png | + +## Element-level notes from the captures (main agent's own pass) + +Sidebar / top bar +- Brand + version chip, 9 nav rows, language combobox, theme button ("시스템"), a "프록시" label + row with stop + reload-models icon buttons, GitHub row with star + download(update) icons. +- The "프록시" row is a label with two icon buttons and no state; "시스템" (theme) is a full-width row + for a rarely-used control. + +Dashboard overview +- Six stat cards: subagent mode segmented (v1/base/v2 — a control inside a stat card), status, + version, uptime, provider count, tokens(30d)+coverage. +- A green "재부팅 후에도 opencodex가 자동으로 준비됩니다" notice band (duplicated on #startup). +- Card "서브에이전트 위임" with a value chip and "설정 열기" (duplicates #subagents). +- Card "모델 동기화" with "지금 동기화" (duplicates the top-bar reload-models icon). +- Card "Codex 실행 시 opencodex 시작" toggle + two sentences (duplicates #startup shim row). +- Cards "웹 검색 사이드카", "비전 사이드카" with model comboboxes, a streaming toggle, "고급 설정". +- Tabs "활성 프로바이더", "사용 가능한 모델" duplicate #providers and #models content. + +Startup +- Orange sync banner (Codex version drift) with copy button; green hero card; three stat cards + restating the hero; "보호 상태 상세" list; "복구 방법" with copyable commands; "대시보드로 + 돌아가기" + "새로고침" buttons. + +Providers +- Left list (status dot, model count), right overview: 3 stat cards (ready / needs setup / + inactive), "사용량 제한" per-provider quota bars with reset times, "최근 사용" list, "JSON 편집", + "+ 프로바이더 추가", filter icon. + +Models +- Top notice "Codex가 이 카탈로그보다 오래된 모델 목록을 보여주고 있습니다" + "Codex 모델 목록 + 새로고침" button; 4 tabs; a 4-line explanatory paragraph; provider list; global toggles row + (새 모델을 비활성화 상태로 추가, 섀도우 호출 가로채기 with model picker, 기본 창/상한 stepper + + toggle) each with a helper sentence; "우선 순서" explanation block; "모두 접기 / 모두 펼치기"; + per-provider group header with 6 controls (edit, 기본 별칭 사용, 커스텀 모델 추가, 모두 켜기, + 모두 끄기, 기본 창/상한 + 사용자 지정 창) repeated per group. + +Subagents +- 3 tabs; "추천" list with per-row up/down/remove; "저장"; "모델" search + checklist. Helper + sentence with inline code. + +Logs +- Title + sentence; auto-refresh checkbox; tabs; surface segmented; "가로챈 헬퍼만"; two filter + inputs with labels; 10-column table; per-row "상세보기" link under the status. + +Usage +- Range segmented (전체/Codex/Claude/Grok) + period segmented; 4 tabs with counts; 6 stat cards; + cost banner sentence; heatmap with legend; model search + table; provider table; coverage. + +Codex 설정 +- Tabs 다중 인증 / 프롬프트; header controls (Spark 할당량 toggle, 한도 도달 계정 일시 중지, + 할당량 새로고침); "OpenAI 계정 모드" card; main account card with 5 badges/buttons; per-account + cards with plan badge, count badge, 4 buttons, priority select, quota bars, ✕. + +Integrations +- 18 tabs (one per client) in two rows; 4-number summary + "모두 해제"; "API 키" row; explanatory + paragraph; card grid: name, status badge, one-line, toggle, "설정". + +Storage +- Title + sentence, "다시 스캔", card list (source: per-category size cards, cleanup presets, + log guard section, protection toggles). diff --git a/devlog/_plan/260904_dashboard_minimal/001_subagent_opinions.md b/devlog/_plan/260904_dashboard_minimal/001_subagent_opinions.md new file mode 100644 index 0000000000..d4ccdedcd9 --- /dev/null +++ b/devlog/_plan/260904_dashboard_minimal/001_subagent_opinions.md @@ -0,0 +1,881 @@ +# 001 — Subagent opinions (independent, read-only, dev @ 664d80c76) + +Three reviewers were dispatched in parallel with the same packet (evidence pack in `assets/`, +full source access, no edits, no suites, no proxy mutation). Model requested → model that +answered (as self-reported in the REVIEWER line): + +| # | Requested | Answered as | Agent | Status | +|---|---|---|---|---| +| R1 | gpt-5.6-sol / medium | claude-fable-5-1 (proxy routed) | 01a06823-44b2 "Mendel" | complete | +| R2 | anthropic/claude-opus-5 / medium | claude-opus-5 | 01a06823-4549 "Epicurus" | complete | +| R3 | xai/grok-4.6 / high | grok-4.6 | 01a06823-45e1 "Averroes" | complete (≈21 min) | + +Evidence caveat both reviewers raised: the first capture pass had four misrouted text files +(logs, logs_debug, models_compatibility, and subagents==storage). They were recaptured before +002 was written; the reviewers' verdicts for those routes were source-grounded and were +re-checked against the corrected captures in 002. + +## Where R1 and R2 agree (high confidence) + +- Dashboard "활성 프로바이더" and "사용 가능한 모델" tabs duplicate Providers/Models → remove. +- Dashboard duplicates settings that have a home elsewhere: subagent v1/base/v2 switch, + "서브에이전트 위임" card, shadow-call intercept, "Codex 실행 시 opencodex 시작" → demote to + their owning page (Subagents / Models / Startup). +- Integrations 18-tab strip is redundant with the card grid → collapse; zero-valued summary + cards and uninstalled-client cards → hide when zero / behind "add client". +- Sidebar: GitHub star orb removed from chrome; GitHub row demoted; language + theme + collapsed into a compact footer control; "프록시" label removed (orbs keep aria-labels). +- Models: 4-line catalog subtitle + picker-order paragraph → tooltip/help; per-provider header + control wall (6 controls × N providers) → per-provider action menu. +- Codex 설정: per-account priority explanation ×6 → one shared ⓘ; 별칭 편집 / ✕ → overflow + menu; truncated account ID → tooltip (copyable). +- Usage: 활동일 card removed; coverage shown once; cost estimate keeps its disclaimer; heatmap + collapsed/follows range. +- Startup: three stat cards restating the hero → collapse; "대시보드로 돌아가기" removed. +- Providers: 3 summary cards restate the rail → collapse; "최근 사용" demoted to Usage; + quota bars KEEP (both call them the highest-value element on the page). +- Never touch: stop/restart orbs, reboot-protection health bar, quota bars, storage + destructive-action ceremony + quarantine, JSON 편집 escape hatch, conditional warning + banners, cost/lab disclaimers, model visibility toggles. + +## Where they disagree + +| Topic | R1 | R2 | Note for 002 | +|---|---|---|---| +| Version chip in sidebar | demote to tooltip | keep (most-asked support fact) | R2 wins: one chip, zero cost, high support value. | +| Sidebar nav rows | demote Codex 설정 / 서브에이전트 / 저장소 under other pages | keep all 9 | R2 wins for this loop: route changes are a scope expansion; nav stays. | +| Logs 10-column table | collapse 5 columns behind a column picker | (no call) | Defer — Logs was just reworked (#3367); revisit after the rest lands. | +| Memory 관찰 card | collapse behind runtime details | #1 highest noise: collapse body, keep pressure bar | Agree on collapse; R2's shape (keep pressure/in-flight/restart) is the one to build. | +| Providers summary cards | keep | collapse (restate the rail) | R2 wins: the rail group headers already carry ready/needs-setup/inactive counts. | +| Integrations "모두 해제" | demote to bulk menu | keep | R2 wins: bulk rollback of a config-writing feature is safety, not noise. | +| Storage subtitle | keep | keep | agree. | + +## R3 — headline (full text in §R3 below) + +R3 converges with R1/R2 on: dashboard clone tabs, triple v1/base/v2 (owner: Subagents), dual shadow-call (owner: Models), Models essay/control wall, Integrations 18 tabs, Usage heatmap + sticker price, sidebar star/GitHub/update chrome, Combos empty-state expert form, Routing dry-run on empty tab, per-account 선택 순서 ×N, page subtitles. R3-only calls: remove the third Codex-restart orb on the Models page head (Models.tsx:2207); demote "재시도" on Routing to error-only; DEMOTE 활동일; keep Providers 3 summary cards (disagrees with R2). R3 keeps Startup three stat cards (R1 collapses them) and keeps "모두 해제". + +## R1 — full review + +Review basis: commit `664d80c76`, current source, visible-text/control captures, and all 16 PNGs. No files were changed and no tests or proxy operations were run. + +The intended product posture should be: show current health, exceptions, and the next useful action; disclose implementation detail, raw identifiers, historical data, and rare configuration only on demand. + +Evidence warning: three supplied captures are misrouted: + +- `logs_1440.png` / `logs_text.txt` show Codex authentication. +- `logs_debug_1440.png` / `logs_debug_text.txt` show Integrations. +- `models_compatibility_1440.png` / its text show Usage. + +Those routes can be reviewed structurally from source, but not visually validated from this evidence pack. + +## Sidebar + +| element | source file:line | verdict | reason | risk | +|---|---|---|---|---| +| `opencodex` | `gui/src/App.tsx:249` | KEEP | Stable product identity anchors every route. | Removing it makes the shell anonymous. | +| `v2.42.0` | `gui/src/App.tsx:250` | DEMOTE-to-System/status tooltip | Version matters during diagnosis, not during every navigation decision. | Operators may take one extra action when comparing versions. | +| `대시보드`, `프로바이더`, `모델`, `로그&디버그`, `사용량`, `연동` | `gui/src/App.tsx:62` | KEEP | These are distinct, frequent operator jobs. | Combining them would obscure major workflows. | +| `Codex 설정` | `gui/src/App.tsx:64` | DEMOTE-to-Codex subsection under Providers or Models | It is product-specific configuration inside a universal proxy and currently competes with primary operations. | Codex-heavy users lose one-click access. | +| `서브에이전트` | `gui/src/App.tsx:67` | DEMOTE-to-Models/Advanced | It configures model selection behavior rather than a standalone runtime resource. | Multi-agent users need one extra click. | +| `저장소` | `gui/src/App.tsx:70` | DEMOTE-to-System/maintenance | Storage cleanup is periodic maintenance, not a primary daily destination. | Disk-pressure investigation is less immediately discoverable. | +| `한국어` | `gui/src/App.tsx:323` | COLLAPSE-behind-settings-popover | Locale is a rare preference after initial selection. | Language switching becomes one click deeper. | +| `시스템` theme control | `gui/src/App.tsx:335` | COLLAPSE-behind-settings-popover | Theme has no proxy-operational decision value. | Theme switching becomes less immediate. | +| `프록시` plus stop/restart icons | `gui/src/App.tsx:339` | KEEP | Stop and restart are consequential runtime controls. | Hiding them would delay recovery. | +| `GitHub` | `gui/src/components/sidebar-github-row.tsx:131` | REMOVE | Repository promotion is unrelated to operating the local proxy. | Users lose a convenience link; the repository remains reachable elsewhere. | +| star control | `gui/src/components/sidebar-github-row.tsx:136` | REMOVE | Spending user identity/reputation has zero operator value in persistent navigation. | Users cannot star from the dashboard. | +| update icon | `gui/src/components/sidebar-github-row.tsx:147` | DEMOTE-to-System/version-status | Updating is operationally relevant only when an update exists. | Manual update checks become less prominent. | + +## Topbar + +The desktop evidence has no independent topbar; this is the mobile shell. + +| element | source file:line | verdict | reason | risk | +|---|---|---|---|---| +| menu button | `gui/src/App.tsx:258` | KEEP | It is the only narrow-screen navigation entry. | Removing it blocks mobile navigation. | +| `opencodex` brand | `gui/src/App.tsx:263` | KEEP | It provides compact route context. | Minimal risk, but removing it weakens orientation. | +| session logout icon | `gui/src/App.tsx:265` | COLLAPSE-behind-account/menu | Logout is infrequent and visually indistinguishable among three adjacent icon-only actions. | Connected-runtime logout takes one extra step. | +| proxy stop icon | `gui/src/App.tsx:271` | KEEP | Emergency shutdown is high-value and confirmation-gated. | None if label and confirmation remain. | +| Codex restart icon | `gui/src/App.tsx:275` | DEMOTE-to-model-stale-banner-or-menu | Restart is usually relevant only after stale-state detection. | Manual restart is one click deeper outside stale conditions. | + +## Dashboard — Overview + +| element | source file:line | verdict | reason | risk | +|---|---|---|---|---| +| subtitle explaining “local proxy, providers, models” | `gui/src/pages/Dashboard.tsx:80` | REMOVE | The sidebar and page title already establish this context. | First-time users lose a generic orientation sentence. | +| `개요 / 활성 프로바이더 / 사용 가능한 모델` tabs | `gui/src/pages/Dashboard.tsx:54` | REMOVE | The latter two duplicate dedicated Providers and Models routes. | Users lose read-only shortcuts; replace with linked counts. | +| `서브에이전트 v1/base/v2` | `gui/src/pages/dashboard-overview-head.tsx:34` | DEMOTE-to-Subagents-settings | It is a mutation embedded in what should be a status overview. | Mode switching is no longer available from the landing screen. | +| `상태 온라인` | `gui/src/pages/dashboard-overview-head.tsx:73` | KEEP | Runtime reachability is the dashboard’s primary decision signal. | None. | +| `버전` | `gui/src/pages/dashboard-overview-head.tsx:79` | DEMOTE-to-status-tooltip | It matters only for mismatch/update diagnosis. | Exact version is less glanceable. | +| `가동 시간` | `gui/src/pages/dashboard-overview-head.tsx:80` | COLLAPSE-behind-runtime-details | Uptime rarely changes an operator decision unless diagnosing restarts. | Restart-loop detection requires opening details. | +| `프로바이더 9` | `gui/src/pages/dashboard-overview-head.tsx:81` | KEEP | A linked count quickly reveals whether expected capacity exists. | Count alone does not reveal unhealthy providers. | +| `토큰 (30일) / 커버리지` | `gui/src/pages/dashboard-overview-head.tsx:82` | DEMOTE-to-Usage | It duplicates the Usage report and dominates the health row with historical volume. | Cost-conscious users lose a landing-page summary. | +| reboot-protection status bar | `gui/src/pages/dashboard-overview-head.tsx:93` | KEEP | Startup protection is a real availability decision and links to remediation. | None. | +| `서브에이전트 위임 / 설정 열기` | `gui/src/pages/dashboard-overview-sections.tsx:127` | DEMOTE-to-Subagents | It duplicates the dedicated configuration surface. | One-click access from dashboard is lost. | +| `모델 동기화 / 지금 동기화` | `gui/src/pages/dashboard-overview-sections.tsx:206` | KEEP | Catalog drift requires an explicit corrective action. | None. | +| `Codex 실행 시 opencodex 시작` | `gui/src/pages/dashboard-overview-sections.tsx:487` | DEMOTE-to-Startup-safety | It is startup policy, not live health. | Users may overlook launcher behavior unless following startup status. | +| `웹 검색 사이드카` | `gui/src/pages/dashboard-overview-sections.tsx:509` | DEMOTE-to-Models/Advanced | This is model-routing configuration, not dashboard status. | Web-search operators need one additional navigation step. | +| `응답 실시간 스트리밍` | `gui/src/pages/dashboard-overview-sections.tsx:531` | COLLAPSE-behind-web-search-details | It is a secondary tuning flag. | Streaming behavior is less discoverable. | +| `비전 사이드카` | `gui/src/pages/dashboard-overview-sections.tsx:549` | DEMOTE-to-Models/Advanced | It is another routing configuration block occupying the primary overview. | Image-routing configuration becomes less immediate. | +| `쉐도우 호출 가로채기` | `gui/src/pages/dashboard-overview-sections.tsx:626` | DEMOTE-to-Models/Advanced | It is a specialized Codex compatibility feature. | Helper-call routing becomes harder to discover. | +| `메모리 관찰` summary | `gui/src/components/MemoryObservabilityCard.tsx:470` | COLLAPSE-behind-System/runtime-details | Memory is useful primarily when abnormal; normal RSS/JSC figures are monitoring noise. | Slow leaks may be noticed later unless warning thresholds remain visible. | + +## Dashboard — Active Providers + +| element | source file:line | verdict | reason | risk | +|---|---|---|---|---| +| entire `활성 프로바이더` tab | `gui/src/pages/Dashboard.tsx:56` | REMOVE | It is a less actionable duplicate of the Providers workspace. | Operators lose a compact read-only inventory. | +| provider count | `gui/src/pages/dashboard-providers-section.tsx:16` | DEMOTE-to-linked-dashboard-stat | The number is useful, but not a standalone page. | None if linked to Providers. | +| `이름` | `gui/src/pages/dashboard-providers-section.tsx:22` | DEMOTE-to-Providers-list | Names belong in the actionable provider workspace. | None. | +| `어댑터` | `gui/src/pages/dashboard-providers-section.tsx:27` | COLLAPSE-behind-provider-details | Adapter type is implementation detail for troubleshooting. | Advanced users need to open details. | +| `Base URL` | `gui/src/pages/dashboard-providers-section.tsx:28` | COLLAPSE-behind-provider-details | Raw endpoints have no routine decision value and visually dominate the table. | Endpoint mistakes become one click less visible. | +| default `모델` | `gui/src/pages/dashboard-providers-section.tsx:29` | DEMOTE-to-provider-details | It is actionable only in the provider editor. | Users lose at-a-glance default-model comparison. | + +## Dashboard — Available Models + +| element | source file:line | verdict | reason | risk | +|---|---|---|---|---| +| entire `사용 가능한 모델` tab | `gui/src/pages/Dashboard.tsx:57` | REMOVE | It duplicates the Models catalog without offering catalog actions. | Operators lose a fast read-only model lookup. | +| total model count | `gui/src/pages/dashboard-models-section.tsx:29` | DEMOTE-to-linked-dashboard-stat | The count is useful as health context, not as a separate page. | None if linked. | +| `모델 검색…` | `gui/src/pages/dashboard-models-section.tsx:37` | DEMOTE-to-Models | Search belongs where results can be enabled, disabled, or configured. | Dashboard-only lookup disappears. | +| provider accordion rows | `gui/src/pages/dashboard-models-section.tsx:51` | REMOVE | They repeat the same provider/model hierarchy already presented in Models. | Read-only browsing requires entering Models. | +| raw model-ID chips | `gui/src/pages/dashboard-models-section.tsx:68` | COLLAPSE-behind-provider-model-details | Raw IDs matter when configuring or copying, not in a health dashboard. | Copying an ID takes one extra action. | + +## Startup Safety + +| element | source file:line | verdict | reason | risk | +|---|---|---|---|---| +| explanatory subtitle | `gui/src/pages/Startup.tsx:322` | COLLAPSE-behind-help-tooltip | The page’s protected/at-risk result explains its purpose more directly. | New users lose conceptual context. | +| `대시보드로 돌아가기` | `gui/src/pages/Startup.tsx:325` | REMOVE | Global navigation already provides this route. | Keyboard users lose a redundant shortcut. | +| `새로고침` | `gui/src/pages/Startup.tsx:328` | KEEP | Rechecking after remediation is a direct operator action. | None. | +| runtime compatibility warning and `ocx sync` | `gui/src/pages/Startup.tsx:360` | KEEP | It identifies actionable version/config drift. | None. | +| protected/at-risk hero | `gui/src/pages/startup-sections.tsx:44` | KEEP | This is the page’s decisive answer. | None. | +| three cards: routing, protection, preference | `gui/src/pages/startup-sections.tsx:59` | COLLAPSE-behind-protection-details | They restate the hero in implementation terms during healthy operation. | Exact mechanism is less glanceable. | +| `보호 상태 상세` with platform | `gui/src/pages/startup-sections.tsx:99` | COLLAPSE-behind-hero-disclosure | Detailed service/shim state is needed mainly when risk exists. | Healthy users need one click to inspect mechanisms. | +| install/repair action for unhealthy service or shim | `gui/src/pages/startup-sections.tsx:112` | KEEP | It is the direct remediation path. | None. | +| `복구 방법` command list | `gui/src/pages/startup-sections.tsx:237` | COLLAPSE-behind-manual-recovery | Manual commands are fallback capability after one-click remediation. | CLI-oriented users need to expand it. | + +## Providers + +| element | source file:line | verdict | reason | risk | +|---|---|---|---|---| +| `프로바이더 추가` | `gui/src/pages/Providers.tsx:322` | KEEP | Adding capacity is a core provider task. | None. | +| left provider rail and ready/disabled status | `gui/src/pages/Providers.tsx:328` | KEEP | It is the primary inventory and selection mechanism. | None. | +| `프로바이더 개요` explanatory sentence | `gui/src/components/provider-workspace/ProviderOverviewDashboard.tsx:98` | REMOVE | The workspace structure already communicates that it manages providers. | Minimal onboarding loss. | +| `JSON 편집` | `gui/src/components/provider-workspace/ProviderOverviewDashboard.tsx:103` | COLLAPSE-behind-Advanced | Raw config editing is high-risk and rarely the first action. | Power users need one extra action; advanced access must remain obvious. | +| ready/setup/disabled summary cards | `gui/src/components/provider-workspace/ProviderOverviewDashboard.tsx:110` | KEEP | They summarize actionable provider health. | None. | +| attention list | `gui/src/components/provider-workspace/ProviderOverviewDashboard.tsx:120` | KEEP | Exceptions should remain more prominent than normal providers. | None. | +| full `사용량 제한` bars for every provider | `gui/src/components/provider-workspace/ProviderOverviewDashboard.tsx:146` | COLLAPSE-behind-usage-limits | Normal low-utilization quota rows consume most of the screen; surface only nearing-limit rows initially. | Operators lose passive comparison of all quotas. | +| `최근 사용` ranking | `gui/src/components/provider-workspace/ProviderOverviewDashboard.tsx:196` | DEMOTE-to-Usage/providers | Historical ranking duplicates Usage and does not help configure a provider. | A quick “most used” glance disappears from Providers. | + +## Models — Catalog + +| element | source file:line | verdict | reason | risk | +|---|---|---|---|---| +| stale-Codex banner and restart action | `gui/src/pages/Models.tsx:2215` | KEEP | It detects a real mismatch and gives the corrective action. | None. | +| `모델 / 콤보 / 라우팅 / 호환성` tabs | `gui/src/pages/models-tab-strip.tsx:19` | KEEP | They represent distinct model-management capabilities. | Removing them would bury major features. | +| long catalog subtitle | `gui/src/pages/Models.tsx:2226` | COLLAPSE-behind-help-tooltip | It explains nuanced cache/visibility semantics but pushes controls below the fold. | Users may misunderstand direct-ID behavior without opening help. | +| provider rail | `gui/src/pages/Models.tsx:2132` | KEEP | It is the simplest way to scope a large catalog. | None. | +| top-level `새 모델을 비활성화 상태로 추가` | `gui/src/pages/Models.tsx:2173` | COLLAPSE-behind-catalog-policy | This is a rare future-model policy, not a routine model-selection action. | Newly discovered models may surprise users who never inspect policy. | +| `별칭` global control | `gui/src/pages/Models.tsx:2175` | COLLAPSE-behind-Advanced | Alias management is specialized and already has per-provider controls. | Users need an extra action to audit all aliases. | +| shadow-call controls | `gui/src/pages/Models.tsx:2173` | COLLAPSE-behind-Codex-advanced | They are product-specific compatibility controls. | Helper-call overrides are less discoverable. | +| subagent mode `v1/base/v2` | `gui/src/pages/Models.tsx:2173` | DEMOTE-to-Subagents-settings | It belongs with delegation configuration. | Cross-surface users lose immediate mode visibility. | +| global `기본 창 / 상한` | `gui/src/pages/Models.tsx:2173` | COLLAPSE-behind-context-settings | Context limits are advanced tuning and dangerous to change casually. | Operators diagnosing truncation need one extra click. | +| picker-order explanatory paragraph | `gui/src/pages/Models.tsx:1752` | COLLAPSE-behind-info-tooltip | It is reference documentation, not a decision control. | Ordering behavior is less immediately explicit. | +| `모두 접기 / 모두 펼치기` | `gui/src/pages/Models.tsx:1759` | KEEP | It directly manages information density in a large catalog. | None. | +| provider header actions: aliases, custom model, all on/off, context | `gui/src/pages/Models.tsx:2192` | COLLAPSE-behind-provider-action-menu | Repeating six controls on every provider creates the screen’s largest control wall. | Bulk actions require opening a per-provider menu. | +| individual model rows/toggles | `gui/src/pages/Models.tsx:2192` | KEEP | Visibility selection is the catalog’s primary capability. | None. | + +## Models — Combos + +| element | source file:line | verdict | reason | risk | +|---|---|---|---|---| +| combos tab | `gui/src/pages/models-tab-strip.tsx:21` | KEEP | Failover/load-distribution is a distinct operator capability. | None. | +| tab subtitle | `gui/src/pages/Models.tsx:2226` | COLLAPSE-behind-help-tooltip | Existing combos explain themselves; onboarding text is primarily needed for an empty state. | First-time comprehension depends more on the empty state. | +| duplicate `콤보 추가` in rail and `콤보 만들기` in editor | `gui/src/components/ComboWorkspace.tsx:108` | REMOVE | The empty workspace presents multiple labels for the same creation action. | Ensure one retained CTA focuses or opens the complete form. | +| combo search with zero combos | `gui/src/components/ComboWorkspace.tsx:112` | REMOVE | Search has no decision value until at least one combo exists. | None; show it conditionally once combos exist. | +| `콤보 ID` | `gui/src/components/ComboWorkspace.tsx:197` | KEEP | Stable identity is required to create and address a combo. | None. | +| public model name and native OpenAI alias | `gui/src/components/ComboWorkspace.tsx:197` | COLLAPSE-behind-identity-advanced | Most users can accept `combo/` and do not need namespace/alias mechanics initially. | Advanced naming is less discoverable. | +| display name | `gui/src/components/ComboWorkspace.tsx:197` | COLLAPSE-behind-identity-advanced | It is conditional on alias behavior rather than core failover setup. | Native-alias users need to expand the section. | +| strategy and ordered targets | `gui/src/components/ComboWorkspace.tsx:197` | KEEP | These define combo behavior and are the primary decisions. | None. | +| default reasoning level | `gui/src/components/ComboWorkspace.tsx:197` | COLLAPSE-behind-behavior-advanced | Target defaults are usually sufficient. | Users may miss a useful normalization override. | +| multimodal/adaptive reasoning toggles | `gui/src/components/ComboWorkspace.tsx:197` | COLLAPSE-behind-capabilities | These are compatibility constraints, not minimum combo creation inputs. | Misconfigured heterogeneous targets may need more deliberate inspection. | + +## Models — Routing + +| element | source file:line | verdict | reason | risk | +|---|---|---|---|---| +| `라우팅 (beta)` | `gui/src/pages/models-tab-strip.tsx:22` | KEEP | Explicit beta labeling correctly bounds expectations. | None. | +| `프로필 만들기` | `gui/src/pages/RoutingProfiles.tsx:624` | KEEP | Creating a policy is the primary task. | None. | +| profile cards with model and revision | `gui/src/pages/RoutingProfiles.tsx:624` | KEEP | Operators need to choose the policy under inspection. | None. | +| revision badge | `gui/src/pages/RoutingProfiles.tsx:638` | COLLAPSE-behind-profile-details | Revision is audit metadata, not a selection criterion for most operators. | Concurrent-edit diagnosis is less immediate. | +| `드라이런 평가` shown before any profile exists | `gui/src/pages/RoutingProfiles.tsx:1015` | COLLAPSE-behind-selected-profile | The disabled form is dead visual weight until a profile is selected. | Users may not discover dry-run until selecting a profile. | +| context/tools/image/structured inputs | `gui/src/pages/RoutingProfiles.tsx:1017` | KEEP | These are the minimum meaningful routing simulation inputs. | None. | +| `라우팅 분석` empty panel | `gui/src/pages/RoutingProfiles.tsx:1097` | REMOVE | “No analysis yet” contributes no decision value before a profile has traffic. | Users lose advance awareness that analytics exists; reveal after first data or via details. | +| p50/p95/p99/cooldown/confidence badge wall | `gui/src/pages/RoutingProfiles.tsx:1101` | COLLAPSE-behind-analytics-details | Default view should show success/fallback and anomalies; latency distribution is diagnostic depth. | Performance tuning requires expansion. | + +## Models — Compatibility + +| element | source file:line | verdict | reason | risk | +|---|---|---|---|---| +| compatibility tab | `gui/src/pages/models-tab-strip.tsx:23` | KEEP | Compatibility evidence prevents unsafe model assumptions. | None. | +| refresh button | `gui/src/pages/CompatibilityMatrix.tsx:460` | KEEP | Evidence freshness is operationally meaningful. | None. | +| community-evidence panel | `gui/src/pages/CompatibilityMatrix.tsx:473` | COLLAPSE-behind-community-evidence | Community information is secondary to local/production evidence. | Users may overlook useful external evidence. | +| status cards | `gui/src/pages/CompatibilityMatrix.tsx:478` | KEEP | They summarize whether compatibility evidence is usable. | None. | +| layer/verdict/subject filters | `gui/src/pages/CompatibilityMatrix.tsx:480` | KEEP | Filtering is necessary for a large evidence matrix. | None. | +| compatibility matrix | `gui/src/pages/CompatibilityMatrix.tsx:520` | KEEP | It is the route’s primary decision surface. | None. | +| second full `verdicts` table | `gui/src/pages/CompatibilityMatrix.tsx:553` | COLLAPSE-behind-list-view | It repeats matrix contents in another representation and doubles page length. | Table-oriented users need to switch views. | +| selected-verdict detail pane | `gui/src/pages/CompatibilityMatrix.tsx:613` | KEEP | Evidence details preserve explainability without crowding every row. | None. | + +## Subagents + +| element | source file:line | verdict | reason | risk | +|---|---|---|---|---| +| `추천 / 모델 / 설정` sticky section tabs | `gui/src/components/subagents-workspace/SubagentsWorkspace.tsx:78` | KEEP | They organize three related jobs in one long page. | None. | +| instructional sentence mentioning `spawn_agent` | `gui/src/components/subagents-workspace/SubagentsWorkspace.tsx:97` | COLLAPSE-behind-info-tooltip | It is durable documentation repeated above a self-explanatory ranked list. | First-time users may not understand dual picker/delegation effects. | +| selected 1–5 ranked list | `gui/src/components/subagents-workspace/SubagentsWorkspace.tsx:105` | KEEP | The order directly changes model preference. | None. | +| separate `저장` button | `gui/src/components/subagents-workspace/SubagentsWorkspace.tsx:145` | KEEP | It makes a multi-row reorder transaction explicit. | Auto-save would make accidental reorder harder to undo. | +| full available-model list | `gui/src/components/subagents-workspace/SubagentsWorkspace.tsx:152` | COLLAPSE-behind-모델-chooser | It should not occupy the first viewport once five recommendations are complete. | Adding/removing candidates takes one disclosure action. | +| `먼저 부를 모델` | `gui/src/components/subagents-workspace/SubagentDelegationSection.tsx:66` | KEEP | It is a clear primary delegation decision. | None. | +| `Codex 설정에도 기본값으로 저장` | `gui/src/components/subagents-workspace/SubagentDelegationSection.tsx:99` | COLLAPSE-behind-Advanced | Persistence scope is an expert setting. | Users may assume dashboard state applies to new sessions. | +| `일 나누는 방법 알려주기` | `gui/src/components/subagents-workspace/SubagentDelegationSection.tsx:116` | COLLAPSE-behind-Advanced | Prompt-injection behavior is implementation-level tuning. | Delegation behavior may be harder to explain. | +| `울트라 모드` and custom text editor | `gui/src/components/subagents-workspace/SubagentDelegationSection.tsx:133` | COLLAPSE-behind-Advanced-policy | It changes broad delegation policy and exposes raw policy text. | Power users need to expand it; active status should remain visible. | + +## Logs + +Visual evidence is invalid for this route; verdicts below come from source. + +| element | source file:line | verdict | reason | risk | +|---|---|---|---|---| +| `로그 / 디버그` tabs | `gui/src/pages/Logs.tsx:550` | KEEP | Historical request inspection and live debug capture are distinct jobs. | None. | +| `자동 새로고침` | `gui/src/pages/Logs.tsx:543` | KEEP | Freshness materially changes incident diagnosis. | None. | +| subtitle | `gui/src/pages/Logs.tsx:596` | REMOVE | The table and filters already make the request-log purpose obvious. | Minimal onboarding loss. | +| surface segmented filter | `gui/src/pages/Logs.tsx:598` | KEEP | It is the fastest way to isolate client-specific failures. | None. | +| intercepted-only checkbox | `gui/src/pages/Logs.tsx:621` | COLLAPSE-behind-more-filters | It is a specialized diagnostic predicate. | Shadow-call debugging needs one extra click. | +| conversation and model filters | `gui/src/pages/Logs.tsx:629` | KEEP | They directly narrow incidents and sessions. | None. | +| default table columns: time, model, provider, status, duration | `gui/src/pages/Logs.tsx:732` | KEEP | These answer what ran, where, whether it worked, and how long it took. | None. | +| tokens, tok/s, estimated cost, effort, request ID all visible | `gui/src/pages/Logs.tsx:735` | COLLAPSE-behind-column-picker | Ten default columns exceed routine scan needs; preserve them as optional columns/detail fields. | Performance/cost comparison requires enabling columns. | +| per-row `상세` | `gui/src/pages/Logs.tsx:833` | KEEP | It is the correct disclosure point for route, attempt, usage, and raw data. | None. | +| raw JSON | `gui/src/pages/Logs.tsx:1140` | KEEP | It is already correctly collapsed behind `
`. | None. | + +## Logs — Debug + +Visual evidence is invalid for this route; verdicts below come from source. + +| element | source file:line | verdict | reason | risk | +|---|---|---|---|---| +| debug subtitle | `gui/src/pages/debug-settings-panel.tsx:119` | COLLAPSE-behind-help-tooltip | Debug users generally know why they opened the route. | First-time users lose guidance. | +| refresh | `gui/src/pages/debug-settings-panel.tsx:105` | KEEP | Manual re-read is essential when follow is disabled. | None. | +| follow checkbox | `gui/src/pages/debug-settings-panel.tsx:113` | KEEP | It controls live-tail behavior directly. | None. | +| four capture switches | `gui/src/pages/debug-settings-panel.tsx:28` | KEEP | Operators must explicitly choose potentially sensitive or expensive debug streams. | None. | +| reset button | `gui/src/pages/debug-settings-panel.tsx:43` | KEEP | It quickly returns debugging to a safe baseline. | None. | +| second stream selector row | `gui/src/pages/debug-settings-panel.tsx:48` | COLLAPSE-behind-active-stream-dropdown | It duplicates the enabled-stream concepts in another horizontal control group. | Switching streams is one compact selector instead of direct buttons. | +| empty debug explanation | `gui/src/pages/debug-log-viewer.tsx:25` | KEEP | It explains why no log viewer is shown and what must be enabled. | None. | +| live raw log viewer | `gui/src/pages/debug-log-viewer.tsx:43` | KEEP | It is the route’s core diagnostic output. | None. | + +## Usage + +| element | source file:line | verdict | reason | risk | +|---|---|---|---|---| +| surface and date-range filters | `gui/src/pages/Usage.tsx:811` | KEEP | They define the report being inspected. | None. | +| subtitle | `gui/src/pages/Usage.tsx:815` | COLLAPSE-behind-info-tooltip | The missing-data caveat matters, but not as permanent header copy. | Users may initially assume missing usage is zero. | +| section tabs with counts | `gui/src/pages/Usage.tsx:722` | KEEP | They provide navigation through a long report. | None. | +| requests + measured cards | `gui/src/pages/Usage.tsx:287` | COLLAPSE-behind-coverage-summary | The pair is meaningful mainly for coverage diagnosis, not as two primary KPIs. | Data-quality gaps become less immediately visible. | +| total tokens | `gui/src/pages/Usage.tsx:289` | KEEP | It is the core consumption measure. | None. | +| cache-hit and cache-write cards | `gui/src/pages/Usage.tsx:290` | COLLAPSE-behind-token-breakdown | Cache accounting is optimization detail. | Cache-efficiency analysis takes one extra step. | +| coverage | `gui/src/pages/Usage.tsx:299` | KEEP | It qualifies every aggregate on the page. | None. | +| active days | `gui/src/pages/Usage.tsx:300` | REMOVE | The selected 7/30-day range and heatmap already communicate activity continuity. | Users lose a compact count. | +| API list-price estimate and disclaimer | `gui/src/pages/Usage.tsx:302` | COLLAPSE-behind-cost-estimate | It is explicitly not billing and can dwarf more reliable usage signals. | Cost comparison is less prominent. | +| annual heatmap | `gui/src/pages/Usage.tsx:400` | COLLAPSE-behind-activity-history | It consumes substantial vertical space while rarely affecting proxy operation. | Long-term usage patterns need expansion. | +| models and providers tables | `gui/src/pages/Usage.tsx:691` | KEEP | They answer where consumption occurred. | None. | +| detailed coverage panel | `gui/src/pages/Usage.tsx:707` | COLLAPSE-behind-coverage | Keep the percentage primary; disclose reported/estimated/unreported composition. | Data provenance takes one extra action. | + +## Storage + +| element | source file:line | verdict | reason | risk | +|---|---|---|---|---| +| `다시 스캔` | `gui/src/pages/Storage.tsx:1414` | KEEP | Storage state can change after cleanup and needs explicit refresh. | None. | +| subtitle | `gui/src/pages/Storage.tsx:1419` | KEEP | The promise not to disturb active sessions is an important safety contract. | None. | +| `CODEX_HOME` path and last-scan timestamp | `gui/src/pages/Storage.tsx:1421` | COLLAPSE-behind-scan-details | These are diagnostic metadata rather than cleanup decisions. | Multi-home users must open details to confirm target. | +| bucket rail with size/count | `gui/src/components/storage-workspace/StorageWorkspace.tsx:535` | KEEP | It identifies where disk usage is concentrated. | None. | +| total bytes and files | `gui/src/components/storage-workspace/StorageWorkspace.tsx:614` | KEEP | They establish cleanup scale. | None. | +| repeated home-path summary card | `gui/src/components/storage-workspace/StorageWorkspace.tsx:623` | REMOVE | The same path is already available in page scan details and does not merit a KPI card. | Target path is less visible if scan details are also collapsed. | +| ten largest files | `gui/src/components/storage-workspace/StorageWorkspace.tsx:643` | COLLAPSE-behind-largest-files | File-level paths are diagnostic depth after bucket-level triage. | Manual forensic cleanup needs expansion. | +| bucket oldest/newest timestamps | `gui/src/components/storage-workspace/StorageWorkspace.tsx:575` | COLLAPSE-behind-bucket-details | They are useful for investigation, not the initial storage decision. | Age-based cleanup decisions take one extra action. | +| cleanup policy/quarantine tabs | `gui/src/pages/Storage.tsx:1278` | KEEP | Policy and recoverable deletion are safety-critical capabilities. | None. | +| manual archived-session cleanup | `gui/src/pages/Storage.tsx:1310` | COLLAPSE-behind-manual-cleanup | Automatic policy should be primary; manual cleanup is fallback. | Immediate archive cleanup is one click deeper. | + +## Codex Settings + +| element | source file:line | verdict | reason | risk | +|---|---|---|---|---| +| `다중 인증 / 프롬프트` tabs | `gui/src/pages/CodexSet.tsx:43` | KEEP | They are unrelated capabilities and should remain separated. | None. | +| `Codex Spark 할당량` | `gui/src/components/codex-account-pool-main-card.tsx:218` | COLLAPSE-behind-account-display-options | It controls visibility of a special quota rather than account operation. | Spark users may overlook the hidden quota. | +| `한도 도달 계정 일시 중지` | `gui/src/components/codex-account-pool-main-card.tsx:234` | KEEP | It is a high-value bulk recovery action. | None. | +| `할당량 새로고침` | `gui/src/components/codex-account-pool-main-card.tsx:242` | KEEP | Quota freshness directly affects routing decisions. | None. | +| account email, plan, next/current, quota bars | `gui/src/components/codex-account-pool-cards.tsx:79` | KEEP | These are the minimum facts needed to manage account rotation. | None. | +| repeated email + plan + truncated account ID line | `gui/src/components/codex-account-pool-cards.tsx:146` | COLLAPSE-behind-account-details | It duplicates the visible identity; raw ID is troubleshooting detail. | Copying an account ID takes one extra action. | +| `별칭 편집` on every row | `gui/src/components/codex-account-pool-cards.tsx:133` | COLLAPSE-behind-row-overflow-menu | It is infrequent and repeats as a prominent button across the pool. | Alias editing takes one extra click. | +| delete `×` | `gui/src/components/codex-account-pool-cards.tsx:136` | COLLAPSE-behind-row-overflow-menu | Destructive account removal should not sit as an unlabeled visual peer to routing controls. | Removal is less immediate but safer. | +| selection-priority control | `gui/src/components/codex-account-pool-cards.tsx:148` | COLLAPSE-behind-routing-details | Most users use defaults; priority is advanced pool tuning. | Priority conflicts may be harder to inspect. | +| explanatory paragraph repeated for each priority selector | `gui/src/components/codex-account-pool-cards.tsx:148` | REMOVE | One shared tooltip/help disclosure is sufficient. | No capability loss if the explanation remains centrally accessible. | + +## Integrations + +| element | source file:line | verdict | reason | risk | +|---|---|---|---|---| +| page subtitle | `gui/src/pages/Integrations.tsx:133` | COLLAPSE-behind-help-tooltip | The route and client states already communicate the job. | New users lose a short orientation sentence. | +| 18-tab strip | `gui/src/pages/Integrations.tsx:142` | COLLAPSE-behind-client-picker | Showing every supported client before relevance is known is the page’s largest noise source. | Direct one-click navigation to rare clients is lost; hashes must remain supported. | +| overview tab | `gui/src/pages/integrations/integration-tabs.ts:31` | KEEP | It is the appropriate default summary. | None. | +| detected/configured/update counts | `gui/src/pages/integrations/IntegrationsOverview.tsx:517` | KEEP | They summarize actionable integration state. | None. | +| `마지막 변경` | `gui/src/pages/integrations/IntegrationsOverview.tsx:541` | COLLAPSE-behind-history | A timestamp alone rarely changes the next action. | Recent unexpected changes are less glanceable. | +| `모두 해제…` | `gui/src/pages/integrations/IntegrationsOverview.tsx:545` | DEMOTE-to-bulk-actions-menu | A broad destructive mutation should not be a permanent summary-row peer. | Emergency bulk disable takes one extra action. | +| API key row | `gui/src/pages/integrations/IntegrationsOverview.tsx:568` | KEEP | Credentials are a distinct integration prerequisite. | None. | +| onboarding paragraph about backups/provider blocks | `gui/src/pages/integrations/IntegrationsOverview.tsx:571` | COLLAPSE-behind-how-it-works | It is important reference copy, but not a repeated operational decision. | Users may not understand backup behavior before first apply; show it in confirmation. | +| cards for applied or update-needed clients | `gui/src/pages/integrations/IntegrationsOverview.tsx:596` | KEEP | These states require monitoring or action. | None. | +| cards for every uninstalled client | `gui/src/pages/integrations/IntegrationsOverview.tsx:596` | COLLAPSE-behind-add-client | Unsupported/uninstalled clients should be discoverable without dominating routine operation. | Users may not notice a supported integration until opening “Add client.” | +| config filesystem paths on overview cards | `gui/src/pages/integrations/IntegrationsOverview.tsx:596` | COLLAPSE-behind-client-details | Paths are implementation detail useful during troubleshooting. | Manual file verification takes one extra action. | +| rollback history | `gui/src/pages/integrations/IntegrationsOverview.tsx:619` | COLLAPSE-behind-recent-changes | Keep a visible warning/recent reversible operation, but hide normal chronology. | Cross-client audit history becomes less prominent. | + +## Top 15 highest-noise removals + +1. Remove the Dashboard `Active providers` tab; it duplicates Providers (`gui/src/pages/Dashboard.tsx:56`). +2. Remove the Dashboard `Available models` tab; it duplicates Models (`gui/src/pages/Dashboard.tsx:57`). +3. Replace Integrations’ 18 always-visible tabs with a relevant-client picker (`gui/src/pages/Integrations.tsx:142`). +4. Hide all uninstalled integration cards behind `Add client` (`gui/src/pages/integrations/IntegrationsOverview.tsx:596`). +5. Move repeated per-provider Models controls into a provider action menu (`gui/src/pages/Models.tsx:2192`). +6. Remove GitHub star from persistent navigation (`gui/src/components/sidebar-github-row.tsx:136`). +7. Remove the persistent GitHub repository row (`gui/src/components/sidebar-github-row.tsx:131`). +8. Remove the second full compatibility verdict table from default flow (`gui/src/pages/CompatibilityMatrix.tsx:553`). +9. Remove the empty Routing Analytics panel until data exists (`gui/src/pages/RoutingProfiles.tsx:1097`). +10. Remove `Active days`; the range and heatmap already express it (`gui/src/pages/Usage.tsx:300`). +11. Remove the duplicate Storage home-path KPI card (`gui/src/components/storage-workspace/StorageWorkspace.tsx:623`). +12. Remove the duplicate Combos creation CTA in the zero-combo state (`gui/src/components/ComboWorkspace.tsx:108`). +13. Remove combo search while the combo count is zero (`gui/src/components/ComboWorkspace.tsx:112`). +14. Remove repeated account-priority explanatory copy from every Codex account row (`gui/src/components/codex-account-pool-cards.tsx:148`). +15. Remove generic page subtitles where the title and primary surface already communicate the job, beginning with Dashboard and Logs (`gui/src/pages/Dashboard.tsx:80`, `gui/src/pages/Logs.tsx:596`). + +## Explicitly do not touch + +- Runtime offline/error states and retry actions: they carry immediate recovery value. +- Startup protected/at-risk status and remediation: this prevents reboot-time routing failures. +- Provider attention/reauthentication indicators: exceptions are exactly what the minimal UI should emphasize. +- Model visibility toggles and stale-catalog warning: these directly determine what Codex can select. +- Combo target ordering and strategy: collapsing these would hide the combo’s actual behavior. +- Routing dry-run inputs and result evidence after a profile is selected. +- Log status, model, provider, time, duration, and row detail disclosure. +- Usage coverage percentage: aggregates without provenance are misleading. +- Storage quarantine and confirmation flows: recoverability and destructive-action safety outrank visual minimalism. +- Codex account quota, current/next account, pause, and reauthentication controls. +- Integration applied/update-needed/conflict states and confirmation dialogs. +- Accessibility semantics already present in tabs, disclosures, dialogs, focus restoration, and icon labels. + +REVIEWER: claude-fable-5-1 + + + +## R2 — full review + +I have everything I need. Writing up the full review now. + +# Dashboard minimalism review — opencodex GUI @ 664d80c76 + +**Evidence caveat first.** Four of the sixteen text/interactive captures are byte-identical duplicates of other routes, so those pages were never actually captured. `models_compatibility_text.txt` == `usage_text.txt` (md5 `9c6df39d…`), `subagents_text.txt` == `storage_text.txt` (`2052bcf5…`), `logs_text.txt` is the Codex-Set multi-auth page, and `logs_debug_text.txt` == `integrations_text.txt` (`afd25efb…`). The `_interactive.txt` dumps for those four *are* correct, and I verified the mismatch against the PNGs. Verdicts for Compatibility, Subagents, Logs, and Debug are therefore grounded in source + interactive refs, not screenshots — worth recapturing before anyone acts on them. + +My operator model throughout: someone running a local proxy who needs to answer *is it up, is my traffic going where I think, am I about to hit a limit, and how do I fix it*. Everything else is reference material and should be one click away, not on the page. + +## Sidebar and topbar + +| element | source | verdict | reason | risk | +|---|---|---|---|---| +| `opencodex` + `v2.42.0` brand | [App.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/App.tsx:246) | KEEP | Version is the single most-asked support question and it is live from `/healthz`. | none | +| 9 nav rows (대시보드…연동) | [App.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/App.tsx:62) | KEEP | One row per page, already deduplicated once. | none | +| 언어 `Select` in footer | [App.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/App.tsx:325) | DEMOTE-to-icon-menu | A locale is set once per install and then occupies a full-width footer row forever. | Discoverability drops for first-run users; keep it in the same footer cluster as theme. | +| 시스템 / theme toggle | [App.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/App.tsx:335) | COLLAPSE-behind-icon-only | Same argument, and the `mode` word adds nothing the icon does not. | Screen-reader label already exists on the button; keep it. | +| `프록시` label + stop/restart orbs | [App.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/App.tsx:339) | KEEP orbs, REMOVE label | The two orbs are the only destructive controls in the shell and must stay reachable; the word "프록시" above them is decoration. | Orbs already carry `aria-label` + `title`, so nothing is lost. | +| GitHub link row | [sidebar-github-row.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/components/sidebar-github-row.tsx:132) | DEMOTE-to-footer-icon | A repo link is not an operating control; it currently gets equal weight to the proxy kill switch. | None — the same URL is the star button's fallback. | +| ★ star orb | [sidebar-github-row.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/components/sidebar-github-row.tsx:136) | REMOVE from chrome | This is a promotion ask polling `gh` every 5 min on every page; it carries zero operator value. Note `AGENTS.md` treats starring as a user-consent action, which reinforces that it should not be ambient UI. | Maintainer loses a star funnel. Keep the action inside the update dialog if it must live somewhere. | +| ⬇ update orb + dot | [sidebar-github-row.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/components/sidebar-github-row.tsx:147) | KEEP | "Am I current?" is a real operator question and the dot is the only ambient signal for it. | none | +| Mobile topbar duplicate orbs | [App.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/App.tsx:264) | KEEP | Sidebar is off-canvas at that width; these are not duplicates in practice. | none | + +## Dashboard — 개요 + +| element | source | verdict | reason | risk | +|---|---|---|---|---| +| "대시보드" h2 + subtitle | [Dashboard.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/Dashboard.tsx:78) | REMOVE subtitle | The sidebar row is already highlighted; the sentence restates the product description. | Nothing; the h2 stays. | +| 개요 / 활성 프로바이더 / 사용 가능한 모델 tabs | [Dashboard.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/Dashboard.tsx:54) | COLLAPSE-behind-Providers-and-Models-pages | Both tabs are strictly-poorer copies of full pages that already exist in the sidebar (see the two sections below). | Loses a same-page glance; the counts stay in the stat row. | +| 서브에이전트 `v1 / base / v2` radio group | [dashboard-overview-head.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/dashboard-overview-head.tsx:52) | DEMOTE-to-Subagents-page | A three-way mode switch is the highest-consequence control on the page and it is sitting in a stat cell shaped like a read-only metric. The identical control already exists on Models. | Users who learned it here must relearn; mitigate by leaving the resolved mode as text. | +| ⓘ next to 서브에이전트 | [dashboard-overview-head.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/dashboard-overview-head.tsx:37) | KEEP | The modes are genuinely non-obvious; this is disclosure done right. | none | +| 상태 / 온라인 | [dashboard-overview-head.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/dashboard-overview-head.tsx:73) | KEEP | The reason the page exists. | none | +| 버전 `2.42.0` | [dashboard-overview-head.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/dashboard-overview-head.tsx:79) | REMOVE | Byte-identical to the sidebar brand version 200px away, from the same `/healthz`. | none | +| 가동 시간 `1시간 42분` | [dashboard-overview-head.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/dashboard-overview-head.tsx:80) | DEMOTE-to-tooltip-on-상태 | Uptime only matters when it is *short* (did it crash?); as a standing number it is trivia. | A restart-detector loses a glance; the tooltip keeps it. | +| 프로바이더 `9` | [dashboard-overview-head.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/dashboard-overview-head.tsx:81) | KEEP | Cheap, and a drop to 0 is diagnostic. | none | +| 토큰 (30일) + 커버리지 99% | [dashboard-overview-head.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/dashboard-overview-head.tsx:82) | KEEP value, DEMOTE coverage | 51.5B tokens is a real signal; "커버리지 99%" is a measurement-quality caveat that belongs on Usage where it is already explained in full. | Users misreading totals as exact; keep coverage as a tooltip. | +| 재부팅 보호 health bar | [dashboard-overview-head.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/dashboard-overview-head.tsx:95) | KEEP | Highest-value row on the page: one line, actionable, deep-links to Startup. | none | +| 프로젝트 설정 경고 block | [dashboard-overview-head.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/dashboard-overview-head.tsx:116) | KEEP | Conditional and only renders when broken. | none | +| 서브에이전트 위임 / 없음 / 설정 열기 | [dashboard-overview-sections.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/dashboard-overview-sections.tsx:120) | DEMOTE-to-Subagents-page | A whole panel whose steady state is "없음" plus a link to the page that owns it. | The link is the only affordance lost; the sidebar row replaces it. | +| 모델 동기화 + hint + 지금 동기화 | [dashboard-overview-sections.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/dashboard-overview-sections.tsx:206) | KEEP button, REMOVE hint | Sync is a genuine recurring action; the two-line explanation is read once. | Move the hint to the button's `title`. | +| Codex 실행 시 opencodex 시작 toggle + 2-line hint | [dashboard-overview-sections.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/dashboard-overview-sections.tsx:487) | DEMOTE-to-Startup-page | Its own hint tells you to go verify on Startup — that is the page that owns launch behaviour, and it already renders the shim row. | Two places to change one setting becomes one; bookmark holders lose nothing. | +| 웹 검색 사이드카 card | [dashboard-overview-sections.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/dashboard-overview-sections.tsx:509) | COLLAPSE-behind-고급 disclosure | Set once at install; occupies a permanent half-width card thereafter. | Rarely-changed setting gets one extra click. | +| 응답 실시간 스트리밍 toggle | [dashboard-overview-sections.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/dashboard-overview-sections.tsx:533) | COLLAPSE-behind-same | Sub-setting of a set-once setting. | none beyond the above | +| 비전 사이드카 card + effort | [dashboard-overview-sections.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/dashboard-overview-sections.tsx:549) | COLLAPSE-behind-same | Same lifecycle as web search; pair them in one "사이드카" section. | none | +| 고급 설정 popover (max/timeout) | [dashboard-overview-sections.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/dashboard-overview-sections.tsx:588) | KEEP | Already correctly collapsed. | none | +| 쉐도우 호출 가로채기 panel | [dashboard-overview-sections.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/dashboard-overview-sections.tsx:626) | REMOVE (duplicate) | The identical toggle + model select + ⓘ + `⚠ 5.6-luna` badge is rendered on Models at [Models.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/Models.tsx:1594). Two live editors for one setting is a consistency bug waiting to happen. | Dashboard-only users lose the control; Models is the honest home since it is about model rewriting. | +| 추론 상한 panel | [dashboard-overview-sections.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/dashboard-overview-sections.tsx:37) | COLLAPSE-behind-고급 | Conditional on v2 already, but still a full panel for two rarely-touched selects. | none | +| 메모리 관찰 card (whole) | [dashboard-overview-panels.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/dashboard-overview-panels.tsx:21) | COLLAPSE-behind-single-pressure-row | This is developer telemetry on the operator's home screen. It polls every 5s and renders RSS, JS heap, JSC heap, arena, and a growth rate. | Leak-hunting gets slower; keep the pressure bar + 상세 정보 so every number stays reachable. | +| 진행 중 요청 `3` | [MemoryObservabilityCard.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/components/MemoryObservabilityCard.tsx:425) | DEMOTE-to-overview-stat-row | This is the one genuinely operator-facing number in the card — it belongs next to 상태, not inside a memory panel. | none if relocated | +| 작업 완료 후 재시작 button | [MemoryObservabilityCard.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/components/MemoryObservabilityCard.tsx:431) | KEEP | Drain-and-restart is materially different from the sidebar stop orb and is confirm-gated. | none | +| rss / 임계값의 28% pressure bar | [MemoryObservabilityCard.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/components/MemoryObservabilityCard.tsx:443) | KEEP | The one memory fact with a threshold attached, so the only one that is actionable. | none | +| 상주 메모리 / JS 힙 / JSC 힙 / 시간당 변화 | [MemoryObservabilityCard.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/components/MemoryObservabilityCard.tsx:451) | COLLAPSE-behind-상세-정보 | Four monospace byte counts nobody acts on; the growth tone already escalates into the pressure bar. | Move them into the existing `
` at line 470 — zero capability lost. | +| 상세 정보 `
` | [MemoryObservabilityCard.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/components/MemoryObservabilityCard.tsx:470) | KEEP | Model example for the rest of the page. | none | + +## Dashboard — 활성 프로바이더 tab + +| element | source | verdict | reason | risk | +|---|---|---|---|---| +| Whole tab (9-row table) | [dashboard-providers-section.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/dashboard-providers-section.tsx:20) | REMOVE | The Providers page shows the same nine providers *plus* quota bars, attention list, and per-provider actions. This tab is a read-only subset with no path to act on anything in it. | Loses a compact table; add an "adapter/baseURL" column toggle to the Providers rail if anyone misses it. | +| `Base URL` column | [dashboard-providers-section.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/dashboard-providers-section.tsx:28) | DEMOTE-to-provider-detail | Raw endpoint URLs are configuration trivia except when debugging a specific provider — which is exactly when you are in its detail view. | Local-endpoint users (`http://100.100.125.116:8081/v1`) lose an at-a-glance check. | +| `어댑터` chip column | [dashboard-providers-section.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/dashboard-providers-section.tsx:27) | DEMOTE-to-provider-detail | `openai-responses` vs `openai-chat` matters at setup time only. | same | + +## Dashboard — 사용 가능한 모델 tab + +| element | source | verdict | reason | risk | +|---|---|---|---|---| +| Whole tab | [dashboard-models-section.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/dashboard-models-section.tsx:27) | REMOVE | Shows 96 models grouped by provider with a search box — the Models page shows the same grouping with visibility toggles, aliases, caps, and per-model detail. | Loses a read-only browser; the `96` count survives in the stat row. | +| 모델 검색 input | [dashboard-models-section.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/dashboard-models-section.tsx:39) | REMOVE with tab | Duplicate of the Models rail. | none | + +## 시작 안전성 (startup) + +| element | source | verdict | reason | risk | +|---|---|---|---|---| +| 시작 안전성 h2 + subtitle | [Startup.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/Startup.tsx:321) | KEEP | Genuinely non-obvious page; the subtitle earns its line here. | none | +| 대시보드로 돌아가기 | [Startup.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/Startup.tsx:325) | REMOVE | The sidebar is permanently visible and has a 대시보드 row. This is a back button in an app with no back problem. | Deep-linked arrivals lose one click; browser Back still works. | +| 새로고침 | [Startup.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/Startup.tsx:328) | KEEP | State changes out-of-band after `ocx service repair`. | none | +| Codex runtime clamp notice | [Startup.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/Startup.tsx:366) | KEEP | Conditional, explains a live capability loss, ships its own fix command. | none | +| 재부팅 보호됨 hero + h3 + detail | [startup-sections.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/startup-sections.tsx:44) | KEEP badge, COLLAPSE prose | Badge + heading + paragraph say the same thing three times when green. | Keep the paragraph for `at-risk`/`error` where it carries the diagnosis. | +| Codex 라우팅 / 재부팅 보호 / 필요 시 자동 시작 grid | [startup-sections.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/startup-sections.tsx:59) | COLLAPSE-behind-보호-상태-상세 | Three stats that restate the hero when protected. | Nothing if folded into the details panel below them. | +| 보호 상태 상세 + `darwin` | [startup-sections.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/startup-sections.tsx:99) | KEEP | Per-mechanism status with install/repair buttons — the actionable core. | none | +| 백그라운드 서비스 / shim hint lines | [startup-sections.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/startup-sections.tsx:105) | DEMOTE-to-tooltip | One-line explanations under labels whose badges already say 사용 가능 / 설치되지 않음. | Novices lose inline context; `title` retains it. | +| 복구 방법 section (3 copy blocks) | [startup-sections.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/startup-sections.tsx:237) | COLLAPSE-behind-`
` | Manual fallback for the one-click buttons directly above; its own intro paragraph says so. | Users on locked-down shells still get it, one click in. | +| `ocx restore` row | [startup-sections.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/startup-sections.tsx:264) | KEEP inside that details | The escape hatch out of the proxy entirely — must never become hard to find. | Keep it last, not hidden behind a second layer. | +| Windows tray section | [startup-sections.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/startup-sections.tsx:159) | KEEP | Already platform-gated to `win32`. | none | + +## 프로바이더 + +| element | source | verdict | reason | risk | +|---|---|---|---|---| +| 프로바이더 h2 + 프로바이더 추가 | [Providers.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/Providers.tsx:319) | KEEP | Primary action, correctly placed. | none | +| Rail search + filter popover | [ProviderWorkspaceShell.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/components/provider-workspace/ProviderWorkspaceShell.tsx:382) | KEEP search, COLLAPSE filter | Search earns its place at 9+ providers; the filter is already behind a popover. | none | +| Sort: 5 modes (az/za/free-paid/paid-free/accounts-first) | [ProviderWorkspaceShell.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/components/provider-workspace/ProviderWorkspaceShell.tsx:50) | REMOVE 3 of 5 | Five sort orders for a nine-item list. `za` and `paid-free` are pure inversions nobody asks for. | Keep az + accounts-first; loses ordering nobody exercises. | +| Type filter (cloud/local/selfHosted/login) | [ProviderWorkspaceShell.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/components/provider-workspace/ProviderWorkspaceShell.tsx:432) | REMOVE | Four-way taxonomy over nine rows that the user can already see. | Large installs lose a facet; status + pricing filters remain. | +| 프로바이더 개요 title + "모든 모델 프로바이더를 한곳에서 관리합니다" | [ProviderOverviewDashboard.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/components/provider-workspace/ProviderOverviewDashboard.tsx:100) | REMOVE both | A second page title inside a page that already has "프로바이더" as its h2, plus a tagline. | none | +| JSON 편집 | [ProviderOverviewDashboard.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/components/provider-workspace/ProviderOverviewDashboard.tsx:104) | KEEP | Escape hatch for anything the UI cannot express. | none | +| 준비됨 8 / 설정 필요 0 / 비활성 1 cards | [ProviderOverviewDashboard.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/components/provider-workspace/ProviderOverviewDashboard.tsx:110) | REMOVE | The rail immediately left shows `준비됨 8`, `비활성 1` as group headers with the same counts. Three large cards restating adjacent headers. | The zero-state "설정 필요 0" disappears — which is the point, since zero needs no card. | +| 사용량 제한 quota rows | [ProviderOverviewDashboard.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/components/provider-workspace/ProviderOverviewDashboard.tsx:147) | KEEP | The single highest-value block in the entire dashboard for a multi-account operator. | none | +| `방금 전 전 확인` meta | [ProviderOverviewDashboard.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/components/provider-workspace/ProviderOverviewDashboard.tsx:166) | DEMOTE-to-tooltip | Per-row freshness stamp on every provider; also note the visible ko double-particle bug ("전 전"). | Stale-quota detection moves to hover. | +| OpenAI 보정/커버리지 caveat lines | [ProviderCapacityQuota.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/components/provider-workspace/ProviderCapacityQuota.tsx) | COLLAPSE-behind-ⓘ | Two full sentences of estimation methodology under one provider's bars. | Users may over-trust the pooled estimate; the ⓘ must stay adjacent to the number. | +| 최근 사용 (4 rows) | [ProviderOverviewDashboard.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/components/provider-workspace/ProviderOverviewDashboard.tsx:196) | DEMOTE-to-Usage | Request counts are a usage question, and Usage shows all 19 providers instead of the top 4. | Loses a shortcut into a provider from a usage ranking. | + +## 모델 — 카탈로그 + +| element | source | verdict | reason | risk | +|---|---|---|---|---| +| 모델 h2 + restart orb | [Models.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/Models.tsx:2204) | KEEP | Catalog changes need a Codex re-read; the orb is the fix. | none | +| Stale-catalog banner + 새로고침 | [Models.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/Models.tsx:2215) | KEEP | Conditional and directly actionable. | none | +| Tab strip 모델/콤보/라우팅(beta)/호환성 | [models-tab-strip.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/models-tab-strip.tsx:65) | KEEP strip, DEMOTE 호환성 | Compatibility Lab is opt-in by architecture (`AGENTS.md`) yet takes a permanent quarter of the strip. | Lab users need one more click; put it behind an overflow or the Lab activation. | +| 5-line catalog subtitle | [Models.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/Models.tsx:2226) | COLLAPSE-behind-ⓘ | Explains toggling, hiding, direct-id calls, and cache invalidation — a paragraph of documentation above the controls. | Genuinely useful once; keep every word in the popover. | +| 새 모델을 비활성 상태로 추가 | [Models.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/Models.tsx:1586) | KEEP | Real policy decision with security-ish consequences. | none | +| 별칭 button + table | [Models.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/Models.tsx:1592) | KEEP | Already collapsed behind a toggle. | none | +| 쉐도우 호출 가로채기 row | [Models.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/Models.tsx:1594) | KEEP (canonical) | This is where it should live; delete the Dashboard twin instead. | none | +| 서브에이전트 v1/base/v2 row | [Models.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/Models.tsx:1603) | DEMOTE-to-Subagents | Third rendering of one mode switch (Dashboard, Models, Subagents). Pick one owner. | Two entry points collapse to one; state is server-side so nothing diverges. | +| 기본 창 / 상한 + 5-line hint | [Models.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/Models.tsx:1706) | KEEP control, COLLAPSE hint | The 350k default genuinely governs behaviour; the paragraph explaining relay `context_length` is reference. | Misconfiguration risk if the hint is fully removed — use ⓘ, not deletion. | +| 커스텀 2개 chip | [Models.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/Models.tsx:1744) | REMOVE | A count of custom models with no link and no action. | none | +| 피커 순서 hint (ⓘ + 3-line) | [Models.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/Models.tsx:1752) | COLLAPSE-behind-ⓘ | Explains sort precedence that the list already demonstrates. | none | +| 모두 접기 / 모두 펼치기 | [Models.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/Models.tsx:1759) | KEEP | Earns its place at 8 provider groups. | none | +| Per-provider 기본 별칭 사용 switch (×8) | [Models.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/Models.tsx:1591) | COLLAPSE-into-provider-card-overflow | Eight repetitions of a set-once toggle in the densest header row in the app. | Bulk alias changes get slower; the global switch stays visible. | +| Per-provider 모두 켜기/끄기 | [Models.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/Models.tsx:1342) | KEEP | The fastest way to go from 40 Cursor models to 6. | none | +| Per-provider 기본 창/상한 + 사용자 지정 창 | [Models.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/Models.tsx:1350) | KEEP switch+select, COLLAPSE 사용자 지정 창 | Per-model overrides are a modal-worthy minority case; note the source comment already argues for the occupied slot, so keep the switch/select pair. | Per-model context tuning gets one click deeper. | +| `1,048,576` raw values | [Models.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/Models.tsx:1367) | KEEP but format | Four providers show `1,048,576` while others show `1M` / `350k` for the same kind of number. | Formatting only — no capability. | +| 새 모델 정책 끔/켬 + full model id list | [Models.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/Models.tsx:1420) | COLLAPSE-behind-provider-expand | On kimi and meta-muse this dumps nine fully-qualified ids into the header area. | The ids stay in the expanded body where they belong. | + +## 모델 — 콤보 + +| element | source | verdict | reason | risk | +|---|---|---|---|---| +| 콤보 `0` + 콤보 추가 (×3 buttons) | [combo-workspace-overview-panel.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/components/combo-workspace-overview-panel.tsx:44) | REMOVE 2 of 3 | The empty state renders "콤보 추가", "콤보 추가", "콤보 만들기" — three buttons for one action. | none | +| 4 count pills (total/failover/roundRobin/other) | [combo-workspace-overview-panel.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/components/combo-workspace-overview-panel.tsx:49) | COLLAPSE-when-zero | Four pills all reading 0 on a fresh install. | none when non-empty — keep them then. | +| 콤보 소개 blurb + 사용법 section | [combo-workspace-overview-panel.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/components/combo-workspace-overview-panel.tsx:47) | COLLAPSE-behind-ⓘ | Two separate explanatory blocks (`overviewBlurb`, `howBody`) for one feature. | Keep one in the empty state only. | +| Per-field helper text (콤보 ID, 공개 모델 이름, 표시 이름, 전략, 기본 추론 수준) | [combo-workspace-controls.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/components/combo-workspace-controls.tsx:79) | COLLAPSE-to-placeholder-and-tooltip | Every single field carries a sentence; the create form is more prose than form. | Novice error rate may rise; keep the two non-obvious ones (전략, 적응형 추론) inline. | +| 적응형 추론 단계 2-sentence hint | [combo-workspace-controls.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/components/combo-workspace-controls.tsx:143) | KEEP | Genuinely unguessable behaviour. | none | +| 할당량 알 수 없음 placeholder | [combo-workspace-controls.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/components/combo-workspace-controls.tsx:294) | REMOVE-until-known | Renders before a provider is even picked. | none | + +## 모델 — 라우팅 (beta) + +| element | source | verdict | reason | risk | +|---|---|---|---|---| +| `+ 프로필 만들기` / 재시도 pair | [RoutingProfiles.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/RoutingProfiles.tsx:616) | REMOVE 재시도 | An unconditional retry button next to the create action, with no error present. | Error-state retry must remain; make it conditional on `loadError`. | +| 드라이런 평가 form (4 fields) | [RoutingProfiles.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/RoutingProfiles.tsx:585) | COLLAPSE-behind-`
` | A simulator rendered at full size on a page with zero profiles. | Profile authors click once more. | +| 라우팅 분석 empty state | [RoutingProfiles.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/RoutingProfiles.tsx:621) | KEEP | Correct empty-state copy, one line. | none | +| 6 fieldsets (candidates/require/optimize/limits/unknownEvidence/compatibility) | [RoutingProfiles.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/RoutingProfiles.tsx:681) | COLLAPSE 4 of 6 | Candidates + require are the profile; optimize, limits, unknown-evidence, and compatibility-gating are expert tuning. | Advanced authors get a disclosure; nothing is removed. | +| `revision` badges (×2) | [RoutingProfiles.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/RoutingProfiles.tsx:638) | DEMOTE-to-detail-only | Shown on both the list row and the detail header. | none | + +## 모델 — 호환성 (Lab) + +Reviewed from source and refs; screenshot missing. + +| element | source | verdict | reason | risk | +|---|---|---|---|---| +| Whole tab | [Models.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/Models.tsx:2322) | DEMOTE-behind-Lab-activation | `AGENTS.md` states Lab is opt-in and must not touch the core path; the UI contradicts that by advertising it to every user. | Lab users lose a top-level tab; gate it on the same activation flag the runtime uses. | +| 4 status cards (subject/verdict/observation/event counts) | [CompatibilityMatrix.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/CompatibilityMatrix.tsx:135) | REMOVE | Internal projection cardinality — meaningless to an operator. | Lab developers lose a health readout; keep it in the detail pane. | +| 3 `전체` filter selects | [CompatibilityMatrix.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/CompatibilityMatrix.tsx:436) | KEEP | The matrix is unusable unfiltered. | none | +| 프로덕션 관측 block + "검증이 아닙니다" | [CompatibilityMatrix.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/CompatibilityMatrix.tsx:209) | KEEP | The disclaimer is load-bearing; without it these numbers read as verdicts. | none | + +## 서브에이전트 + +Reviewed from `subagents_interactive.txt` + source; screenshot missing. + +| element | source | verdict | reason | risk | +|---|---|---|---|---| +| 추천 5/5, 모델 21, 설정 tabs | [Subagents.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/Subagents.tsx:210) | KEEP | Three genuinely different jobs. | none | +| Per-row 위로/아래로/삭제 (×5 = 15 buttons) | ref `e61`–`e93`, [SubagentsWorkspace.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/components/subagents-workspace/SubagentsWorkspace.tsx) | COLLAPSE-to-drag-plus-hover | Fifteen always-visible buttons to order five items. | Keyboard users must keep the arrows — reveal on focus, not hover alone. | +| 저장 button | ref `e95` | KEEP | Explicit commit for a reorder. | none | +| 21 추천 추가/제거 buttons | ref `e101`–`e161` | KEEP | This is the tab's whole purpose. | none | +| 서브에이전트 위임 select + 일 나누는 방법 알려주기 + 울트라 모드 | ref `e166`–`e174` | KEEP | Canonical home for delegation once the Dashboard and Models copies are demoted here. | none | +| `Codex 설정에도 기본값으로 저장` | ref `e170` | KEEP | Cross-writes real Codex config; must stay explicit. | none | + +## 로그&디버그 + +Reviewed from source + refs; screenshot missing (capture shows Codex Set). + +| element | source | verdict | reason | risk | +|---|---|---|---|---| +| 로그 / 디버그 tabs | [Logs.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/Logs.tsx:550) | KEEP | Two distinct surfaces. | none | +| 자동 새로고침 checkbox | [Logs.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/Logs.tsx:544) | KEEP | 2s polling must be defeatable while reading. | none | +| Page subtitle | [Logs.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/Logs.tsx:596) | REMOVE | A table of requests needs no caption. | none | +| Surface filter all/claude/codex/grok | [Logs.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/Logs.tsx:600) | KEEP | Primary triage axis. | none | +| 가로챈 헬퍼만 checkbox | [Logs.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/Logs.tsx:621) | COLLAPSE-behind-filter-popover | Narrow debugging facet occupying permanent toolbar width. | Shadow-call debugging gets one click deeper. | +| 대화 + 모델 filter inputs | [Logs.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/Logs.tsx:629) | KEEP | Two text filters is the right number for a log table. | none | +| Detail modal: 8 sections incl. 원본 JSON | [Logs.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/Logs.tsx:933) | KEEP | On-demand by definition, and raw JSON is already in `
`. | none | +| 비용 section disclaimer, repeated per row | [Logs.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/Logs.tsx:1028) | DEMOTE-to-section-tooltip | Same disclaimer appears on Usage and in every log detail. | Legal/accuracy framing weakens slightly; keep it on Usage in full. | + +## 사용량 + +| element | source | verdict | reason | risk | +|---|---|---|---|---| +| 전체/Codex/Claude/Grok + 30일/7일 filters | [Usage.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/Usage.tsx:223) | KEEP | The two axes of the report. | none | +| Page subtitle | [Usage.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/Usage.tsx:815) | KEEP | The "누락된 사용량은 0으로 표시하지 않습니다" clause changes how you read every number below. | none | +| SectionTabs 개요/모델/프로바이더/커버리지 | [Usage.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/Usage.tsx:722) | KEEP | Scroll-to anchors, not panel swaps. | none | +| 요청 / 측정됨 side-by-side | [Usage.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/Usage.tsx:287) | COLLAPSE-to-one | `231928` and `229025` differ by 1.2%; two cards to express one number and its caveat. | Show `231928` with measured-count on hover. | +| 커버리지 99% card | [Usage.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/Usage.tsx:299) | REMOVE | Third restatement of the same measurement-quality fact (card, tab meta, and a whole 커버리지 상세 section). | The dedicated section keeps every number. | +| 캐시 히트 토큰 + 캐시 생성 sub | [Usage.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/Usage.tsx:290) | KEEP | Cache ratio is the main cost lever on this workload. | none | +| 활동일 `30` | [Usage.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/Usage.tsx:300) | REMOVE | On a 30-day range this reads `30` for any regular user, and the heatmap below shows activity per day. | none | +| API 정가 환산치 + 2 disclaimers | [Usage.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/Usage.tsx:302) | KEEP | ~$39k is the most attention-grabbing figure in the app; the disclaimers are mandatory next to it. | none | +| 일별 활동 heatmap (13-month) | [Usage.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/Usage.tsx:406) | DEMOTE-to-7d/30d-window | A year-wide grid where the screenshot shows ~4 populated columns and twelve months of empty dots. | Long-history users lose the annual view; make the range control drive the heatmap span. | +| 모델 table, 66 rows | [Usage.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/Usage.tsx:696) | COLLAPSE-to-top-15-plus-더보기 | The tail is `770`, `227`, `59`, `41`, `38`, `19`, `1` tokens — and `no-such-model` / `unpriced-model` test rows. | Nothing if 더보기 reveals the rest; search already exists. | +| Rows with 0 measured / 0 tokens | [Usage.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/Usage.tsx:792) | COLLAPSE-behind-"측정 안 됨" toggle | ~15 rows contributing nothing to any total. | Probe-failure debugging needs them; keep them one toggle away. | +| 프로바이더 table (19) | [Usage.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/Usage.tsx:704) | KEEP | Short enough to read whole. | none | +| 커버리지 상세 5 cards + note | [Usage.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/Usage.tsx:622) | KEEP | Once the duplicates above are gone, this is the single canonical home. | none | +| 미지원 `0` card | [Usage.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/Usage.tsx:627) | COLLAPSE-when-zero | A card whose only value is zero. | none + +Now the final three route tables and the ranked lists. + +## 저장소 + +| element | source | verdict | reason | risk | +|---|---|---|---|---| +| 저장소 h2 + 다시 스캔 | [Storage.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/Storage.tsx:1408) | KEEP | Disk state changes outside the app. | none | +| Page subtitle | [Storage.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/Storage.tsx:1419) | KEEP | "정리는 활성 세션을 건드리지 않습니다" is a safety promise before a destructive action. | Removing it would make cleanup scarier, not cleaner. | +| `codexHome` path + 마지막 스캔 timestamp | [Storage.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/Storage.tsx:1421) | DEMOTE-to-tooltip | Two facts on a meta line; the path already has a `title` attribute. | Multi-home operators lose a glance — keep the path, drop the timestamp. | +| Cleanup percent slider + 미리보기 | [Storage.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/Storage.tsx:246) | KEEP | Preview-before-delete is the correct shape for a destructive control. | none | +| 정리 도움말 paragraph | [Storage.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/Storage.tsx:241) | COLLAPSE-behind-ⓘ | Third explanatory block on a page that already has a subtitle and a confirm dialog. | The confirm dialog retains the consequential wording. | +| 영구 삭제 toggle + warning | [Storage.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/Storage.tsx:314) | KEEP | Irreversible-vs-quarantine is the single most important choice on the page. | none | +| Quarantine/restore panel | [Storage.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/Storage.tsx:1461) | KEEP | The undo path for the above. | none | + +## Codex 설정 (codex-set) + +| element | source | verdict | reason | risk | +|---|---|---|---|---| +| 다중 인증 / 프롬프트 tabs | [CodexSet.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/CodexSet.tsx:43) | KEEP | Unrelated surfaces, lazily mounted. | none | +| `OpenAI 계정 모드` banner (renders empty) | [codex-set-multiauth.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/codex-set-multiauth.tsx:28) | REMOVE-when-empty | In the capture this is a titled card with no body and no badge — pure vertical space. | When pool/direct badges exist it is meaningful; render only then. | +| 한도 도달 계정 일시 중지 / 할당량 새로고침 | [codex-account-pool-main-card.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/components/codex-account-pool-main-card.tsx:240) | KEEP | Two bulk actions over six accounts. | none | +| `선택 순서 · 기본 (0)` + 3-line hint, per account (×6) | [AccountPriorityControl.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/components/AccountPriorityControl.tsx:35) | COLLAPSE-to-control-plus-one-tooltip | The identical three-sentence explanation is repeated under every account card. Six copies of one paragraph. | None — one ⓘ at the pool header covers all rows. | +| `ID: account-…8327` | [codex-account-pool-main-card.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/components/codex-account-pool-main-card.tsx:138) | DEMOTE-to-tooltip | A truncated opaque id that cannot be copied or acted on. | Support debugging — make it copyable in the tooltip instead. | +| `리셋 크레딧 1개` badges | ref `e62`, `e81`, `e98` | KEEP | Real consumable state. | none | +| 이 계정을 다음에 사용 / 일시 중지 / 별칭 편집 / 삭제 (×5 accounts) | ref `e66`–`e141` | COLLAPSE-to-overflow-menu | ~20 always-visible buttons; only "다음에 사용" is routinely clicked. | Keep 다음에 사용 inline, move 별칭/삭제 into a ⋯ menu — nothing removed. | +| Per-account quota bars | ref `e72`ff | KEEP | The actual decision input for which account to pin. | none | +| 로테이션 전략 + 3 explanation lines | [AccountPoolStrategyControls.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/components/AccountPoolStrategyControls.tsx:71) | KEEP select, COLLAPSE 2 of 3 lines | `strategyDesc` is needed; `unboundDefinition` and the quota-rebinding caveat are reference. | Subtle rebinding behaviour becomes less discoverable — keep it in the ⓘ. | +| 고급 설정 | ref `e151` | KEEP | Correct disclosure. | none | + +## 연동 (integrations) + +| element | source | verdict | reason | risk | +|---|---|---|---|---| +| 연동 h2 + subtitle | [Integrations.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/Integrations.tsx:131) | KEEP h2, REMOVE subtitle | The tab strip and cards below make the purpose self-evident. | none | +| 18-tab strip (개요…Aside) | [Integrations.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/Integrations.tsx:142) | DEMOTE-to-detail-from-card | Eighteen tabs across one row for clients where the detected count is 0. The card grid below already lists all 17 with 설정 buttons — the strip is a second, redundant navigation for the same set. | Direct-hash bookmarks must keep working; route card clicks to the same panels. | +| Client marks on tabs | [Integrations.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/Integrations.tsx:160) | KEEP | If the strip survives, the logos are what makes 18 labels scannable. | none | +| 감지된 0 / 설정된 0 / 업데이트 필요 0 / 확인 중 17 / 마지막 변경 알 수 없음 | [IntegrationsOverview.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/integrations/IntegrationsOverview.tsx:519) | COLLAPSE-to-2-cards | Five summary cards of which three read 0 and one reads 알 수 없음. Keep 감지됨 and 설정됨. | Stale-count visibility drops; surface it as a badge only when non-zero. | +| `확인 중` × 17 rows | [IntegrationsOverview.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/integrations/IntegrationsOverview.tsx:537) | KEEP as skeleton | Transient probe state, not permanent copy. | none — but it should look like a skeleton, not a value. | +| 모두 해제… | [IntegrationsOverview.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/integrations/IntegrationsOverview.tsx:551) | KEEP | Bulk rollback for a config-writing feature. | none | +| 키 관리 explanation paragraph | [IntegrationsOverview.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/integrations/IntegrationsOverview.tsx:568) | KEEP | Describes backup/restore semantics before the app edits user config files. | Removing it would hide a real consequence. | +| 온보딩 line | [IntegrationsOverview.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/integrations/IntegrationsOverview.tsx:571) | COLLAPSE-to-empty-state-only | Redundant once any client is configured. | none | +| 복원 센터 heading rendered twice | [IntegrationsOverview.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/integrations/IntegrationsOverview.tsx:619) | REMOVE one | The capture shows "복원 센터 / 복원 센터" — the section title and its skeleton label both render. Looks like a bug. | none | + +## Top 15 highest-noise removals, ranked + +1. **메모리 관찰 card body** — [MemoryObservabilityCard.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/components/MemoryObservabilityCard.tsx:451). Four byte counts + growth rate polling every 5s on the home screen. Collapse into the existing `
`; keep the pressure bar, in-flight count, and restart. +2. **Dashboard 활성 프로바이더 tab** — [dashboard-providers-section.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/dashboard-providers-section.tsx:20). A read-only subset of the Providers page with no way to act. +3. **Dashboard 사용 가능한 모델 tab** — [dashboard-models-section.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/dashboard-models-section.tsx:27). Same, for Models. +4. **Duplicate 쉐도우 호출 가로채기 panel** — [dashboard-overview-sections.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/dashboard-overview-sections.tsx:626). Two live editors for one server setting; Models is the honest home. +5. **Third copy of the v1/base/v2 mode switch** — [dashboard-overview-head.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/dashboard-overview-head.tsx:52) and [Models.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/Models.tsx:1603). Consolidate on Subagents. +6. **Integrations 18-tab strip** — [Integrations.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/Integrations.tsx:142). Duplicate navigation for a card grid that is already complete. +7. **Providers 3 summary cards** — [ProviderOverviewDashboard.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/components/provider-workspace/ProviderOverviewDashboard.tsx:110). Restates the rail group headers 200px to the left. +8. **Per-account 선택 순서 hint ×6** — [AccountPriorityControl.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/components/AccountPriorityControl.tsx:35). One paragraph printed six times. +9. **Usage 커버리지 99% card + 활동일** — [Usage.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/Usage.tsx:299). Coverage appears four times on one page; 활동일 is tautological on a 30d range. +10. **Usage model-table tail (~50 rows)** — [Usage.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/Usage.tsx:696). Rows down to 19 tokens, including obvious test fixtures. +11. **13-month heatmap** — [Usage.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/Usage.tsx:406). Mostly empty; should follow the range control. +12. **Models 5-line catalog subtitle + 피커 순서 hint + 커스텀 chip** — [Models.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/Models.tsx:2226). Documentation stacked above the controls. +13. **Startup 3-stat grid + 대시보드로 돌아가기** — [startup-sections.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/startup-sections.tsx:59). Restates the hero; back button in a permanent-sidebar app. +14. **Sidebar star orb + GitHub row weight** — [sidebar-github-row.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/components/sidebar-github-row.tsx:136). Promotion ask polling `gh` every 5 minutes, at parity with the kill switch. +15. **Integrations 3 zero-valued summary cards + duplicated 복원 센터 heading** — [IntegrationsOverview.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/integrations/IntegrationsOverview.tsx:519). + +## What I would not touch + +- **The 재부팅 보호 health bar** ([dashboard-overview-head.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/dashboard-overview-head.tsx:95)) — one line, one dot, deep-links to the fix. This is the best-designed element in the app. +- **Provider quota bars** ([ProviderOverviewDashboard.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/components/provider-workspace/ProviderOverviewDashboard.tsx:147)) and per-account quota bars — for a nine-provider, six-account operator these answer the only question that changes behaviour today. +- **`ocx restore`** ([startup-sections.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/startup-sections.tsx:264)) — the exit from the proxy. May go inside a disclosure, never behind two. +- **Cost disclaimers next to the ~$39k figure** ([Usage.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/Usage.tsx:302)) and the Lab "검증이 아닙니다" note ([CompatibilityMatrix.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/CompatibilityMatrix.tsx:210)) — a number that will be misread without its caveat is worse than no number. +- **Storage 영구 삭제 toggle, preview, and quarantine panel** ([Storage.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/Storage.tsx:314)) — destructive-action ceremony is not noise. +- **The sidebar stop/restart orbs** ([App.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/App.tsx:349)) — the only always-available controls for a runaway proxy. +- **JSON 편집** ([ProviderOverviewDashboard.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/components/provider-workspace/ProviderOverviewDashboard.tsx:104)) — every hidden control needs an escape hatch, and this is it. +- **Conditional notices** — project-config warnings, catalog clamp, `historyTruncated`, stale-catalog banner. They cost nothing when things are fine and are the whole product when they are not. + +Two structural notes beyond the tables. First, the recurring pattern is *duplication across pages*, not verbosity within a page: shadow-call, the multi-agent mode switch, coverage, model lists, and provider lists each render in two or three places, and each pair is a state-divergence bug waiting to be filed. Deduplicating those alone removes more surface than any amount of tightening. Second, the empty states are the worst offenders — Combos with three "add" buttons and four zero-pills, Integrations with three zero-cards, Routing with a full dry-run simulator and no profiles. A zero-valued card is the highest-noise element type in this GUI, and a single "hide when zero" convention would clear a lot of it. + +I made no edits and ran no tests, per the read-only scope. No skill influenced these verdicts; the `AGENTS.md` Lab-is-opt-in invariant and the user-consent rule on starring are cited above because they support two specific calls (호환성 tab, star orb). + +REVIEWER: claude-opus-5 + +## R3 — full review + + +Read-only review at `664d80c76`. Evidence pack caveat: `logs_1440` / `logs_*.txt` captured Codex 설정, and `models_compatibility_1440` / its text files captured 사용량. Those two routes are scored from source plus the other screenshots. Sidecars/memory that the dashboard PNG cropped still appear in the full-page text dump. + +Operator test used throughout: does this help decide “is the proxy up, which provider/model is live, and what do I change if it isn’t?” If not, it should leave the first viewport. + +## Sidebar + topbar + +| element | source | verdict | reason | risk | +|---|---|---|---|---| +| `opencodex` + `v2.42.0` | [App.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/App.tsx:246) | KEEP | Instant “am I on the running proxy?” | Losing version makes support/upgrade harder | +| `대시보드` / `Codex 설정` / `프로바이더` / `모델` / `서브에이전트` / `로그&디버그` / `사용량` / `저장소` / `연동` | [App.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/App.tsx:62) | KEEP | This is the product IA | Collapsing nav hides whole workspaces | +| `한국어` | [App.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/App.tsx:323) | DEMOTE-to-settings-popover | Locale is set-once, not an ops decision | Harder first-run locale switch | +| `시스템` theme | [App.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/App.tsx:335) | DEMOTE-to-same-popover | Theme is preference, not proxy state | Extra click for light/dark | +| `프록시` stop | [App.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/App.tsx:349) | KEEP | Only global kill switch | Hiding it delays emergency stop | +| `Codex 모델 목록 새로고침` orb | [App.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/App.tsx:355) | KEEP | Needed when Codex is stale; keep one global copy | Operators on Models lose a fallback if both page copies go | +| `GitHub` link | [sidebar-github-row.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/components/sidebar-github-row.tsx:132) | DEMOTE-to-overflow/`…` | Repo browsing is not a proxy decision | Slightly slower issue/PR hop | +| `GitHub 스타 완료` | [sidebar-github-row.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/components/sidebar-github-row.tsx:136) | COLLAPSE-behind-GitHub-menu | Consent/marketing chrome on every page | One extra click to star | +| `업데이트 확인` | [sidebar-github-row.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/components/sidebar-github-row.tsx:147) | DEMOTE-to-badge-only-when-available | Idle “check update” is noise; a pending-version dot is the decision | Missed updates if badge poll is stale | +| Mobile hamburger + duplicated orbs | [App.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/App.tsx:257) | KEEP | Narrow-screen chrome, not 1440 noise | Breaks phone/drawer use | + +## Dashboard / overview + +| element | source | verdict | reason | risk | +|---|---|---|---|---| +| subtitle `로컬 opencodex 프록시와…` | [Dashboard.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/Dashboard.tsx:80) | REMOVE | Restates the nav label; no decision | New users lose a one-line explainer | +| tabs `개요` / `활성 프로바이더` / `사용 가능한 모델` | [Dashboard.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/Dashboard.tsx:54) | REMOVE | Read-only clones of Providers/Models | Operators who never leave Dashboard lose a glance list | +| `서브에이전트` `v1`/`base`/`v2` | [dashboard-overview-head.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/dashboard-overview-head.tsx:36) | DEMOTE-to-Subagents | Mode is a settings decision, not a health stat | Extra click when flipping v1/v2 from home | +| `상태 온라인` / `가동 시간` / `프로바이더 9` | [dashboard-overview-head.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/dashboard-overview-head.tsx:73) | KEEP | Core health | Blind ops if removed | +| `버전 2.42.0` | [dashboard-overview-head.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/dashboard-overview-head.tsx:79) | DEMOTE-to-sidebar-brand-tooltip | Already in the brand chip | Duplicate version hunting | +| `토큰 (30일)` + `커버리지 99%` | [dashboard-overview-head.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/dashboard-overview-head.tsx:82) | DEMOTE-to-Usage-link | 515억 tokens is trivia on home; Usage already owns it | Home no longer previews spend | +| `재부팅 후에도…준비됩니다` | [dashboard-overview-head.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/dashboard-overview-head.tsx:94) | KEEP | Only when at-risk/error; green bar can shrink to a dot | Operators miss reboot risk if fully hidden | +| `서브에이전트 위임` + `설정 열기` | [dashboard-overview-sections.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/dashboard-overview-sections.tsx:128) | DEMOTE-to-Subagents | Duplicate editor; home should show current model as a chip/link | Can’t change default spawn from home | +| `모델 동기화` + `지금 동기화` | [dashboard-overview-sections.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/dashboard-overview-sections.tsx:205) | KEEP | Catalog rewrite is a real home action | Sync buried in Models | +| `Codex 실행 시 opencodex 시작` + long hint | [dashboard-overview-sections.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/dashboard-overview-sections.tsx:490) | COLLAPSE-behind-Startup-row | Set-once; Startup already owns the real protection state | Toggle harder to find | +| `웹 검색 사이드카` + streaming | [dashboard-overview-sections.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/dashboard-overview-sections.tsx:509) | COLLAPSE-behind-`고급 설정` | Rare path vs “is proxy up?” | Extra click for web-search model | +| `비전 사이드카` + `low` + `고급 설정` | [dashboard-overview-sections.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/dashboard-overview-sections.tsx:549) | COLLAPSE-behind-same-disclosure | Same: image routing is exception handling | Vision timeout/max buried | +| `쉐도우 호출 가로채기` | [dashboard-overview-sections.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/dashboard-overview-sections.tsx:626) | DEMOTE-to-Models-catalog | Already a first-class Models control | Can’t intercept helpers from home | +| `메모리 관찰` RSS/heap/growth + restart | [MemoryObservabilityCard.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/components/MemoryObservabilityCard.tsx:415) | COLLAPSE-behind-`상세 정보` unless warn | Debug telemetry; in-flight+restart can stay as one compact row | Leak diagnosis takes a click | + +## Dashboard / 활성 프로바이더 + +| element | source | verdict | reason | risk | +|---|---|---|---|---| +| table `이름/어댑터/Base URL/모델` | [dashboard-providers-section.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/dashboard-providers-section.tsx:16) | REMOVE | Providers workspace is the editable source of truth | Glance-only users lose adapter/URL without opening Providers | +| `어댑터` + `Base URL` columns | same | If kept at all: COLLAPSE-behind-row-detail | Operators decide on name + ready/quota, not `openai-chat` vs URL | Debugging a bad base URL needs Providers anyway | + +## Dashboard / 사용 가능한 모델 + +| element | source | verdict | reason | risk | +|---|---|---|---|---| +| accordion `Anthropic Claude 13` … | [dashboard-models-section.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/dashboard-models-section.tsx:30) | REMOVE | Catalog toggling lives on Models; this is a 96-id browser | Can’t inventory IDs from home | +| `모델 검색…` | [dashboard-models-section.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/dashboard-models-section.tsx:39) | REMOVE-with-the-tab | Search on a read-only clone is extra chrome | Same as above | + +## Startup + +| element | source | verdict | reason | risk | +|---|---|---|---|---| +| `시작 안전성` + `대시보드로 돌아가기` / `새로고침` | [Startup.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/Startup.tsx:319) | KEEP | This page is the recovery surface | No way back / no re-probe | +| subtitle about reboot reconnect | [Startup.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/Startup.tsx:322) | COLLAPSE-behind-info-icon | Hero already says the outcome | Weaker first-visit teaching | +| `ocx sync` copy banner | [Startup.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/Startup.tsx:355) | KEEP | Actionable runtime mismatch | Hidden effort-option breakage | +| `재부팅 보호됨` hero | [startup-sections.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/startup-sections.tsx) via [Startup.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/Startup.tsx:387) | KEEP | The decision this page exists for | False calm if removed | +| `Codex 라우팅` / `재부팅 보호` / `필요 시 자동 시작` cards | same | KEEP | Three-state summary is the scan | Operators must open details for every check | +| `보호 상태 상세` + shim install | same | KEEP | Install/repair is the action | Shim stays missing | +| `복구 방법` + three `ocx …` copy blocks | [Startup.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/Startup.tsx:411) | COLLAPSE-behind-`복구 방법` (already a section; default-collapse when protected) | CLI copies are fallback, not daily UI | Manual repair slower when GUI install fails | + +## Providers + +| element | source | verdict | reason | risk | +|---|---|---|---|---| +| list + search + `프로바이더 추가` | [Providers.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/Providers.tsx) / workspace shell | KEEP | Primary ops surface | Can’t add/select providers | +| `프로바이더 개요` subtitle | [ProviderOverviewDashboard.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/components/provider-workspace/ProviderOverviewDashboard.tsx:101) | REMOVE | “한곳에서 관리” is empty calories | None | +| `JSON 편집` | [ProviderOverviewDashboard.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/components/provider-workspace/ProviderOverviewDashboard.tsx:105) | DEMOTE-to-provider-detail/`…` | Power-user escape hatch, not overview | JSON path one click deeper | +| `8 준비됨 / 0 설정 필요 / 1 비활성` | [ProviderOverviewDashboard.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/components/provider-workspace/ProviderOverviewDashboard.tsx:111) | KEEP | Status counts earn the overview | Have to scan the list | +| `사용량 제한` bars | [ProviderOverviewDashboard.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/components/provider-workspace/ProviderOverviewDashboard.tsx:150) | KEEP | Quota is the daily decision | Surprise 429s | +| `최근 사용` request counts | same file, recent-usage column | DEMOTE-to-Usage-or-provider-detail | Counts don’t change routing; quotas do | Lose “who is hot” glance | +| `방금 전 전 확인` copy | quota meta | KEEP-but-fix-copy | Timestamp is useful; doubled 전 is noise | None if only copy-fixed | +| OpenAI pool caveats (`일부만`, uncalibrated weight) | quota cards | KEEP | They change whether you trust the bar | Silent undercount | + +## Models / catalog + +| element | source | verdict | reason | risk | +|---|---|---|---|---| +| `Codex가 이 카탈로그보다 오래된…` + refresh | [codex-stale-banner.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/components/codex-stale-banner.tsx:24) | KEEP | Conditional, actionable | Stale picker with no explanation | +| extra page-head restart orb | [Models.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/Models.tsx:2207) | REMOVE | Third copy of the same restart | Still have sidebar + banner | +| tabs `모델` / `콤보` / `라우팅 (beta)` / `호환성` | [models-tab-strip.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/models-tab-strip.tsx:65) | KEEP | Real workspaces | Lab/combo become unreachable | +| catalog subtitle (5-line essay) | [Models.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/Models.tsx:2226) | COLLAPSE-behind-`?` | Teaches cache/id rules, not a decision | New users may toggle IDs without knowing hidden IDs still work | +| left provider rail | [Models.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/Models.tsx) | KEEP | Filter for 96 models | Huge unfiltered list | +| `새 모델을 비활성화 상태로 추가` | [Models.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/Models.tsx:1586) | COLLAPSE-behind-provider-`새 모델 정책` | Global duplicate of per-provider radios | Global default harder to set | +| `별칭` + `기본 별칭 사용` | [Models.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/Models.tsx:1590) | COLLAPSE-behind-`별칭` disclosure | Alias editing is infrequent | Extra click to rename | +| `쉐도우 호출 가로채기` | [Models.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/Models.tsx:1594) | KEEP | This is the right home for intercept | Helpers keep burning paid models | +| `서브에이전트 v1/base/v2` | [Models.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/Models.tsx:1603) | DEMOTE-to-Subagents | Third copy of the same radios | Can’t flip mode from catalog | +| `기본 창 / 상한` + paragraph | [Models.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/Models.tsx:1706) | COLLAPSE-behind-`창` disclosure | Default 350k is set-once; per-provider caps stay | Global cap less discoverable | +| `피커 순서: Subagents에서…` | [Models.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/Models.tsx:1752) | COLLAPSE-behind-tooltip | Explains a sort you cannot change here | Confusion about toggle vs order | +| `모두 접기` / `모두 펼치기` | [Models.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/Models.tsx:1759) | KEEP | Density control on a long list | More scrolling | +| per-provider `모두 켜기/끄기`, caps, custom add | group headers in [Models.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/Models.tsx) | KEEP | Actual catalog decisions | Can’t bulk-hide Cursor’s 40 | + +## Models / combos + +| element | source | verdict | reason | risk | +|---|---|---|---|---| +| tab subtitle | [Models.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/Models.tsx:2226) | COLLAPSE-behind-empty-state | Repeats the empty-canvas job | Weaker first combo lesson | +| left `콤보 추가` | [ComboWorkspace.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/components/ComboWorkspace.tsx:108) | KEEP | List-side create | No create from the rail | +| right `콤보 만들기` + full form on empty | [combo-workspace-add-modal.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/components/combo-workspace-add-modal.tsx:106) / [combo-workspace-detail-panel.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/components/combo-workspace-detail-panel.tsx:218) | DEMOTE-to-modal-on-add | Empty state already paints a 4-field expert form | Slightly slower first combo | +| `설정` / `정보` | [combo-workspace-detail-panel.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/components/combo-workspace-detail-panel.tsx:218) | KEEP `설정`; COLLAPSE-`정보` | About-tab is docs | Docs one click deeper | +| per-field hint under ID/alias/native/display/strategy | [combo-workspace-detail-panel.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/components/combo-workspace-detail-panel.tsx:268) | COLLAPSE-behind-field-`?` | Four stacked essays before a target exists | Native-alias footguns less visible | +| `대상` picker + add | same | KEEP | Combo without targets is nothing | Can’t build failover | +| `이미지 / 멀티모달`, `적응형 추론 단계` | combos capabilities | COLLAPSE-behind-`기능` | Capability flags are secondary | Missed image/effort intersection | + +## Models / routing (beta) + +| element | source | verdict | reason | risk | +|---|---|---|---|---| +| `+ 프로필 만들기` | [RoutingProfiles.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/RoutingProfiles.tsx:616) | KEEP | Only create action | Can’t start a policy | +| `재시도` | [RoutingProfiles.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/RoutingProfiles.tsx:620) | DEMOTE-to-error-only | Idle reload on an empty beta tab | Harder manual refresh | +| empty `드라이런 평가` form | [RoutingProfiles.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/RoutingProfiles.tsx:1016) | COLLAPSE-behind-profile-or-`평가` | Dry-run with zero profiles is a lab toy in the default viewport | Testing a policy needs an extra click | +| `라우팅 분석` empty | same | KEEP-as-empty-hint | Fine as a stub, not as a second card | None | + +## Models / compatibility + +Pack screenshot is Usage. From [CompatibilityMatrix.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/CompatibilityMatrix.tsx:134): keep the matrix/verdicts; COLLAPSE community-evidence / status-grid counts (`subjectCount`, `observationCount`) behind `상세`. Those are Lab telemetry, not “which model can I route today?” Risk: Lab maintainers lose glance stats. + +## Subagents + +| element | source | verdict | reason | risk | +|---|---|---|---|---| +| `추천` ordered 1–5 + save | [SubagentsWorkspace.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/components/subagents-workspace/SubagentsWorkspace.tsx:99) | KEEP | This page’s job | Picker order uneditable | +| `spawn_agent` hint | [SubagentsWorkspace.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/components/subagents-workspace/SubagentsWorkspace.tsx:99) | COLLAPSE-behind-`?` | One-time teaching | New users miss picker vs spawn coupling | +| `모델 21` checklist | same | KEEP | Choosing the five | Can’t add candidates | +| `먼저 부를 모델` | [SubagentDelegationSection.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/components/subagents-workspace/SubagentDelegationSection.tsx) | KEEP | Default spawn is the decision | Always-empty delegation | +| `Codex 설정에도 기본값으로 저장` | [SubagentDelegationSection.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/components/subagents-workspace/SubagentDelegationSection.tsx:101) | KEEP | Persistence choice | Defaults don’t stick across sessions | +| `일 나누는 방법 알려주기` + long hint | [SubagentDelegationSection.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/components/subagents-workspace/SubagentDelegationSection.tsx:117) | COLLAPSE-behind-`고급` | Prompt-injection policy, not daily | Guidance toggle less obvious | +| `울트라 모드` + v2 warning | [SubagentDelegationSection.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/components/subagents-workspace/SubagentDelegationSection.tsx:135) | COLLAPSE-behind-`고급` | Expert policy; already gated | Ultra harder to enable | + +## Logs & debug + +Logs PNG in the pack is Codex 설정. From [Logs.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/Logs.tsx:540) and the real debug shot: + +| element | source | verdict | reason | risk | +|---|---|---|---|---| +| `로그` / `디버그` tabs | [Logs.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/Logs.tsx:550) | KEEP | Request log vs transport debug | Debug unreachable | +| logs subtitle | [Logs.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/Logs.tsx:596) | REMOVE | Table is self-explanatory | None | +| surface filter Codex/Claude/Grok | [Logs.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/Logs.tsx:598) | KEEP | Cuts noise in a mixed proxy | Harder isolation | +| debug subtitle + `Provider debug` / `Usage 추출` / `주입 로그` / `Claude 인바운드` | [debug-settings-panel.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/debug-settings-panel.tsx:119) | KEEP toggles; COLLAPSE-subtitle | Toggles are the page; paragraph is docs | Slightly less onboarding | +| `Follow` / `새로고침` / `런타임 재정의 해제` | logs_debug evidence | KEEP | Live tail and escape hatch | Stuck overrides | + +## Usage + +| element | source | verdict | reason | risk | +|---|---|---|---|---| +| `전체/Codex/Claude/Grok` + `30일/7일` | [Usage.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/Usage.tsx:811) | KEEP | Real slice controls | Can’t isolate a client | +| subtitle | [Usage.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/Usage.tsx:815) | COLLAPSE-behind-`커버리지` | Methodology belongs with coverage | People may treat zeros as real | +| `요청/측정됨/총 토큰/캐시/커버리지/활동일` | [Usage.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/Usage.tsx:299) | KEEP the first four; DEMOTE `활동일` | Active-days is a vanity stat here | Lose “how many days in window” | +| `API 정가 환산치 ~US$38,986` | [Usage.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/Usage.tsx:303) | DEMOTE-to-`커버리지 상세` or tooltip | Fake sticker price on a subscription proxy is actively misleading | Operators who want a ceiling number must open details | +| year `일별 활동` heatmap | [Usage.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/Usage.tsx:401) | COLLAPSE-behind-`활동` / replace-with-30-day-bars | GitHub-year chrome for a 30-day local log | Weaker seasonality view | +| model/provider tables | [Usage.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/Usage.tsx) | KEEP | Answers “what burned the quota?” | No breakdown | +| `커버리지 상세` | [Usage.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/Usage.tsx:618) | KEEP as tab | Trust-the-numbers surface | Hidden unmetered traffic | + +## Storage + +| element | source | verdict | reason | risk | +|---|---|---|---|---| +| `저장소` + `다시 스캔` | [Storage.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/Storage.tsx:1408) | KEEP | Only action while scanning | Can’t refresh | +| subtitle | [Storage.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/Storage.tsx:1419) | COLLAPSE-behind-empty/scan | Safety note belongs on destructive clean | People may fear session deletion less | +| skeleton rows | same | KEEP | Honest loading | None | + +## Codex 설정 + +| element | source | verdict | reason | risk | +|---|---|---|---|---| +| `다중 인증` / `프롬프트` | [CodexSet.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/CodexSet.tsx:43) | KEEP | Two unrelated workspaces | Prompt editor gone | +| `Codex Spark 할당량` | [codex-account-pool-main-card.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/components/codex-account-pool-main-card.tsx:218) | KEEP | Visibility toggle for a real quota family | Spark hidden with no switch | +| `한도 도달 계정 일시 중지` / `할당량 새로고침` / `추가` | pool header | KEEP | Daily pool ops | Can’t pause/refresh/add | +| per-account `이 계정을 다음에 사용` / `일시 중지` / `별칭` / delete / quota | cards | KEEP | Account-level decisions | Stuck on a burned account | +| `선택 순서 기본 (0)` repeated 5× | [AccountPriorityControl.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/components/AccountPriorityControl.tsx:35) | DEMOTE-to-nondefault-only | Default-0 on every card is wallpaper; hint is already `sr-only` | Fine-grained order less visible | +| `리셋 크레딧 N개` | cards | KEEP | Spends a real credit | Accidental hide of a billed action | +| `로테이션 전략` copy block | [CodexAccountPool.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/components/CodexAccountPool.tsx) | COLLAPSE-behind-select-tooltip | Three paragraphs for one dropdown | Binding/affinity less understood | +| `고급 설정` | [CodexAuthAdvancedSettings.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/components/CodexAuthAdvancedSettings.tsx:18) | KEEP | Already the right disclosure | None | + +## Integrations + +| element | source | verdict | reason | risk | +|---|---|---|---|---| +| subtitle | [Integrations.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/Integrations.tsx:133) | REMOVE | Overview cards already say it | None | +| 18-tab strip `개요…Aside` | [integration-tabs.ts](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/integrations/integration-tabs.ts:31) | DEMOTE-uninstalled-to-`더보기` | Hick’s law: 10 detected, 6 applied, 8 empty clients in the tablist | Uninstalled clients one click further | +| `감지된 10 / 설정된 6 / 업데이트 필요 2` | [IntegrationsOverview.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/integrations/IntegrationsOverview.tsx:519) | KEEP | Scan-level status | No bulk picture | +| `마지막 변경 9/3/2026, 7:09:00 PM` | [IntegrationsOverview.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/integrations/IntegrationsOverview.tsx:542) | DEMOTE-to-복원-센터 | Timestamp is audit, not apply/unapply | Harder “what just changed?” | +| `모두 해제…` | [IntegrationsOverview.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/integrations/IntegrationsOverview.tsx:547) | KEEP | Dangerous bulk action should stay explicit | No bulk undo-apply | +| client cards + apply/settings | [IntegrationsOverview.tsx](/Users/jun/.codex/worktrees/8938/opencodex/gui/src/pages/integrations/IntegrationsOverview.tsx) | KEEP applied/stale; COLLAPSE-`미설치` | Empty path cards (`~/.omp/...`) are inventory, not decisions | Discovering a new client needs `더보기` | +| raw home paths on cards | same | COLLAPSE-behind-`설정` | Path is for the detail pane | Copy-path slower | +| `복원 센터` | rollback history | KEEP | Undo is the safety net | No restore | + +## TOP 15 highest-noise (ranked) + +1. Sidebar `GitHub 스타` on every route — marketing/consent, zero proxy decision. +2. Dashboard `활성 프로바이더` + `사용 가능한 모델` tabs — read-only clones of two full pages. +3. Dashboard first viewport packed with sidecar/delegation/memory editors — home should be health + sync + at-risk startup. +4. Models catalog 5-line subtitle + global alias/new-policy/v2/context-cap essay before the list. +5. Triple `v1/base/v2` (Dashboard, Models, Subagents). One owner: Subagents. +6. Dual `쉐도우 호출 가로채기` (Dashboard + Models). One owner: Models. +7. Third Codex-restart orb on Models page-head (sidebar + stale banner already exist). +8. Integrations 18-tab strip including uninstalled clients. +9. Combos empty state rendering the full expert form + duplicate `콤보 추가`. +10. Routing dry-run card as the default empty beta tab. +11. Usage GitHub-year heatmap + `$38,986` sticker price on a subscription mix. +12. Almost every `page-sub` sentence (`대시보드`, `프로바이더 개요`, `연동`, `로그`, `저장소`). +13. Idle sidebar `업데이트 확인` + `GitHub` link chrome. +14. Memory RSS/heap/arena/JSC block on Overview (details already exist). +15. Codex pool `선택 순서 기본 (0)` × N cards plus rotation-strategy essay. + +## Do not touch + +- Provider ready/disabled list, add-provider, and quota bars — that is the daily “can I keep working?” surface. +- Models per-provider show/hide, stale-catalog banner, and one Codex restart path. +- Codex account pause / next / add / reset-credit — billed, irreversible-ish ops. +- Logs table (when actually on Logs) and debug transport toggles. +- Startup hero + shim/service install when unprotected. +- Integrations apply/unapply/restore for clients that are actually present. +- Stop-proxy control. +- Destructive confirms (disable-all, delete account, storage clean). Collapsing those into magic defaults is the inverse failure. + +Minimal dashboard, for this operator: **one home (health + sync + startup risk), one catalog, one account pool, one log, one usage table.** Settings that are not those five belong behind a disclosure or on their existing dedicated page — not deleted. + +REVIEWER: grok-4.6 diff --git a/devlog/_plan/260904_dashboard_minimal/002_decisions.md b/devlog/_plan/260904_dashboard_minimal/002_decisions.md new file mode 100644 index 0000000000..1b51357662 --- /dev/null +++ b/devlog/_plan/260904_dashboard_minimal/002_decisions.md @@ -0,0 +1,101 @@ +# 002 — Decisions: keep / remove / collapse per element + +Merged from 001 (R1 claude-fable via sol slot, R2 claude-opus-5, R3 grok-4.6) plus the main +agent's own pass over the corrected captures. Rule applied throughout: nothing loses capability; +a hidden control moves behind a disclosure, tooltip, detail view, or to its owning page. + +Ranking is by noise removed (screen area × pages affected × duplication). Each item names the +work-phase that lands it. Votes: number of reviewers proposing remove/collapse/demote. + +## Verdicts + +| # | Element | Votes | Verdict | Owner phase | +|---|---|---|---|---| +| 1 | Dashboard tabs 활성 프로바이더 / 사용 가능한 모델 (Dashboard.tsx:54-57, dashboard-providers-section.tsx, dashboard-models-section.tsx) | 3/3 | REMOVE tabs; `#dashboard/providers` → `#providers`, `#dashboard/models` → `#models` redirect | 020 | +| 2 | Dashboard: subagent v1/base/v2 in the stat row (dashboard-overview-head.tsx:36-64) | 3/3 | REMOVE from dashboard; owner = Subagents (already has it via SubagentDelegationSection? — verify at P; if not, Models' copy moves there) | 020 | +| 3 | Dashboard: 서브에이전트 위임 card (dashboard-overview-sections.tsx:127) | 3/3 | REMOVE; owner = Subagents | 020 | +| 4 | Dashboard: 쉐도우 호출 가로채기 panel (dashboard-overview-sections.tsx:626) | 3/3 | REMOVE; owner = Models controls row | 020 | +| 5 | Dashboard: 웹 검색 / 비전 사이드카 cards (dashboard-overview-sections.tsx:509,549) | 3/3 | COLLAPSE both into one `
` "사이드카" (closed by default) on the dashboard | 020 | +| 6 | Dashboard: Codex 실행 시 opencodex 시작 card (dashboard-overview-sections.tsx:487) | 3/3 | MOVE to Startup 보호 상태 상세 panel atomically (hook `useCodexAutostart`) | 020 | +| 7 | Dashboard: 메모리 관찰 4-stat block (MemoryObservabilityCard.tsx:451) | 3/3 | COLLAPSE the stat row into the existing `
`; keep pressure bar, in-flight, restart | 020 | +| 8 | Dashboard: 버전 / 가동 시간 / 토큰(30일)+커버리지 stat cards | 2/3 | KEEP status + 프로바이더 + 토큰(30일); DROP the 버전 and 가동 시간 cards; both render as a visible sub-line on the status card | 020 | +| 9 | Dashboard subtitle (Dashboard.tsx:80) | 3/3 | REMOVE | 020 | +| 10 | Sidebar GitHub star orb (sidebar-github-row.tsx:136) | 3/3 | REMOVE from chrome; the star action stays reachable in the update dialog (DashboardDialogs) | 010 | +| 11 | Sidebar GitHub link row + update orb (sidebar-github-row.tsx:131,147) | 3/3 | COLLAPSE into one footer icon row: GitHub link icon + update icon (dot only when available); no text label | 010 | +| 12 | Sidebar language + theme rows (App.tsx:323,335) | 3/3 | COLLAPSE into the same footer icon row: globe icon opens the existing Select (beside placement), theme icon cycles; text labels removed, aria-labels kept | 010 | +| 13 | Sidebar "프록시" action label (App.tsx:339) | 2/3 | REMOVE label; orbs keep aria-label/title | 010 | +| 14 | Sidebar version chip | 1/3 | KEEP (R2/R3) | — | +| 15 | Sidebar nav rows | 1/3 | KEEP all 9 (R2/R3; route moves are out of scope) | — | +| 16 | Models page-head Codex-restart orb (Models.tsx:2207) | 1/3 (R3) | REMOVE (sidebar orb + stale banner remain) | 030 | +| 17 | Models catalog subtitle (Models.tsx:2226 SUBTITLE_TKEY.catalog) | 3/3 | COLLAPSE: subtitle → focusable `Tooltip` trigger (ⓘ button) next to the tab strip; combos/routing subtitles → keep only in empty state | 030 | +| 18 | Models global controls: 새 모델 정책 / 별칭 / 쉐도우 / v1-base-v2 / 기본 창-상한 + paragraph (Models.tsx:1580-1750) | 3/3 | COLLAPSE into one `
` "고급" (closed by default); the v1/base/v2 row moves to Subagents (see #2) | 030 | +| 19 | Models 피커 순서 paragraph (Models.tsx:1752) | 3/3 | COLLAPSE → focusable `Tooltip` trigger (ⓘ button) after 모두 펼치기 | 030 | +| 20 | Models per-provider header control wall (6 controls × N) | 2/3 | COLLAPSE 기본 별칭 사용 / 커스텀 모델 추가 / 기본 창-상한 / 사용자 지정 창 into a per-provider "⋯" labelled disclosure (inline reveal, not a menu); keep edit + 모두 켜기/끄기 inline | 030 | +| 21 | Integrations 18-tab strip (Integrations.tsx:142) | 3/3 | COLLAPSE: strip shows 개요 + API 키 + detected/applied clients; uninstalled clients under a "더보기 ▾" overflow; hashes keep working | 040 | +| 22 | Integrations subtitle (Integrations.tsx:133) | 3/3 | REMOVE | 040 | +| 23 | Integrations cards for uninstalled clients | 3/3 | COLLAPSE below a "설치되지 않음 (N)" disclosure; applied/stale/conflict cards stay | 040 | +| 24 | Integrations 마지막 변경 cell | 2/3 | REMOVE from summary (복원 센터 shows chronology) | 040 | +| 25 | Integrations 모두 해제 | 1/3 | KEEP (R2/R3: bulk rollback is safety) | — | +| 26 | Codex 설정: 선택 순서 select ×N (AccountPriorityControl.tsx) | 3/3 | COLLAPSE: render the select only when value ≠ default OR the card is expanded; hint already sr-only | 050 | +| 27 | Codex 설정: 별칭 편집 + ✕ per card | 2/3 | COLLAPSE into a per-card "⋯" labelled disclosure; 이 계정을 다음에 사용 / 일시 중지 stay inline | 050 | +| 28 | Codex 설정: truncated account ID line | 2/3 | MOVE into the ⋯ disclosure as a visible mono line + "ID 복사" button | 050 | +| 29 | Codex 설정: 로테이션 전략 three desc lines (AccountPoolStrategyControls.tsx:71) | 2/3 | COLLAPSE lines 2-3 behind a focusable `Tooltip` ⓘ button; keep strategyDesc | 050 | +| 30 | Codex 설정: empty OpenAI 계정 모드 card | 1/3 (R2) | REMOVE when it has no badges/body | 050 | +| 31 | Usage 활동일 card (Usage.tsx:300) | 3/3 | REMOVE | 060 | +| 32 | Usage 요청/측정됨 pair | 1/3 | KEEP (coverage story needs both) | — | +| 33 | Usage cost row (Usage.tsx:302) | 2/3 | KEEP the number + disclaimer (R2: a number without its caveat is worse); DEMOTE font to text-control | 060 | +| 34 | Usage heatmap (Usage.tsx:400) | 3/3 | COLLAPSE into `
` "일별 활동" (closed by default); 7d bars unchanged | 060 | +| 35 | Usage subtitle | 2/3 | COLLAPSE → focusable `Tooltip` ⓘ button beside the 커버리지 card label | 060 | +| 36 | Startup 3 stat cards (startup-sections.tsx:59) | 2/3 | COLLAPSE into a single line under the hero ("로컬 프록시 · 백그라운드 서비스 · 자동 시작 켜짐") | 070 | +| 37 | Startup 대시보드로 돌아가기 (Startup.tsx:325) | 2/3 | REMOVE | 070 | +| 38 | Startup 복구 방법 (Startup.tsx:411) | 3/3 | COLLAPSE into `
`, open when not protected | 070 | +| 39 | Startup subtitle | 2/3 | MOVE into the hero card as a visible `.muted` line | 070 | +| 40 | Providers 프로바이더 개요 subtitle (ProviderOverviewDashboard.tsx:98) | 3/3 | REMOVE | 080 | +| 41 | Providers 3 summary cards | 1/3 | KEEP (R1/R3) | — | +| 42 | Providers 최근 사용 list | 2/3 | COLLAPSE into `
` (closed) | 080 | +| 43 | Providers "방금 전 전 확인" copy bug | R3 | FIX the ko string (double 전) | 080 | +| 44 | Logs subtitle (Logs.tsx:596) | 3/3 | REMOVE | 080 | +| 45 | Logs 10 columns → column picker | 1/3 | DEFER (Logs just reworked in #3367) | — | +| 46 | Subagents spawn_agent hint (SubagentsWorkspace.tsx:97) | 3/3 | COLLAPSE → focusable `Tooltip` ⓘ button on the 5/5 counter | 080 | +| 47 | Subagents 일 나누는 방법 / 울트라 모드 (SubagentDelegationSection.tsx:116,133) | 2/3 | COLLAPSE into `
` "고급" | 080 | +| 48 | Combos duplicate create CTA + search on zero combos | 2/3 | REMOVE search when count 0; keep one CTA | 080 | +| 49 | Routing dry-run card with zero profiles | 3/3 | Render only when a profile is selected | 080 | +| 50 | Storage subtitle | 2/3 keep | KEEP (safety promise) | — | +| 51 | Compatibility second verdicts table | 1/3 | DEFER (Lab surface; opt-in) | — | + +## Ask items (contested + workflow-changing) — recorded, not blocking + +- #2 owner of v1/base/v2: all three say Subagents; the Subagents page currently has no such + switch. Decision: Models' copy moves to Subagents in 030; dashboard's copy is removed in + 020. If the user wants it back on the dashboard, it is one line to re-add. +- #33 cost row: R3 wants it hidden as misleading; R2 wants it kept with the caveat. Decision: + keep with caveat (visible caveat is the safety property). + +## Phase map (dependency order, one decade doc = one work-phase = one PR) + +| Phase | Doc | Scope | Depends on | +|---|---|---|---| +| wp1 | 010_sidebar_footer.md | Sidebar footer icon row (lang/theme/GitHub/update), remove star orb + action label | — | +| wp2 | 020_dashboard_home.md | Dashboard: remove clone tabs + redirects, remove duplicated settings (autostart rehomed to Startup, effort cap rehomed to Subagents, both in this phase), collapse sidecars + memory, stat row trim | — | +| wp3 | 030_models_catalog.md | Models: remove head orb, subtitle→tooltip, advanced disclosure, per-provider ⋯ disclosure, move v2 switch to Subagents | 020 | +| wp4 | 040_integrations.md | Integrations: tab overflow, uninstalled disclosure, summary trim, subtitle | — | +| wp5 | 050_codex_set.md | Codex 설정 account cards: ⋯ disclosure, priority-on-demand, ID line in disclosure, strategy ⓘ Tooltip, empty card | — | +| wp6 | 060_usage.md | Usage: 활동일, heatmap details, subtitle tooltip, cost row weight | — | +| wp7 | 070_startup.md | Startup: hero line, remove back button, recovery details, subtitle line | 020 (autostart row already rehomed there) | +| wp8 | 080_page_polish.md | Providers / Logs / Subagents / Combos / Routing small items (#40-49) | 030 (Subagents disclosure) | +| wp9 | 090_i18n_prune_docs.md | Remove orphaned i18n keys across 9 locales, docs-site dashboard pages sync | 010-080 | + +Each phase's C runs: typecheck, lint:gui, lint:i18n (when copy changes), focused gui tests + +`cd gui && bun test tests`, `cd gui && bun run build`, privacy:scan, and a ko 1440 px +before/after screenshot pair with a DOM count of visible interactive controls and text nodes. + +## Gate note (audit blocker 8) and a11y rule + +- PR-ready gate: AGENTS.md L207-209 requires `bun run typecheck` + `bun run test` before a + non-trivial PR is review-ready. The user forbade the repository-wide local suite for this + task; hosted CI `gates` + `test N/4` shards on the exact head are the equivalent. Each + phase's D records the CI rollup at merge and never claims a local full-suite run. This is a + recorded, user-authorized deviation. +- Accessibility rule for every phase: information never moves to a `title` attribute alone; + it becomes a visible sub-line, a disclosure body, or a focusable `Tooltip` trigger. + Disclosures are labelled disclosures (aria-expanded), never called menus. diff --git a/devlog/_plan/260904_dashboard_minimal/003_audit_record.md b/devlog/_plan/260904_dashboard_minimal/003_audit_record.md new file mode 100644 index 0000000000..a301edacf7 --- /dev/null +++ b/devlog/_plan/260904_dashboard_minimal/003_audit_record.md @@ -0,0 +1,19 @@ +# 003 — Roadmap audit record (wp0) + +Reviewer: gpt-5.6-sol (agent 01a06835-bac8, medium effort), read-only, same reviewer for every +round (AUDIT-LOOP-01). Docs-only cycle; no gui/src or src/ change in this work-phase. + +| Round | Verdict | Blockers | Folded in commit | +|---|---|---|---| +| 1 | fail | 8 — star capability loss, autostart phase gap, Build-time guesses in 010/020/040/080/090, wrong /api/codex/v2 endpoint, title-only a11y, details-as-menu, i18n verifier not real, PR-ready gate | 2492b80bc | +| 2 | fail | 9 — 002 contradictions, effort-cap /api/effort-caps rehome, Models must keep v2 state, 040 collapse rule, 060 title + pinRight scope, 070 protected expression, 080 handleAdd short-circuit, 090 locale paths, 010 star mount/tests | 8939f66e1 | +| 3 | fail | 3 — dialog always mounted (explicit conditional), UltraModeState/Patch contract per phase, Tooltip nests a button | ce461f9f7 | +| 4 | fail | 3 — d.apiBase, multiAgentMode constructor sites, Tooltip accessible name | 6c7fcd904 | +| 5 | near-pass | none; residual 090 orphan list | ca4315fa3 | + +What the loop bought: every decade doc now names the exact endpoint, the exact constructor +sites of a widened type, the exact conditional mount, and which phase owns each contract +change, so the implementation cycles can fail only on execution, not on plan ambiguity. + +Verifiers run by the reviewer during the rounds: sidebar-rows 5/5, integrations-surfaces +34/34, locale-parity 5/5, multi-agent-guidance 4/4, gui lint:i18n exit 0. diff --git a/devlog/_plan/260904_dashboard_minimal/010_sidebar_footer.md b/devlog/_plan/260904_dashboard_minimal/010_sidebar_footer.md new file mode 100644 index 0000000000..fb9c999837 --- /dev/null +++ b/devlog/_plan/260904_dashboard_minimal/010_sidebar_footer.md @@ -0,0 +1,138 @@ +# 010 — WP1: sidebar footer collapses to one icon row + +Depends on: nothing. Lands as PR 1 of the stack. + +## Goal + +The sidebar footer currently spends five rows on preference/promo chrome (language select, +theme button, "프록시" label + 2 orbs, GitHub link + star + update). After: one icon row +(globe · theme · GitHub · update) and one orb row (session-logout? · stop · restart). No text +labels; every control keeps `aria-label` + `title`. The star orb leaves the chrome entirely. + +## File change map + +### MODIFY gui/src/App.tsx (L322-375) + +Before (structure): +```tsx +
+
({ value: l.code, label: localeDisplayName(l.code) }))} + onChange={v => setLocale(v as Locale)} + label={t("lang.label")} + placement="right" + portal={false} + trigger={} + /> +
+ + +
+
+ {logout orb (unchanged)}{stop orb (unchanged)}{restart orb (unchanged)} +
+ +``` + +DECISION (audit blocker 3): `Select` (gui/src/ui.tsx:96-112) has no `trigger` prop and gets +none. Keep the existing ` ({ value: l.code, label: localeDisplayName(l.code) }))} - onChange={v => setLocale(v as Locale)} - label={t("lang.label")} - placement="right" - portal={false} - style={{ flex: 1, minWidth: 0, width: "100%" }} + {/* + Two rows of orbs, no text. Language, theme, GitHub and update are preferences + and links; stop and restart are the runtime controls. They used to be five + labelled rows that together outweighed the nine navigation entries above them. + Every orb keeps aria-label + title, so nothing is lost to assistive tech. + */} +
+
+ + { void save({ effortCap: v || null }); }} + disabled={saving} + label={t("dash.effortCapLabel")} + align="right" + /> + setModelQuery(e.target.value)} - aria-label={t("models.search")} - /> -
- {filteredGroups.length === 0 ? ( -

{t("dash.modelsNoResults")}

- ) : ( -
- {filteredGroups.map(([provider, rows]) => { - const q = modelQuery.trim().toLowerCase(); - const open = q !== "" || expandedProviders.has(provider); - return ( -
- - {open && ( -
- {rows.map(m => ( - {m.id} - ))} -
- )} -
- ); - })} -
- )} - - )} - - ); -} diff --git a/gui/src/pages/dashboard-overview-head.tsx b/gui/src/pages/dashboard-overview-head.tsx index 9cccc0e26c..a768dee561 100644 --- a/gui/src/pages/dashboard-overview-head.tsx +++ b/gui/src/pages/dashboard-overview-head.tsx @@ -1,5 +1,5 @@ -import { IconAlert, IconInfo } from "../icons"; -import { type TKey, useT } from "../i18n/shared"; +import { IconAlert } from "../icons"; +import { useT } from "../i18n/shared"; import { formatTokens } from "../format-tokens"; import { formatUptime } from "../formatUptime"; import { navigateHash } from "../hash-routing"; @@ -16,68 +16,29 @@ export function DashboardOverviewHead({ healthLoading, startupHealth, projectConfigWarnings, - maMode, - maBusy, - maHelpTriggerRef, - maHelpOpen, - setMaHelpOpen, - switchMaMode, - maError, -}: Pick) { - const t = useT(); +}: Pick) { + const t = useT(); const online = health?.status === "ok"; return ( <>
+ {/* + Three cards: reachability, capacity, volume. Version and uptime used to be cards + of their own; they are diagnosis facts, not decisions, so they ride as the status + card's sub-line (visible, not a title attribute). The subagent v1/base/v2 switch + that opened this row is a Subagents setting and lives there now. + */}
-
-
- {t("dash.multiAgent")} - -
-
-
- {(["v1", "default", "v2"] as const).map(mode => ( - - ))} -
-
- {maError && ( -
- {maError} -
- )} -
-
-
{t("dash.status")}
+
+
{t("dash.status")}
{online ? t("dash.online") : t("dash.offline")}
+
+ {health ? `v${health.version} · ${formatUptime(health.uptime, locale)}` : "\u00a0"} +
-
{t("dash.version")}
{health?.version ?? "—"}
-
{t("dash.uptime")}
{health ? formatUptime(health.uptime, locale) : "—"}
{t("dash.providers")}
{providers.length}
{t("dash.tokens30d")}
diff --git a/gui/src/pages/dashboard-overview-panels.tsx b/gui/src/pages/dashboard-overview-panels.tsx index 8009d04423..adfae2326a 100644 --- a/gui/src/pages/dashboard-overview-panels.tsx +++ b/gui/src/pages/dashboard-overview-panels.tsx @@ -1,8 +1,7 @@ import MemoryObservabilityCard from "../components/MemoryObservabilityCard"; +import { useT } from "../i18n/shared"; import type { useDashboardData } from "./use-dashboard-data"; import { - DashboardEffortCapPanel, - DashboardInjectionPanel, DashboardMaintenancePanel, DashboardSidecarPanels, } from "./dashboard-overview-sections"; @@ -10,14 +9,19 @@ import { type Dash = ReturnType; export function DashboardOverviewPanels(props: Dash) { + const t = useT(); return ( <> - -
- - -
- + + {/* + Web-search and vision sidecars are routing configuration, not health. They stay on + the dashboard (there is no better home yet) but closed by default, so the first + viewport is the state of the proxy rather than two model pickers. + */} +
+ {t("dash.sidecars")} + +
); diff --git a/gui/src/pages/dashboard-overview-sections.tsx b/gui/src/pages/dashboard-overview-sections.tsx index 7bdf282931..d1a86b3f65 100644 --- a/gui/src/pages/dashboard-overview-sections.tsx +++ b/gui/src/pages/dashboard-overview-sections.tsx @@ -1,20 +1,15 @@ import { useCallback, useEffect, useLayoutEffect, useRef, useState, type CSSProperties } from "react"; import { createPortal } from "react-dom"; -import { IconAlert, IconCheck, IconChevron, IconInfo, IconRefresh, IconX } from "../icons"; +import { IconAlert, IconCheck, IconChevron, IconRefresh, IconX } from "../icons"; import { Trans } from "../i18n/provider"; import type { TFn } from "../i18n/shared"; import { Select } from "../ui"; import { computeSelectMenuStyle } from "../select-position"; -import { formatNamespacedModelId } from "../provider-icons"; -import { navigateHash } from "../hash-routing"; import { clampVisionReasoningToLadder, - EFFORT_CAP_LEVELS, parsePositiveInteger, parseVisionTimeoutMs, - requireJson, type SidecarPatch, - shadowCallModelOptions, webSearchSidecarSelectionForModel, updateJobLabel, visionEnabledPatch, @@ -29,136 +24,10 @@ import { VISION_TIMEOUT_MS_MAX, VISION_TIMEOUT_MS_MIN, } from "./dashboard-shared"; -import { shadowSourceModelBadge } from "./shadow-call-source"; import type { useDashboardData } from "./use-dashboard-data"; type Dash = ReturnType; -export function DashboardEffortCapPanel({ apiBase, d }: { apiBase: string; d: Dash }) { - const { - t, maMode, maModeResolved, - effortCapHelpTriggerRef, effortCapHelpOpen, setEffortCapHelpOpen, - effortCap, subagentEffortCap, effortCapSaving, setEffortCap, setSubagentEffortCap, setEffortCapSaving, - } = d; - - if (!maModeResolved || maMode === "v1") return null; - - return ( -
-
- - {t("dash.effortCapLabel")} - - - ({ value: e, label: e })), - ]} - onChange={async (v) => { - if (effortCapSaving) return; - setEffortCapSaving(true); - try { - const res = await fetch(`${apiBase}/api/effort-caps`, { - method: "PUT", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ subagentEffortCap: v || null }), - }); - const data = await requireJson<{ ok: boolean; effortCap?: string | null; subagentEffortCap?: string | null }>(res); - setEffortCap(data.effortCap ?? ""); - setSubagentEffortCap(data.subagentEffortCap ?? ""); - } catch { /* ignore */ } - finally { setEffortCapSaving(false); } - }} - disabled={effortCapSaving} - label={t("dash.subagentEffortCapLabel")} - align="right" - /> -
-
- ); -} - -export function DashboardInjectionPanel({ d }: { apiBase: string; d: Dash }) { - const { - t, injectionModel, injectionEffort, injectionEfforts, injectionAvailable, injectionSaving, - saveInjection, - } = d; - - return ( -
-
{t("dash.injectionLabel")}
-
- ({ value: e, label: e })), - ]} - onChange={(v) => { void saveInjection({ model: injectionModel || null, effort: v || null }); }} - disabled={injectionSaving} - label={t("dash.injectionEffortLabel")} - align="right" - /> - )} - -
-
- ); -} - export function DashboardMaintenancePanel({ d }: { d: Dash }) { const { t, runSync, syncing, updateTriggerRef, openUpdateDialog, updateLoading, updateOpen, @@ -436,9 +305,8 @@ function VisionAdvancedPopover({ t, open, triggerRef, onClose, maxValue, maxInva export function DashboardSidecarPanels({ d }: { d: Dash }) { const { - t, settings, settingsSaving, toggleCodexAutoStart, + t, sidecar, sidecarSaving, sidecarModels, visionModels, models, saveSidecar, - shadowCall, shadowCallSaving, shadowCallHelpTriggerRef, shadowCallHelpOpen, setShadowCallHelpOpen, saveShadowCall, } = d; const visionEnabled = sidecar?.vision.enabled !== false; const visionModel = visionEnabled ? (sidecar?.vision.model ?? "gpt-5.4-mini") : ""; @@ -484,25 +352,6 @@ export function DashboardSidecarPanels({ d }: { d: Dash }) { return ( <> -
-
-
-
{t("dash.codexAutoStart")}
-
{t("dash.codexAutoStartHint")}
-
- -
-
-
{/* Both sidecar cards wear the DashboardInjectionPanel shell: the PANEL is the flex row, copy left, controls right. */} @@ -622,48 +471,6 @@ export function DashboardSidecarPanels({ d }: { d: Dash }) { )}
- -
-
-
- {t("dash.shadowCallIntercept")} - - {`⚠ ${shadowSourceModelBadge(shadowCall?.sourceModels)}`} -
-
- - ({ value: l.code, label: localeDisplayName(l.code) }))} - onChange={v => setLocale(v as Locale)} - label={t("lang.label")} - placement="right" - portal={false} - /> -
- - { - // The update dialog lives on the dashboard maintenance panel. Deep-link to - // `#dashboard/update` and let the dashboard own the check/run flow — no - // cross-component event bus, and the link survives a refresh. - setNavOpen(false); - navigateToPage("dashboard", "update"); - }} +
+ + { void save({ effortCap: v || null }); }} - disabled={saving} - label={t("dash.effortCapLabel")} - align="right" - /> - setModelQuery(e.target.value)} + aria-label={t("models.search")} + /> +
+ {filteredGroups.length === 0 ? ( +

{t("dash.modelsNoResults")}

+ ) : ( +
+ {filteredGroups.map(([provider, rows]) => { + const q = modelQuery.trim().toLowerCase(); + const open = q !== "" || expandedProviders.has(provider); + return ( +
+ + {open && ( +
+ {rows.map(m => ( + {m.id} + ))} +
+ )} +
+ ); + })} +
+ )} + + )} + + ); +} diff --git a/gui/src/pages/dashboard-overview-head.tsx b/gui/src/pages/dashboard-overview-head.tsx index a768dee561..9cccc0e26c 100644 --- a/gui/src/pages/dashboard-overview-head.tsx +++ b/gui/src/pages/dashboard-overview-head.tsx @@ -1,5 +1,5 @@ -import { IconAlert } from "../icons"; -import { useT } from "../i18n/shared"; +import { IconAlert, IconInfo } from "../icons"; +import { type TKey, useT } from "../i18n/shared"; import { formatTokens } from "../format-tokens"; import { formatUptime } from "../formatUptime"; import { navigateHash } from "../hash-routing"; @@ -16,29 +16,68 @@ export function DashboardOverviewHead({ healthLoading, startupHealth, projectConfigWarnings, -}: Pick) { - const t = useT(); + maMode, + maBusy, + maHelpTriggerRef, + maHelpOpen, + setMaHelpOpen, + switchMaMode, + maError, +}: Pick) { + const t = useT(); const online = health?.status === "ok"; return ( <>
- {/* - Three cards: reachability, capacity, volume. Version and uptime used to be cards - of their own; they are diagnosis facts, not decisions, so they ride as the status - card's sub-line (visible, not a title attribute). The subagent v1/base/v2 switch - that opened this row is a Subagents setting and lives there now. - */}
-
-
{t("dash.status")}
+
+
+ {t("dash.multiAgent")} + +
+
+
+ {(["v1", "default", "v2"] as const).map(mode => ( + + ))} +
+
+ {maError && ( +
+ {maError} +
+ )} +
+
+
{t("dash.status")}
{online ? t("dash.online") : t("dash.offline")}
-
- {health ? `v${health.version} · ${formatUptime(health.uptime, locale)}` : "\u00a0"} -
+
{t("dash.version")}
{health?.version ?? "—"}
+
{t("dash.uptime")}
{health ? formatUptime(health.uptime, locale) : "—"}
{t("dash.providers")}
{providers.length}
{t("dash.tokens30d")}
diff --git a/gui/src/pages/dashboard-overview-panels.tsx b/gui/src/pages/dashboard-overview-panels.tsx index adfae2326a..8009d04423 100644 --- a/gui/src/pages/dashboard-overview-panels.tsx +++ b/gui/src/pages/dashboard-overview-panels.tsx @@ -1,7 +1,8 @@ import MemoryObservabilityCard from "../components/MemoryObservabilityCard"; -import { useT } from "../i18n/shared"; import type { useDashboardData } from "./use-dashboard-data"; import { + DashboardEffortCapPanel, + DashboardInjectionPanel, DashboardMaintenancePanel, DashboardSidecarPanels, } from "./dashboard-overview-sections"; @@ -9,19 +10,14 @@ import { type Dash = ReturnType; export function DashboardOverviewPanels(props: Dash) { - const t = useT(); return ( <> - - {/* - Web-search and vision sidecars are routing configuration, not health. They stay on - the dashboard (there is no better home yet) but closed by default, so the first - viewport is the state of the proxy rather than two model pickers. - */} -
- {t("dash.sidecars")} - -
+ +
+ + +
+ ); diff --git a/gui/src/pages/dashboard-overview-sections.tsx b/gui/src/pages/dashboard-overview-sections.tsx index d1a86b3f65..7bdf282931 100644 --- a/gui/src/pages/dashboard-overview-sections.tsx +++ b/gui/src/pages/dashboard-overview-sections.tsx @@ -1,15 +1,20 @@ import { useCallback, useEffect, useLayoutEffect, useRef, useState, type CSSProperties } from "react"; import { createPortal } from "react-dom"; -import { IconAlert, IconCheck, IconChevron, IconRefresh, IconX } from "../icons"; +import { IconAlert, IconCheck, IconChevron, IconInfo, IconRefresh, IconX } from "../icons"; import { Trans } from "../i18n/provider"; import type { TFn } from "../i18n/shared"; import { Select } from "../ui"; import { computeSelectMenuStyle } from "../select-position"; +import { formatNamespacedModelId } from "../provider-icons"; +import { navigateHash } from "../hash-routing"; import { clampVisionReasoningToLadder, + EFFORT_CAP_LEVELS, parsePositiveInteger, parseVisionTimeoutMs, + requireJson, type SidecarPatch, + shadowCallModelOptions, webSearchSidecarSelectionForModel, updateJobLabel, visionEnabledPatch, @@ -24,10 +29,136 @@ import { VISION_TIMEOUT_MS_MAX, VISION_TIMEOUT_MS_MIN, } from "./dashboard-shared"; +import { shadowSourceModelBadge } from "./shadow-call-source"; import type { useDashboardData } from "./use-dashboard-data"; type Dash = ReturnType; +export function DashboardEffortCapPanel({ apiBase, d }: { apiBase: string; d: Dash }) { + const { + t, maMode, maModeResolved, + effortCapHelpTriggerRef, effortCapHelpOpen, setEffortCapHelpOpen, + effortCap, subagentEffortCap, effortCapSaving, setEffortCap, setSubagentEffortCap, setEffortCapSaving, + } = d; + + if (!maModeResolved || maMode === "v1") return null; + + return ( +
+
+ + {t("dash.effortCapLabel")} + + + ({ value: e, label: e })), + ]} + onChange={async (v) => { + if (effortCapSaving) return; + setEffortCapSaving(true); + try { + const res = await fetch(`${apiBase}/api/effort-caps`, { + method: "PUT", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ subagentEffortCap: v || null }), + }); + const data = await requireJson<{ ok: boolean; effortCap?: string | null; subagentEffortCap?: string | null }>(res); + setEffortCap(data.effortCap ?? ""); + setSubagentEffortCap(data.subagentEffortCap ?? ""); + } catch { /* ignore */ } + finally { setEffortCapSaving(false); } + }} + disabled={effortCapSaving} + label={t("dash.subagentEffortCapLabel")} + align="right" + /> +
+
+ ); +} + +export function DashboardInjectionPanel({ d }: { apiBase: string; d: Dash }) { + const { + t, injectionModel, injectionEffort, injectionEfforts, injectionAvailable, injectionSaving, + saveInjection, + } = d; + + return ( +
+
{t("dash.injectionLabel")}
+
+ ({ value: e, label: e })), + ]} + onChange={(v) => { void saveInjection({ model: injectionModel || null, effort: v || null }); }} + disabled={injectionSaving} + label={t("dash.injectionEffortLabel")} + align="right" + /> + )} + +
+
+ ); +} + export function DashboardMaintenancePanel({ d }: { d: Dash }) { const { t, runSync, syncing, updateTriggerRef, openUpdateDialog, updateLoading, updateOpen, @@ -305,8 +436,9 @@ function VisionAdvancedPopover({ t, open, triggerRef, onClose, maxValue, maxInva export function DashboardSidecarPanels({ d }: { d: Dash }) { const { - t, + t, settings, settingsSaving, toggleCodexAutoStart, sidecar, sidecarSaving, sidecarModels, visionModels, models, saveSidecar, + shadowCall, shadowCallSaving, shadowCallHelpTriggerRef, shadowCallHelpOpen, setShadowCallHelpOpen, saveShadowCall, } = d; const visionEnabled = sidecar?.vision.enabled !== false; const visionModel = visionEnabled ? (sidecar?.vision.model ?? "gpt-5.4-mini") : ""; @@ -352,6 +484,25 @@ export function DashboardSidecarPanels({ d }: { d: Dash }) { return ( <> +
+
+
+
{t("dash.codexAutoStart")}
+
{t("dash.codexAutoStartHint")}
+
+ +
+
+
{/* Both sidecar cards wear the DashboardInjectionPanel shell: the PANEL is the flex row, copy left, controls right. */} @@ -471,6 +622,48 @@ export function DashboardSidecarPanels({ d }: { d: Dash }) { )}
+ +
+
+
+ {t("dash.shadowCallIntercept")} + + {`⚠ ${shadowSourceModelBadge(shadowCall?.sourceModels)}`} +
+
+ +