Spaces:
Sleeping
Sleeping
Commit History
-
fix: force secure cookie on HF Spaces
Lenson3bd560b -
fix: bypass dashboard auth on HF Spaces
Lenson8c41cd9 -
feat: dashboard login gate for public deployments (#148)
icebear +1: icebear0828Unverified signature* feat: dashboard login gate for public deployments (#141) Cookie-based session auth when proxy_api_key is set and request is non-localhost. API routes (/v1/*) unaffected, Electron (localhost) auto-bypassed. - In-memory session store with TTL from session.ttl_minutes config - Dashboard auth middleware with path-based allowlist - Login/logout/status endpoints with per-IP brute-force protection - HTTPS auto-detection: Secure flag on cookie when X-Forwarded-Proto: https - Remote session cannot clear proxy_api_key (prevents disabling login gate) - Frontend LoginGate component with i18n (en/zh), conditional logout button * fix: dashboard login security hardening - Timing-safe password comparison (crypto.timingSafeEqual) - Rate limiter cleanup of expired entries to prevent memory leak - Extract shared parseSessionCookie to src/utils/parse-cookie.ts - Remove silent password.trim() that could cause subtle mismatches - Logout button visible on mobile (icon-only on small screens) - Fix fragi…91ee702 -
fix: model list not updating at startup — fast-retry on auth race (#149)
icebear +1: icebear0828Unverified signatureRoot cause: token refresh and model fetch race at startup. Initial fetch fires 1s after boot, but accounts may still be refreshing OAuth tokens. isAuthenticated() returns false → fetch silently skips → next retry is ~1 hour later. - model-fetcher: fast-retry (10s × 12 attempts) until accounts are ready - config/models.yaml: add gpt-5.4/5.4-mini/5.3-codex (restored 2026-03-18) - Export hasFetchedModels() for observability - 4 new tests covering retry/fallback/immediate-success scenarios Co-authored-by: icebear0828 <icebear0828@users.noreply.github.com>56be298 -
feat: usage stats page with time-series token tracking (#147)
icebear +1: icebear0828Unverified signature* feat: usage stats page with time-series token tracking - Background snapshot recording (piggybacks on quota refresh, ~5min interval) - UsageStatsStore: JSON persistence, 7-day retention, delta computation - GET /admin/usage-stats/summary — live cumulative totals - GET /admin/usage-stats/history — time-series deltas with hourly/daily granularity - Pure SVG line charts (input/output tokens + request count) - Granularity (hourly/daily) and range (24h/3d/7d) selectors - 13 backend tests (snapshot, delta, aggregation, retention, API routes) - i18n: en + zh * fix: usage stats review fixes — chart coords, refresh, cleanup --------- Co-authored-by: icebear0828 <icebear0828@users.noreply.github.com>df56b50 -
feat: account management page with batch operations (#146)
icebear +1: icebear0828Unverified signature* fix: treat unfetched plan types as possibly compatible in acquire() When model-fetcher fails to fetch models for a plan type (e.g. the selected free account had an expired token), _planModelMap only contains successfully fetched plans. acquire() then excludes all accounts whose planType is absent from the map — even though their compatibility is unknown, not negative. Add isPlanFetched() to model-store and use it in acquire() to distinguish "plan was fetched and model is absent" (exclude) from "plan was never fetched" (include as possibly compatible). This prevents false 503s when a plan's model fetch failed at startup. * feat: account management page with batch operations Add #/account-management page for bulk account management: - POST /auth/accounts/batch-delete and batch-status endpoints - Batch delete, set active, set disabled with confirmation - Status summary chips (clickable to filter) - Reuse AccountTable with optional proxy column - Import/export accessible from new pag…7516302 -
fix: exclude test files from tsc build to unbreak Docker CI
icebear0828tsconfig included src/**/__tests__/** and *.test.ts, so `npx tsc` in Dockerfile compiled test files. When test fixtures drifted from changed interfaces (e.g. CurlFetchResponse gaining/losing fields), Docker build failed even though Vitest passed. Exclude test globs from the build tsconfig and fix the stale `headers` property in update-checker mocks.28a98b1 -
fix: write runtime cache to data/ instead of git-tracked config/ (#138)
icebear +1: icebear0828Unverified signaturemodel-store synced merged catalog back to config/models.yaml on every backend fetch (~1h), and update-checker mutated config/default.yaml on version detection. Both made the repo dirty after normal runtime. - syncStaticModels() → data/models-cache.yaml (gitignored) - applyVersionUpdate() → data/version-state.json (gitignored) - loadStaticModels() overlays cache on cold start for fallback - loadCurrentConfig() checks version override before YAML baseline - config/ is now read-only for runtime operations Co-authored-by: icebear0828 <icebear0828@users.noreply.github.com>50720ce -
feat: detect banned accounts and show status in dashboard (#142)
icebear +1: icebear0828Unverified signature* feat: detect banned accounts and show status in dashboard When upstream returns non-Cloudflare 403, the account is now marked as "banned" instead of silently failing quota fetches with no UI feedback. - Add "banned" to AccountStatus type - usage-refresher: detect 403 ban during quota fetch, auto-recover on success - proxy-handler: detect 403 ban during requests, fallback to next account - Dashboard: rose-colored "Banned"/"已封禁" badge in card/table views - Status filter dropdown includes "Banned" option - acquire() naturally skips banned (status !== "active") * feat: handle 401 token invalidation with auto-retry Previously 401 "token has been invalidated" was passed through to the client without marking the account or trying alternatives. - proxy-handler: 401 → markStatus("expired") + try next account - usage-refresher: 401 → markStatus("expired") during quota fetch - Refresh scheduler will attempt token renewal for expired accounts --------- Co-authored-by: icebear0828 <icebear…ba262d0
-
fix: handle new Responses SSE lifecycle events (#137)
icebear +1: icebear0828Unverified signature* fix: handle new Responses SSE lifecycle events (output_item.added message, content_part) Upstream Codex API now wraps text responses in message/content_part lifecycle events. Our parser only recognized output_item.added with item.type=function_call, causing unknown event log spam. - Widen output_item.added to accept any item.type (message, etc.) - Add content_part.added and content_part.done as recognized no-op events - Update extractor to handle optional call_id/name on non-function_call items * chore: remove loose index signature from CodexOutputItemAddedEvent.item --------- Co-authored-by: icebear0828 <icebear0828@users.noreply.github.com>1531084
-
fix: remove isCodexCompatibleId whitelist — trust backend model list (#129)
icebear +1: icebear0828Unverified signatureThe regex whitelist filtered out models like gpt-5.4-mini whose ID didn't match hardcoded patterns. Remove the filter entirely and trust the /codex/models endpoint which only returns Codex-compatible models. Co-authored-by: icebear0828 <icebear0828@users.noreply.github.com>1e54caa -
fix: add missing userId field to test fixtures (#128)
icebear +1: icebear0828Unverified signatureTwo test helper functions (account-persistence, rotation-strategy) were missing the required userId field added in #127. Co-authored-by: icebear0828 <icebear0828@users.noreply.github.com>b72575f -
fix: allow multiple team accounts sharing same chatgpt_account_id (#126) (#127)
icebear +1: icebear0828Unverified signatureTeam members share chatgpt_account_id but have distinct chatgpt_user_id. Change dedup key from accountId alone to accountId + userId composite, so each team member gets their own entry in the pool. - Add userId field to AccountEntry/AccountInfo types - Dedup by (accountId, userId) in addAccount() - Backfill userId from JWT on persistence load - 5 new tests for team dedup scenarios Co-authored-by: icebear0828 <icebear0828@users.noreply.github.com>7e5ecc4
-
fix: prevent quota-exhausted accounts from cycling back to active (#115)
icebear0828Accounts that hit their quota limit were cycling between rate_limited and active status every 60s, causing requests to be routed to exhausted accounts repeatedly. Root cause: markQuotaExhausted() only worked on "active" accounts, and the quota refresher only checked "active" accounts. Since 429 backoff (60s) recovered accounts to "active" before the 5-min quota refresh could mark them exhausted with the real reset time, neither mechanism could break the cycle. Fix: - markQuotaExhausted() now works on rate_limited accounts (only extends, never shortens rate_limit_until) - Quota refresher now includes rate_limited accounts in its fetch cycle261b199
-
fix: clear quota warnings when account is deleted (#100) (#102)
icebear +1: icebear0828Unverified signatureDELETE /auth/accounts/:id was missing clearWarnings() call, causing stale quota warning banners to persist in the dashboard after account removal. Co-authored-by: icebear0828 <icebear0828@users.noreply.github.com>5c5129f -
refactor: decouple AccountPool, split codex-api and web.ts, fix CI (#113)
icebear +1: icebear0828Unverified signature* refactor: decouple AccountPool, split codex-api and web.ts, fix CI Phase 0 — CI fixes: - Fix TS2322: WS transport instructions type mismatch (Electron/Docker build) - Remove duplicate selectAll i18n keys (Vite warning) - Add rebase retry to sync-changelog.yml push step (race with bump-electron) Phase 1-5 — Architecture improvements: - Extract codex-types.ts: API type definitions separated from class - Extract rotation-strategy.ts: pure-function selection logic (10 tests) - Split web.ts (605 LOC) into routes/admin/ (health/update/connection/settings) - Extract account-persistence.ts: injectable fs persistence interface (8 tests) - Split codex-api.ts into codex-sse/usage/models modules (10 tests) All existing import paths preserved via re-exports. 902 tests pass (70 files), build clean. * fix: address PR #113 review issues before merge - Fix needsPersist regression: load() now returns { entries, needsPersist } and auto-persists when backfill is applied (restores original behavio…0c8b3c0 -
fix: make instructions optional in /v1/responses for client compatibility (#71) (#112)
icebear +1: icebear0828Unverified signatureCherry and other third-party clients don't send the `instructions` field. Previously this returned 400; now it defaults to an empty string. Co-authored-by: icebear0828 <icebear0828@users.noreply.github.com>6fa846e -
feat: add sticky rotation strategy (#107) (#110)
icebear +1: icebear0828Unverified signature* feat: add sticky rotation strategy with admin UI (#107) Keep using the same account until rate-limited or quota exhausted, instead of rotating across multiple accounts. - Add "sticky" to rotation_strategy config enum - Implement sticky sort in selectByStrategy() (last_used desc) - Add GET/POST /admin/rotation-settings endpoints - Add RotationSettings component (sticky vs rotation radio group) - Add i18n translations (en + zh) - 13 new tests (account-pool-sticky + rotation-settings routes) * refactor: extract ROTATION_STRATEGIES constant to eliminate hardcoded duplication Single source of truth for valid rotation strategies — route handler and Zod schema both reference the exported constant. --------- Co-authored-by: icebear0828 <icebear0828@users.noreply.github.com>d0eeb87 -
fix: handle stream disconnection properly in curl transport and proxy handler (#108)
icebear +1: icebear0828Unverified signature1. curl-cli-transport: signal error (not close) when curl exits with non-zero code mid-stream, so downstream readers get a proper error instead of a silent truncation. 2. proxy-handler: catch s.write() failures during streaming to detect client disconnects, abort the upstream curl process, and stop the stream cleanly instead of leaking resources. 3. proxy-handler: don't re-throw after sending error SSE event — the stream is already closing, re-throwing causes unhandled rejection. Co-authored-by: icebear0828 <icebear0828@users.noreply.github.com>45525c6 -
feat: add POST /admin/refresh-models endpoint + plan routing integration tests (#104)
icebear +1: icebear0828Unverified signatureModel-fetcher's ~1h cache caused stale plan maps that blocked valid model+plan combinations (e.g. free accounts couldn't use gpt-5.4 after it was opened to free tier). This adds a manual refresh endpoint and integration tests covering the full proxy handler plan routing path. Co-authored-by: icebear0828 <icebear0828@users.noreply.github.com>fce5280 -
test: add plan-based model routing tests (14 cases)
icebear0828- model-store: plan map build, reverse index, plan updates, non-Codex slug filtering, gpt-oss admission, store debug output - account-pool: plan-matched acquire, team-only rejection, fallback on empty plans, plan map update re-routing, preference ordering Covers the exact code path where free accounts get blocked from models that the backend has since opened to free tier (gpt-5.4 case).9c1c54e
-
feat: add quota settings UI to dashboard (#101)
icebear +1: icebear0828* feat: add quota settings UI to dashboard (#92) Allow users to configure quota refresh interval, warning thresholds, and skip-exhausted toggle directly from the web dashboard instead of editing YAML manually. * docs: update CHANGELOG for quota settings UI --------- Co-authored-by: icebear0828 <icebear0828@users.noreply.github.com>07041c6 -
fix: macOS Electron login error -86 (EBADARCH) due to wrong-arch curl binary (#96) (#98)
icebear +1: icebear0828Unverified signatureCI built both arm64/x64 DMGs on a single arm64 runner, bundling only the arm64 curl-impersonate into both. Intel Mac users got EBADARCH when spawning the binary during OAuth token exchange. - Split macOS CI into per-arch jobs with --arch flag for setup-curl.ts - Add --arch override to setup-curl.ts for cross-compilation - Improve spawn error message to diagnose architecture mismatch Co-authored-by: icebear0828 <icebear0828@users.noreply.github.com>6c928c9 -
fix: disable desktop context injection by default (#95) (#97)
icebear +1: icebear0828Unverified signature* fix: disable desktop context injection by default (#95) The proxy was injecting a ~1500 token desktop context system prompt into every request, inflating prompt_tokens from ~17 to ~1587 for a simple message. Add `model.inject_desktop_context` config option (default false) to skip injection unless explicitly enabled. * docs: update CHANGELOG for #95 * chore: revert unrelated default.yaml changes --------- Co-authored-by: icebear0828 <icebear0828@users.noreply.github.com>921d606 -
feat: auto-refresh quota with tiered warnings (#92) (#93)
icebear +1: icebear0828Unverified signatureBackground quota refresher fetches official usage every 5min (configurable), caches on AccountEntry for instant dashboard reads, and marks exhausted accounts as rate_limited. Tiered warning thresholds (default 80%/90%) trigger visual banners in the dashboard. Frontend auto-polls every 30s. Co-authored-by: icebear0828 <icebear0828@users.noreply.github.com>4f2665c -
Revert "fix: fall back to all accounts when model plan doesn't match (#91)"
icebear0828This reverts commit 5abff3872cdb12703c51d0eb110c7e67f0dd415d.f14db3a -
fix: fall back to all accounts when model plan doesn't match (#91)
icebear +1: icebear0828Unverified signatureThe backend's /codex/models API returns different model lists per plan (e.g. free doesn't list gpt-5.4), but free accounts can actually use those models. Previously, acquire() returned null when no account matched the model's plan requirements, blocking valid requests. Now falls back to all available accounts instead of hard-blocking, since the backend's plan-based model list is incomplete. Co-authored-by: icebear0828 <icebear0828@users.noreply.github.com>5abff38 -
fix: skip proxy update checker in Electron mode (#89)
icebear +1: icebear0828Unverified signatureElectron has its own native auto-updater (electron-updater) that handles download and install via system tray. The backend's startProxyUpdateChecker() was also running, causing the web UI UpdateModal to pop up and conflict with the native updater. - Skip startProxyUpdateChecker() when deploy mode is "electron" - Don't auto-open UpdateModal in Electron mode Co-authored-by: icebear0828 <icebear0828@users.noreply.github.com>7445795 -
fix: dynamic import `ws` to prevent Electron ESM startup crash (#88)
icebear +1: icebear0828Unverified signatureThe `ws` package uses CJS `require('events')` internally. When esbuild bundles the server as ESM for Electron, these dynamic requires fail at startup with "Dynamic require of 'events' is not supported". Switch to lazy dynamic `import("ws")` so the CJS dependency chain is only resolved at first WebSocket use, not at module load time. Also lazy-import `https-proxy-agent` for the same reason. Closes #87 Co-authored-by: icebear0828 <icebear0828@users.noreply.github.com>ea2be52
-
fix: server-side export filtering and JSON parse safety
icebear0828- Export endpoint supports ?ids= query param for selective export, preventing full token payload over the network - Import hook wraps JSON.parse in try-catch for invalid files - Add test for selective export filteringa8a2d95 -
feat: bulk account import/export with selective export (#82)
icebear0828- GET /auth/accounts/export — export all accounts (with tokens) for backup - POST /auth/accounts/import — bulk import from JSON with dedup detection - Dashboard: import/export buttons next to refresh, multi-file import - Selective export: checkbox on each account card, select all/deselect all - 9 unit tests for backend routes268c5a4 -
feat: WebSocket transport + previous_response_id for multi-turn (#84)
icebear +1: icebear0828Unverified signature* feat: WebSocket transport + previous_response_id for multi-turn (#83) The Codex backend only supports `previous_response_id` over WebSocket, not HTTP SSE. This adds a dual-track transport layer: - /v1/responses now connects to the backend via WebSocket by default - WebSocket omits the `store` field, letting the server persist responses - Clients can send `previous_response_id` to reference earlier turns - On WebSocket failure, automatically falls back to HTTP SSE New files: - src/proxy/ws-transport.ts — WebSocket-to-SSE bridge (ReadableStream) Dependencies: ws, https-proxy-agent * fix: move ws from devDependencies to dependencies ws is imported at runtime by src/proxy/ws-transport.ts, so it must be in dependencies for production installs (npm install --omit=dev). --------- Co-authored-by: icebear0828 <icebear0828@users.noreply.github.com>d07b5c0 -
fix: add Cache-Control headers to prevent stale frontend after update (#79)
icebear +1: icebear0828Unverified signatureindex.html served with no-cache so browsers always revalidate after rebuild. /assets/* served with immutable (Vite content-hashed filenames). Fixes #78 where users saw old model list after updating. Co-authored-by: icebear0828 <icebear0828@users.noreply.github.com>edb874f -
feat: tuple schema (prefixItems) support for structured outputs (#77)
icebear +1: icebear0828Unverified signature* feat: tuple schema support — prefixItems auto-conversion for structured outputs Upstream Codex API doesn't support JSON Schema prefixItems (tuple), so we convert tuple schemas to equivalent object schemas on the request side and reconvert the response back to arrays. All three endpoints (OpenAI, Gemini, Responses passthrough) are covered. - New module: src/translation/tuple-schema.ts (convert/reconvert/detect) - prepareSchema() composes tuple conversion + additionalProperties injection - Streaming: buffer text deltas when tupleSchema present, flush reconverted on completion - 23 unit tests for core conversion logic * fix: responses endpoint tuple reconversion + cleanup - Add tuple schema reconversion to /v1/responses (stream + collect), making all three endpoints consistent - Remove unrelated web/src/hooks/use-status.ts from this branch - Add console.warn to silent catch blocks for easier debugging - Add streaming buffer tradeoff comment --------- Co-authored-by: icebear0828 …e25a730
-
feat: model list auto-sync + theme contrast fix
icebear0828- Backend: auto-write merged catalog back to models.yaml after dynamic fetch - Frontend: poll /v1/models/catalog every 60s to pick up new models - Theme: light mode bg #f6f8f6 → #ffffff for clearer dark/light contrast - Remove unused legacy web/src/hooks/use-status.ts94a5578
-
feat: Docker image auto-publish to GHCR + Watchtower support
icebear0828- Add docker-publish.yml workflow: multi-arch (amd64/arm64) build on every push to master, published to ghcr.io/icebear0828/codex-proxy - Switch docker-compose.yml from local build to pre-built GHCR image - Add commented Watchtower service block for automatic updates - Simplify README Docker section: no git clone needed, just curl configs - Update dashboard Docker hint to mention Watchtower option - Add dockerAutoUpdateHint i18n key (en/zh)7ff102d -
fix: mode-specific update hints for Docker and Electron
icebear0828- apply-update 400 response now includes mode + hint per deploy type - Docker: docker compose pull && docker compose up -d (was --build) - Electron: prompt to use built-in auto-updater instead of GitHub link - Add electronUpdateHint i18n key (en/zh)009c43a -
fix: simplify hardRestart — remove helper script, spawn directly with EADDRINUSE retry
icebear0828- Remove `.restart-helper.cjs` temp file approach (fragile, silent failure) - Spawn new server process directly; relies on index.ts EADDRINUSE retry - Bump EADDRINUSE retry from 5→10 attempts for safer restart window - Validate nodeExe exists before spawn to prevent silent death - Log child output to `.restart.log` for post-mortem debugging - Fix 4 failing tests: align with git checkout step + SSE stream format73fd0ce -
feat: SSE update progress + reliable restart on Windows
icebear0828 +1: Claude Opus 4.6 (1M context)- apply-update returns SSE stream with step-by-step progress (pull → install → build → restart) - Frontend parses SSE, shows progress steps with checkmarks in modal - Restart helper writes temp .restart-helper.cjs file instead of -e inline script (fixes Windows command escaping issues) - RESTART_TIMEOUT increased from 30s to 120s - Frontend falls back to restart polling if connection drops mid-stream Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>236dd56 -
fix: dark mode tabs, StableText bilingual width, self-update reliability
icebear0828 +1: Claude Opus 4.6 (1M context)- CodeExamples: fix dark mode tab text invisible (text-dim alpha format broken with CSS variables, use plain #8b949e; tab background use solid #0d1117 instead of transparent bg-dark/30) - StableText: use both en+zh invisible references so button width never jumps on language switch (root cause of recurring i18n layout bug) - Header: remove version/commit display (already shown in footer) - self-update: git checkout -- . before pull to discard local drift; restart via helper script that waits for port release before starting new process (fixes EADDRINUSE on Windows) - apply-update: SSE streaming progress (pull → install → build → restart) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>ca241c4 -
fix: StableText uses both en+zh references for stable button width
icebear0828 +1: Claude Opus 4.6 (1M context)StableText previously used a single reference (either hardcoded English or dynamic t()), so buttons always jumped width on language switch. Now uses both translations.en[key] and translations.zh[key] as invisible grid references — width is always max(en, zh), never changes. Also fix self-update: run git checkout -- . before git pull to discard local modifications (e.g. package-lock.json drift) that block the pull. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>bdd4755 -
fix: model store dual index, desktop UI fallback, dynamic port support
icebear0828- Model store: replace O(n) plan map with dual index (planType→models + modelId→plans), both O(1) - Desktop UI: fallback to web UI when desktop build missing, add startup path diagnostics - Server: resolve actual bound port from server.address() to support port=0 - Model fetcher: fix log message (said 5s, actually 1s) - Update Codex Desktop fingerprint to v26.311.21342 (build 993)b94940f -
fix: self-update restart with graceful close + 3s timeout fallback
icebear0828 +1: Claude Opus 4.6 (1M context)Move restart logic from index.ts handler into self-update.ts hardRestart(). Try graceful server.close() for up to 3s, then force spawn + exit. Add EADDRINUSE retry (5 attempts) so new process survives port race. Eliminates the hang caused by active SSE connections blocking server.close(). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>996585e -
fix: server restart hanging when active SSE connections exist
icebear0828 +1: Claude Opus 4.6 (1M context)server.close() waits for all connections to drain before calling its callback, which hangs indefinitely with active SSE streams. Add a 3s timeout to force shutdown, ensuring the restart handler always spawns the new process and exits. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>1301d7a
-
feat: display secondary rate limit window (weekly cap) in dashboard
icebear0828 +1: Claude Opus 4.6Add secondary_rate_limit to CodexQuota type and map secondary_window from Codex usage API. Dashboard now shows both primary (hourly) and secondary (weekly) quota bars with independent progress and reset times. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>2996fd8 -
refactor: use toErrorStatus() instead of inline duplicate
icebear0828 +1: Claude Opus 4.6Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>d9ac395 -
fix: use real reset duration from 429 response + auto-fallback to next account (#65)
icebear0828 +1: Claude Opus 4.6- Parse resets_in_seconds / resets_at from 429 error body instead of using fixed 60s backoff, so rate-limited accounts stay inactive for their actual cooldown period (e.g. 5.5 days for free plans) - Auto-retry with the next available account on 429 before returning error to client (same pattern as model-not-supported retry) - Add window_reset_at as second tiebreaker in least_used scheduling (prefer accounts whose quota resets sooner) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>8cde2e9
-
fix: prevent false window rollover resets in syncRateLimitWindow
icebear0828 +1: Claude Opus 4.6Codex API reset_at drifts by hundreds of seconds between calls due to activity-based sliding windows. The strict equality check misinterpreted this drift as a window rollover, resetting window_request_count to 0 on every quota fetch. Now only resets when drift exceeds 50% of the window size, correctly distinguishing real rollovers from API noise. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>cba2903 -
fix: review improvements — schema walker, status helper, UI i18n, CI version skip
icebear0828 +1: Claude Opus 4.61. walkSchema: add patternProperties, if/then/else traversal + cycle detection 2. proxy-handler: extract toErrorStatus() helper, centralize 4x StatusCode casts 3. Header StableText: dynamic t() references for i18n + whitespace-nowrap on status button 4. sync-electron.yml: cancel-in-progress: false + merge two pushes into one Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>6220911 -
fix: recursive additionalProperties injection + upstream error status propagation
icebear0828 +1: Claude Opus 4.61. Add injectAdditionalProperties() to recursively inject `additionalProperties: false` on every object-type node in JSON schemas. Codex API requires this explicitly but OpenAI's native API auto-injects it — our proxy was passing schemas verbatim causing 400s. 2. Fix CONNECT tunnel header parsing in curl-cli-transport: loop to skip intermediate header blocks (CONNECT 200, 100 Continue) before parsing the actual response headers. 3. Extract upstream HTTP status from error messages in non-streaming collect path instead of hardcoding 502. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>4779e44