[WRONG BRANCH] fix(responses): prevent cross-request reasoning replay - #37
[WRONG BRANCH] fix(responses): prevent cross-request reasoning replay#37luvs01 wants to merge 134 commits into
Conversation
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
|
Important Review skippedDraft detected. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
📝 WalkthroughWalkthroughThe change removes the ChangesReasoning replay-cache scoping
Estimated code review effort: 3 (Moderate) | ~20 minutes Suggested reviewers: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 2📝 Generate docstrings 💡
🛠️ Fix failing CI checks 💡
🧪 Generate unit tests (beta)
Comment |
⏳ DRAFT
What to do
Its title has been prefixed with |
|
✅ Deterministic PR hygiene checks passed. |
|
@coderabbitai review |
✅ Action performedReview finished.
|
Extend Routing Profiles with optional Compatibility Lab evidence gates, bounded read-only verdict lookup, evaluator penalties, trace explainability, management API parity, and Models editor controls without routing-path probes.
…ctiveness-producer feat(lab): CL-07 task effectiveness evidence
Record CL-06 closure in stack status, fix master plan row, and tag merge/accepted heads to match CL-07 backup practice.
The resolve-pr job mapped a trusted CodeRabbit status SHA back to a PR via the commit-to-PR association index. That index can lag a fresh head push (seen on PR lidge-jun#1441), returning zero matches for a genuine current head, so revalidation was silently skipped and new unresolved findings never re-drafted the PR. When the index yields no unique match, fall back to reconciling directly against the live open-PR list by head.sha — the same authoritative identity the write gate uses. Resolution still requires exactly one open match, so ambiguity remains fail-closed. The fallback is a read and stays within the resolver's pull-requests:read permission. Add behavioural harness tests for the resolver: empty-index fallback, no-match skip, fresh-index direct resolve, and fail-closed on error.
Address CodeRabbit feedback on the resolver tests: - Add a regression test for the ambiguous case: two open PRs sharing the same head SHA must fail closed (no pull-number output), instead of only covering zero and one matches. - Correct the fail-closed test so both resolution calls reject. It previously only failed pulls.list, so the association-index error path was never exercised; assert both warning messages now.
Address CodeRabbit feedback on the ambiguous fallback test: assert that the resolver actually consults the live open-PR list (one pulls.list call with the open-state filter) so the test cannot pass without the head-SHA reconciliation path. The empty-output and ambiguous/stale log assertions are unchanged.
The ambiguous-head regression (two open PRs sharing the same head.sha) only asserted empty output and the skip log. Without a pulls.list assertion it would pass even if the head-SHA fallback were removed, because the empty association index by itself already causes the skip. Assert the live fallback ran: exactly one pulls.list call with the open-state filter, alongside the retained empty-output and skip-log assertions. The neighbouring zero-match test keeps its own independent fallback assertion.
fix(ci): reconcile PR from head SHA when the commit-PR index lags
* fix: recognize legacy Team capacity Treat upstream plan 'team' as the Business-tier configured weight so valid Codex pools are not excluded from dashboard capacity estimates. * test: add all-Team pool capacity regression --------- Co-authored-by: Wibias <37517432+Wibias@users.noreply.github.com>
…ice grant + free/paid live catalog (lidge-jun#1397) * feat(providers): add Nous Portal (Nous Research) OAuth provider — device grant + free/paid live catalog (Closes lidge-jun#1148) * feat(providers): seed Nous Portal free models from live Portal list (hy3, laguna-s/xs, step-3.7-flash) * test(nous-oauth): cover device-flow error paths and refresh-token fallback - access_denied / expired_token surface as terminal NousTokenError - slow_down backs off (interval bump) then resumes polling to success - authorization_pending until deadline raises a timed-out error - refresh omitting a new refresh_token keeps the previous one (header sent) * fix(oauth/nous): enforce HTTPS base URL and single-use refresh rotation; docs + tests Addresses the two CHANGES_REQUESTED blockers on PR lidge-jun#1397: 1. resolvePortalBaseUrl() now hard-validates the full OAuth base URL via new URL() and throws BEFORE any fetch is dispatched: rejects non-HTTPS schemes, embedded credentials, query strings, and fragments; returns only url.origin. Aligns opencodex with Hermes hermes_cli/auth.py (_NOUS_PORTAL_ALLOWED_HOSTS, https-only) and prevents the single-use refresh token / inference JWT from ever traversing cleartext. 2. parseTokenPayload() no longer falls back to the submitted refresh token. A response that omits refresh_token, or returns a replacement equal to the submitted token, throws NousTokenError(oauthError: 'refresh_token_reused') so the next refresh cannot replay a consumed credential and trigger session revocation. Also: - tests/nous-oauth.test.ts: HTTPS/URL hardening (fetch never reached), missing/equal refresh rejection, and NousTokenError.oauthError contract on access_denied / expired_token. - tests/nous-oauth-live.test.ts: opt-in, CI-skipped live verification that reads the local refresh token without printing it (lengths only), asserts rotation + read-only /v1/models reachability. No provider key is shared. - docs ru/guides/providers.md: eight OAuth presets, ocx login nous, nous row. Verified: tsc --noEmit, bun test nous-oauth (17/17), privacy:scan passed, targeted suite 186/186. Full bun run test in progress. * fix(oauth/nous): failure-atomic refresh, terminal errors, scope check, redirect guard Addresses the 10 review points from Wibias on PR lidge-jun#1397: - #2 Single-use refresh is now failure-atomic. A durable refresh-intent file (keyed by a sha256 of the refresh token, never the token in cleartext) is written before the refresh request and cleared only after the rotated token is obtained. If the server responds but the rotation cannot be persisted, the intent is marked 'uncertain' and a later refresh REFUSES to replay the possibly-consumed token (NousTokenError refresh_token_reused, terminal) — forcing a clean re-auth instead of a session-revoking replay. - #3 Credential-bearing OAuth requests (device + token) now pass redirect: 'error' so custom auth headers cannot follow a cross-origin redirect. - #4 invalid_token (and invalid_grant/revoked/revoked_token) are now terminal NousTokenError values that drive re-authentication. - #5 The returned access-token JWT scope is validated for inference:invoke before the credential is treated as usable. An insufficient-scope token is a terminal error that STILL surfaces the already-rotated refresh token, so the caller can persist it and re-auth without discarding the rotation. - #6 Live /models test accepts both the OpenAI-style { data: [...] } body and a bare array (production contract). - #7 freeTier is no longer true for the mixed free/paid provider; free models are classified at model level (the :free slugs). Parity test updated. - #8 pollForToken parses the response body once and passes the payload through to the error path instead of re-reading a consumed body. - #9 sleep() now removes its abort listener on both resolve and abort, so polling iterations do not accumulate listeners. - #1 The live test is now non-destructive: it persists the rotated token back through mergeAccountCredential (prod path), so the local session stays valid. - #10 Russian docs already mirror the English source (8 presets, ocx login nous, nous table row with device grant + single-use rotation). No provider API key is shared; privacy:scan passes. Verified: tsc --noEmit, nous-oauth 21/21, provider-registry-parity + targeted suite 193/193. * fix(oauth/nous): close the uncertain-outcome window for single-use refresh Deep re-review (real execution proof) showed the first intent design still relied on the server to refuse a replay when the rotated token was obtained but lost before the store persisted it. Harden the contract: - The refresh-intent file now stays in the 'submitted' state after a successful rotation (it previously cleared it). It is only cleared by the account store via clearNousRefreshIntent() once mergeAccountCredential persists the rotated token. - Replaying a token whose intent is 'submitted' OR 'uncertain' is refused up front (NousTokenError refresh_token_reused, terminal) — never blindly replayed, and without depending on the server's reuse detection. - Network-level failure (server never saw the token) still clears the intent so a retry is safe. - clearNousRefreshIntent is wired into the shared refresh orchestrator (src/oauth/index.ts) right after mergeAccountCredential; it is a no-op for non-Nous providers (they never write an intent). Verified by a real execution probe (not just mocks): a rotation that obtains the rotated token but crashes before persistence now makes the next replay of the old token refused by the guard, with the intent present on disk. Tests: nous-oauth 23/23 (adds 'rotated token obtained but not persisted blocks replay', '200 unparseable body marks uncertain', 'network failure replayable'); targeted suite 195/195. tsc + privacy:scan clean. * fix(oauth/nous): fail-closed refresh-intent, hardened IO, shared-classifier terminal Addresses the remaining CHANGES_REQUESTED findings from Wibias on PR lidge-jun#1397 (head after this: fail-closed end-to-end single-use refresh recovery). 1. Refresh-intent is now FAIL-CLOSED and reuses the repo's hardened config IO: - writeRefreshIntent uses atomicWriteFile + hardenConfigDir (owner-only 0o700 dir) and THROWS on failure instead of swallowing it (refresh is refused rather than proceeding blind). readRefreshIntent treats any read/parse/permission error as 'uncertain' (replay refused), never as absent. clearNousRefreshIntent surfaces non-ENOENT failures. - Ambiguous fetch failures (timeout/abort/connection) now mark the intent 'uncertain' instead of clearing it: dispatch may have occurred, so the submitted token must never be replayed. 2. Post-persist cleanup is wired into the correct coordinator (refreshGenericAccountWithLock, the actual Nous path) after a successful mergeAccountCredential; removed the misplaced call from the xAI path. 3. Shared terminal classifier now honors NousTokenError.terminal (so provider-classified invalid_token / insufficient_scope move the account to re-authentication instead of staying retryable). 4. Opt-in live test refreshes through the production, generation-aware, account-locked coordinator (refreshGenericAccountWithLock) instead of calling refreshNousToken + mergeAccountCredential outside the lock. 5. First normal refresh-wiring test now isolates OPENCODEX_HOME so it cannot leave durable intent state in the config tree. 6. Embedded-credential URL validation no longer echoes the raw (credential- bearing) URL in the thrown error. 7. NousTokenError no longer stores live credentials as an enumerable property; only the rotated refresh token is retained, via a non-enumerable getter (getRotatedRefresh), so structured logging/serialization cannot leak it. 8. Replay-guard test now proves fetch is never called (not just the error shape). 9. Provider docs (ja/ko/zh-cn) updated to 'eight' OAuth presets to match the English/Russian sources. Verified by a real execution probe (not just mocks): rotation obtained but not persisted -> next replay refused by guard; network failure -> fail-closed uncertain (not replayable); insufficient_scope error does not leak credentials. Tests: nous-oauth 23/23 (adds fail-closed network-failure, replay-guard proves-no-fetch, non-enumerable credentials); targeted suite 195/195. tsc --noEmit and bun run privacy:scan clean. Kept draft, no maintainer-sponsored. * fix(oauth/nous): fail-closed refresh-intent schema, atomic HTTP failure, non-terminal local IO - Validate persisted refresh-intent schema; corrupt/unknown state is treated as uncertain (replay refused), never absent. Only ENOENT means no intent. - Classify HTTP refresh failures atomically: ambiguous 5xx/gateway responses leave the submitted token blocked (uncertain); only definitive 4xx client rejections clear the intent for a safe retry. - Surface local durable-write/read/cleanup failures as a non-terminal RefreshIntentIOError so the coordinator does not mark a valid credential needsReauth for broken local persistence. - Mark device-flow access_denied/expired_token as terminal consistently. - Handle non-JSON successful device-code bodies with the clear validation error instead of a raw JSON parse leak. - Redact raw values from malformed base-URL diagnostics. - Align the opaque-token docstring with the JWT scope gate. - Synchronize OAuth provider lists across en/ja/ko/ru/zh-cn docs. - Add regression coverage for all safety contracts. * test(oauth/nous): cover origin-only base URL normalization * test(oauth/nous): make intent-write failure tests platform-independent Planting a file at the intent-directory path made the guard read fail with ENOTDIR on Linux (treated as uncertain -> terminal) before any write could fail, so the test could not reach the non-terminal operational-error path. Force atomicWriteFile to fail via a spy instead, deterministically on every platform: the pre-dispatch write abort must surface RefreshIntentIOError, never call fetch, and leave the account valid. * fix(oauth/nous): fail closed on every ambiguous post-dispatch refresh outcome A non-2xx response does not prove the single-use refresh token was not consumed: 429 rate limits, unknown/custom 4xx, and gateway-generated client-class errors can be returned after the remote side already processed the token. Previously every 4xx cleared the durable refresh intent, which made a possibly-consumed RT-A locally replayable. Now every post-dispatch non-2xx response retains the intent as uncertain (previously only 5xx did), so the submitted token stays blocked and a later refresh is rejected before any fetch. The intent is cleared only after the rotated credential is durably persisted. Pre-dispatch local I/O failures remain distinct non-terminal operational errors. Replace the invented 'safe 4xx' test with regressions proving HTTP 429 and an unknown/custom 4xx both keep the old token blocked and reject a second attempt before fetch (exactly one token-endpoint call). * fix(oauth/nous): post-persist intent cleanup is best-effort; docs/live-test/modelDiscovery cleanups - refreshGenericAccountWithLock: a failure to unlink the old-token refresh- intent file after mergeAccountCredential commits the rotation no longer fails the refresh or marks the account needsReauth. The stale intent keys the old token (no longer stored), so retaining it is safe; the failure is logged non-fatally with no credential material. - Add coordinator-level regressions: the happy path persists RT-B and clears the RT-A intent (nousRefreshIntentBlocksReplay(RT-A) === false), and a forced cleanup failure still resolves with the fresh access token while the stored credential stays RT-B and the account is not marked needsReauth. - Add the provider-level clear-after-persist regression in nous-oauth.test.ts. - English providers doc: after a terminal Nous refresh failure, run 'ocx login nous' to reauthenticate. - Live test: correct the privacy wording (opt-in; credentials go only to the intended Nous endpoints; token values never printed) and parse the live catalog defensively so malformed bodies yield an empty list instead of a crash. - Nous registry modelDiscovery: use path 'models' (resolves against effectiveBaseUrl to the same canonical /v1/models endpoint). * docs(providers): sync Nous Portal details across ja/ko/ru/zh-cn Add the missing ocx login nous command, the full ous provider table row (openai-chat adapter, inference endpoint, device-grant login, per-request inference JWT, live paid/:free discovery, single-use rotated refresh tokens), and the terminal-refresh reauthentication instruction to each translated provider guide, matching the English source. * fix(oauth/nous): preserve rotated RT-B on terminal refresh errors; trim live-test model ids - refreshGenericAccountWithLock: when a terminal NousTokenError carries an already-issued rotated refresh token (e.g. access JWT lacks inference:invoke), persist RT-B generation-safely before forcing reauthentication. The unusable access token is never persisted as valid (empty placeholder, past expiry); RT-A's intent is cleared only after RT-B is durable (best-effort cleanup); persistence failure or a superseding concurrent generation never clears RT-A intent and never overwrites the newer credential; the account is marked needsReauth generation-safely and the caller receives OAuthLoginRequiredError. - Live catalog test: reject empty/whitespace-only model ids (trim before accept). - Coordinator regressions: RT-B preservation on insufficient_scope, RT-B persistence failure keeps RT-A intent blocking, superseded concurrent generation is not overwritten, cleanup failure after RT-B persistence keeps RT-B and marks needsReauth. --------- Co-authored-by: Wibias <37517432+Wibias@users.noreply.github.com>
Co-authored-by: Ingwannu <ingwannu@users.noreply.github.com>
* fix: add per-model structured output opt-out * fix: honor exact structured-output opt-outs * docs(config): clarify exact structured-output opt-outs --------- Co-authored-by: Ingwannu <ingwannu@users.noreply.github.com>
…bute (lidge-jun#1407) * fix(collaboration): scope catalog-state guidance to what we can attribute `collectCodexAppServerCatalogState()` folds every app-server owned by the current user into ONE global observation, and an inbound request carries no sender PID or catalog fingerprint. So a stale process A makes the global state `stale` even when the request came from a fresh process B, and `unknown` can be reached by a process-enumeration failure that says nothing about any particular server. v2 guidance nonetheless injected do not set model or reasoning_effort overrides until Codex restarts off that global observation. Two problems follow: * it prohibits options the active `spawn_agent` tool legitimately advertises, for a request that cannot be attributed to the stale process (lidge-jun#1395); and * the same text was emitted for `unknown`, asserting a cause that was never established and a restart that cannot clear a failed measurement (lidge-jun#1354). For `stale` and `unknown`, withhold opencodex's own disk-derived claims — preferred model, roster, fallback, custom guidance — and say nothing about overrides, leaving the active tool schema authoritative. `fresh` and `not_running` are unchanged. The previous regression asserted that both states contain `do not set`, which codified the scoping bug; it is replaced by coverage that a mixed stale/fresh process set resolving globally to `stale` produces no blanket no-override instruction for an unidentified request. Documents the four-state guidance contract in the sub-agent surface guide, including why `unknown` is not a synonym for `stale`. Verified: bun test tests/multi-agent-compat.test.ts -> 43 pass, 0 fail bun test tests/codex-app-server-processes.test.ts -> 30 pass, 1 skip, 0 fail bun x tsc --noEmit -> clean Fixes lidge-jun#1354 Fixes lidge-jun#1395 * Update sub-agent-surface.md for clarity on states Clarify the distinction between 'stale' and 'unknown' states in the documentation. --------- Co-authored-by: Wibias <37517432+Wibias@users.noreply.github.com>
* fix(codex): honor split SQLite home * fix(codex): fail closed on unreadable SQLite config * fix(codex): fail closed on invalid SQLite config * fix(codex): preserve residue classification boundary --------- Co-authored-by: Ingwannu <ingwannu@users.noreply.github.com>
…ider Register `daybreak-red-latest` and `daybreak-blue-latest` on `openai-apikey`. The alias ids are the stable contract: OpenAI repoints them at newer snapshots over time (red -> gpt-5.6-cyber, blue -> gpt-5.6-sol as of 2026-08-11), so registering the alias inherits future model swaps, while a pinned snapshot id would silently go stale. No snapshot id enters the registry. Both pages mark `v1/chat/completions` Not supported and `v1/responses` Supported, so the rows go only on `openai-apikey` (adapter openai-responses, api.openai.com). The Codex-login `openai` provider is excluded: Daybreak needs separate provisioning and is absent from the pinned upstream snapshot, so a row there would advertise something that path cannot route. `defaultModel` stays `gpt-5.5` — a provisioned-only model is never a default. Metadata per the official pages: red 400,000 context / 272,000 max input, blue 1,050,000 / 922,000, both text+image with reasoning-token support. `modelReasoningEfforts` carries an explicit `[]` for both. Neither page publishes an effort ladder, and omitting the key would NOT mean "no ladder": `configuredReasoningEfforts` returns undefined, and `applyReasoningLevels` then falls back to the full routed ladder, advertising efforts the models never documented. `noReasoningModels` is also wrong here — both document reasoning tokens, so these are reasoning models with no *selectable* ladder. Pricing is `verified-derived`, not `verified`: the pricing table has no `daybreak-*` rows, only the snapshots', so an alias price is a mapped base price. That status is also what keeps the `estimated` marker on, which matters more than usual because a repoint can change the price. Long-context tiers follow the published table rather than the family default: blue gets the 272,000-exclusive tier (its snapshot publishes a full long row), red gets none (its long columns are all "-"). Blue's tier is one explicit `openai-apikey` entry rather than a member of the shared list, which expands across both OpenAI providers and would otherwise mint a Codex-login tier that cannot exist. Verification: bun run typecheck clean; bun run test 10931 pass / 0 fail. Plan and evidence: devlog/_plan/260811_260811-gpt56-cyber-model/
…idge-jun#1435) * fix(catalog): apply providerContextCaps.openai to native OpenAI rows Native OpenAI catalog rows ignored providerContextCaps.openai: the fixed native context overrides in metadata.ts stayed at 372k while routed models were capped. Thread the openai provider cap through the catalog entry builders (finishUpstreamNativeEntry, deriveEntry, buildCatalogEntries, mergeCatalogEntriesForSync), the runtime native metadata accessors (nativeOpenAiContextWindow, nativeModelRows), and routing capability evidence so the catalog, management rows, and proxy routing agree. Preserved native rows without a hardcoded override (e.g. gpt-5.4-mini) are capped the same way, and auto-compaction follows the capped window. The 372k native value remains the default when no cap is configured. Closes lidge-jun#1430 * fix(catalog): cap live Codex discovery rows
…ive-test account read (follow-up to lidge-jun#1397) (lidge-jun#1450) * docs(providers): table-safe Kiro install pipes in translations; fix live-test account read - ja/ko/ru/zh-cn kiro rows: replace literal pipe characters in the Kiro CLI install pipelines with table-safe | entities so the markdown tables render correctly. - nous-oauth-live.test.ts: read the refreshed account with getAccountCredential('nous', accountId) instead of passing an excess accountId argument to getCredential (fixes the type error). * fix(oauth/nous): address CodeRabbit + review findings on the follow-up - preserveNousRotatedRefresh returns the exact generation it wrote, removing the post-merge getAccountCredential re-read so a concurrent writer cannot be marked needsReauth (TOCTOU). Rethrow OAuthMutationBusyError unchanged so the caller can retry. - parseTokenPayload: a missing access_token is now a terminal NousTokenError (invalid_token) instead of a plain Error. - Live test: read the store row id via getAccountSet(...).activeAccountId and pass rowId! to both refreshGenericAccountWithLock and getAccountCredential (the row id is a SHA-256-derived hash, not the JWT sub). - Docs: escape the Kiro install pipes (|) in the English providers table too, and add command-code to the oauth 'Used by' list across all five locales. - Regression: persisted-branch TOCTOU test (concurrent writer not marked needsReauth). * fix(oauth/nous): code/robustness fixes from CodeRabbit outside-diff review - jwtExpiryMs: only accept an exp within a plausible now-relative window, falling back to expires_in for out-of-range claims; clamp skew-adjusted expiry to non-negative. Add a dedicated DEFAULT_ACCESS_TOKEN_TTL_MS so the device-flow window is not reused as the access-token fallback lifetime. - pollForToken: tolerate transient transport errors (timeout/DNS/connection) until the device deadline; only genuine cancellation aborts early. - writeRefreshIntent: re-apply owner-only 0o700 on an existing intent dir. - resolvePortalBaseUrl docstring: stop claiming Hermes host-allowlist parity. - parseTokenPayload: device-login missing refresh_token is invalid_token, not refresh_token_reused; missing access_token is now a terminal error. - index.ts: anchor nous defaultRefreshPolicy explicitly to lazy-only. - Live test: import NOUS_INFERENCE_BASE_URL instead of the hard-coded URL; use a structural refresh-only def type instead of the non-exported OAuthProviderDef. - Regressions: implausible exp falls back to expires_in; device-login missing refresh_token is invalid_token. * fix(oauth/nous): fail closed on refresh-intent hardening, honor device-flow deadline, classify missing access_token as terminal * docs(providers): render Kiro install pipes as visible | in all locale tables A bare | entity inside a code span is emitted literally by Astro's markdown processor, so the Kiro CLI install commands showed the escaped text instead of a pipe. Move the pipe out of the code span so it renders as a visible | between the two command fragments, keeping the markdown table intact across all five locales. * fix(oauth/nous): enforce device-flow deadline on every poll path and normalize null token bodies CodeRabbit follow-up on the deadline cap: authorization_pending and slow_down still slept the full interval and a delayed success response could return credentials after the deadline. Route every retry through a deadline-aware sleep helper and recheck the deadline after each fetch. Also normalize a valid-JSON null response body to an empty object so a successful-but-null payload raises the terminal invalid_token NousTokenError instead of a raw TypeError. Add a regression test for the null-body case.
…#1444) * refactor(cli): move CLI head dispatch into src/cli/root.ts Phase 1 of the CLI deepening: version/help early exits, the ocx ready pre-parse (exit 64 before any preflight), and the bounded Codex-shim auto-restore preflight move from src/cli/index.ts into a new pure module src/cli/root.ts. index.ts now awaits runCli() and dispatches via its existing switch; behavior is preserved byte-for-byte. - parseCliHead is pure and unit-tested (tests/cli-head.test.ts) - P1 source-guards in tests/cli-ready.test.ts relocate to root.ts - cli-head/cli-ready/cli-help: 74 pass; typecheck green * test(cli): cover the trailing help alias in parseCliHead CodeRabbit finding on lidge-jun#1444: the help-flag test covered --help and -h after position 0 but not the bare 'help' token, which hasHelpFlag also matches.
…s (resync) (lidge-jun#1440) * feat(i18n): add Traditional Chinese (zh-TW) across GUI, README, and docs Add a full Taiwan Traditional Chinese locale so Chinese users can pick Simplified or Traditional instead of a single generic "中文" option. - GUI: new zh-TW dictionary, locale wiring, uptime/token/status formatting - README: new README.zh-TW.md and language switcher links - Docs site: zh-tw locale, sidebar labels, landing/frontier copy - Tests: include zh-TW in locale coverage lists - Prefer 供應商 for provider wording in Traditional Chinese surfaces Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(i18n): address zh-TW PR review findings Point zh-tw docs links at /zh-tw, add missing grok-build and windows-memory pages, polish GUI/README/frontier copy, and align Cursor nativeLocalExec plus headless CLI docs with English. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(i18n): close zh-TW content parity gaps from Codex review Backfill Auth mode and Desktop sections, account selection, external provider managers, missing config fields, debug stream labels, and align the README sidecar bullet with English. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(i18n): address residual zh-TW CodeRabbit findings Align how-it-works sidecar steps with multi-backend selection, fix grok-build manual-recipe heading anchor, escape bare pipes in the headless CLI table, and use a host-only openai-responses baseUrl example so /v1 is not doubled. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(i18n): resolve merge conflict markers and resync zh-TW keys Keep zh-TW sidebar translations while adding the new opencode guide entry from upstream, and bring the Traditional Chinese dictionary to full key parity with en after the dev merge. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(i18n): add zh-TW opencode guide page Translate the EN-only guides/opencode page so the zh-TW sidebar entry resolves, addressing the post-merge CodeRabbit finding. * docs(zh-tw): translate 14 remaining reference and guide pages Complete zh-TW docs parity with English source (33/33 pages): - reference/cli/agents, lifecycle, providers-accounts - reference/configuration/providers, server - reference/management-api, proxy-formats - guides/combos, image-bridge, pi, video-bridge - getting-started/for-agents - reference/configuration/agents, routing All pages render Traditional Chinese with lang="zh-TW"; docs-site build produces 247 pages; privacy:scan green. * docs(zh-tw): add Traditional Chinese GUI screenshot evidence Captured the real dashboard surface (providers workspace) with the zh-TW locale active — nav shows 儀表板/供應商/模型/子代理/整合, language picker shows 繁體中文, and the providers copy is fully translated. * fix(i18n, docs): tr locale parity, real-mount language switch test, Taiwan terms, docs drift resync - sync-locale-keys.mjs / locale-parity / en.ts header now include tr - zh-tw-language-switch test mounts a real LanguageProvider surface and flips locale through the actual Select UI (same DOM, persistence, lang) - zh-TW.ts/README zh-TW term fixes: 獲取→取得, 請求頭→標頭, provider 代理→供應商代理 - README language rows gain Türkçe/繁體中文 cross-links - routing.md fully retranslated (policy profiles, exact account selectors, analytics, migration) - providers.md gains Cockpit import, OAuth reliability, Kiro import, A6API, pool capacity, rate limits; presets 50→79 - lifecycle.md gains ocx ready + ocx service repair * docs(zh-tw): resync 16 pages against current English source Deep content-sync pass found and fixed real drift beyond file parity: - configuration.md, cli.md: old full-text pages rewritten as index pages matching the reduced English source (4/4 sections each) - contributing.md: +Branches/Pull requests/Project maintainers/preset evidence - web-dashboard.md: +Sign-in/star-consent sections - server.md: +local clients that cannot receive the token - cli/providers-accounts.md: +ocx account priority/main - sub-agent-surface/combos/claude-code/grok-build/opencode/codex-integration: missing sections added - quickstart: 79 presets, current release wording - codex-app-models: desktop remote-allowlist limitation + current coverage Verified: docs build 265 pages, 34/35 files match en section count (sole delta is an empty en heading), no stale v2.7.1/50-preset strings, no bare internal links missing /zh-tw/. * chore(assets): remove review-only zh-TW screenshot from the repo The screenshot exists only as PR-description evidence; keep review artifacts out of the product repository per review feedback. * docs(assets): restore zh-TW dashboard screenshot as a docs asset Screenshot belongs in assets/ alongside dashboard.png and the other locale-neutral UI captures: the zh-TW providers workspace is the rendered evidence for the localization, referenced from the PR description and suitable for the docs site. * docs(zh-tw): address CodeRabbit review findings on markdown and content - installation.md: blank line after fenced block before admonition - image-bridge.md: fix #configuration anchor to the translated #設定 - pi.md: blank line between fence and admonition close - adapters.md: translate page title (Adapters → 轉接器); blank line before subsection heading - windows-memory.md: sync observed-memory (max of RSS/external/arrayBuffers) and use ocx service repair for existing services - codex-app-models.md: natural passive wording (可供切換) - README.zh-TW.md: drop duplicated 請在 Other CodeRabbit findings were reviewed and intentionally left aligned with the English source (table pipe escapes, v1/base/v2 labels, xAI pricing note, TODO comments, h3 README hero) or are obsolete since the pages were rewritten. * docs(zh-tw): sync 5 pages with the latest dev docs changes - providers.md: Nous Portal OAuth preset (8 OAuth presets), ocx login nous, nous table row + refresh note - sub-agent-surface.md: catalog-trust section (fresh/not_running/stale/unknown) - claude-code.md: thinking disabled maps to reasoning:{effort:none}; tool_choice hosted WebSearch entry - reference/configuration/providers.md: noStructuredOutputModels field - reference/proxy-formats.md: structured-output translation paragraph Re-merge onto dev 0c17052 (was 38 commits behind; gate blocked readiness). * docs(zh-tw): clarify catalog-trust table wording (CodeRabbit) stale/unknown states do not add or overwrite opencodex-authored guidance, consistent with the withholding paragraph below. * test(zh-tw): restore browser global descriptors exactly * docs(zh-tw): resync Codex integration guide with dev * docs(zh-tw): resync providers guide with current dev --------- Co-authored-by: letr1n1ty <letr1n1ty@users.noreply.github.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
…jun#1446) * refactor(cli): add command registry as single metadata source Phase 2 of the CLI deepening: command names, aliases, usage, summary, and details move from src/cli/help.ts into src/cli/registry.ts (CLI_COMMANDS + findCommand). help.ts becomes a thin renderer over the registry; behavior is unchanged. - 48 visible entries in original order, plus 6 hidden __* entries - alias pairs: init/setup, restore/eject, uninstall/remove, models/model - exact-name-wins lookup keeps alias-name entries' own help text - tests/cli-registry.test.ts pins switch-case <-> registry parity - all 48 help outputs byte-identical; typecheck green * test(cli): require every registry entry name directly in the switch Addresses CodeRabbit finding on lidge-jun#1446: the previous assertion let an alias case satisfy a missing canonical case, so dropping e.g. 'init' while 'setup' remained would pass. Require each entry.name directly in caseSet.
* refactor(cli): registry-driven command dispatch module Phase 3 of the CLI deepening: extract the command switch out of src/cli/index.ts into src/cli/dispatch.ts as a registry-driven runner table. index.ts becomes a thin main that passes its local lifecycle helpers through CliDispatchDeps; aliases resolve via the registry alias map. - dispatchCommand(head, deps) replaces the 61-case switch - behavior preserved: restore/sync/sync-cache/claude/route/integration/ health/ready/gui/codex-shim/update runners match the original bodies - source-level tests migrated to read dispatch.ts - typecheck green; CLI suite 211 pass (4 known pre-existing failures) * test(cli): migrate app-server sync wiring test to dispatch.ts Phase 3 moved the command switch into src/cli/dispatch.ts as runner keys, but tests/codex-app-server-processes.test.ts still sliced the old case labels out of src/cli/index.ts. Update it to read the sync, sync-cache, v2, and gui runner bodies from dispatch.ts (using deps.args), which restores the lidge-jun#476 sync/sync-cache app-server-wiring assertions.
…jun#1455) * refactor(cli): normalize process exit through dispatchCommand Phase 4 of the CLI deepening: dispatch runners now RETURN exit codes instead of calling process.exit/setting process.exitCode directly. dispatchCommand aggregates and returns the final code; index.ts performs the single process.exit(await dispatchCommand(...)). - CommandRunner becomes (deps) => Promise<number> - unknown command -> 1, help/--help/-h/undefined -> 0 - restore/route/integration/claude/ready/health runners return their codes - behavior preserved: smoke tests (version 0, help nosuch 1, ready invalid 64, sync --help 0) pass; cli-ready runner assertion updated * fix(cli): preserve handler-set exit codes in dispatch runners Phase 4 exit normalization made runners return 0, but handlers passed via deps (handleStart/handleStop/handleStatus/handleEnsure/handleUninstall/ handleRecoverHistory/handleProxyRestart/handleTrayProxyRestart) and runInit set process.exitCode internally. The hardcoded return 0 overrode those codes (e.g. ocx init exited 0 instead of 1 on stdin EOF). Return process.exitCode ?? 0 so the handler-set codes are preserved. * fix(cli): propagate ready result through dispatchCommand Addresses CodeRabbit finding on lidge-jun#1455: handleReady was typed Promise<never> and called process.exit(await runReady(args)), so a valid 'ocx ready' terminated below the dispatcher instead of returning its code to the single top-level process.exit(await dispatchCommand(...)). - handleReady now returns Promise<number> and returns runReady(args) - CliDispatchDeps.handleReady typed Promise<number> - cli-ready source test updated to assert the return-through-dispatch behavior (body returns runReady, no process.exit); invalid-args 64 branch still covered
…1456) * refactor(cli): fold internal-dispatch and pin help banner Phase 5 of the CLI deepening: - fold src/cli/internal-dispatch.ts into src/cli/dispatch.ts as three explicit runner entries (__tray-start, __tray-restart, __startup-health) that use the injected deps directly; delete the module and its dedicated test - add banner-coverage test to cli-registry.test.ts and fix the drift it caught: printUsage now lists the previously-missing visible commands (route, logs, api-key) Behavior preserved: typecheck green; 144 pass / 4 known pre-existing environmental failures; smoke tests (version 0, help nosuch 1, ready invalid 64, --help header+new lines) pass * fix(cli): return tray-start status and tighten banner coverage Addresses two CodeRabbit findings on lidge-jun#1456: - __tray-start now returns 1 when handleTrayProxyStart() returns false (could not make the proxy live) instead of always reporting success - banner-coverage test now matches 'ocx <canonical-name>' at the start of a banner line (escaped) rather than any substring; adds the previously uncovered usage/storage/memory banner lines
* ci: add fresh-process Bun test batching * ci: run Linux test shards in fresh-process batches * test: preserve api-usage isolation contract * test: preserve storage-policy isolation contract * ci: bound Bun test batches with timeouts * ci: isolate unstable Bun batches per file
d7b98d8 to
4e22f98
Compare
Motivation
Description
rememberReasoningForCallandpeekReasoningForCallfail closed whenscopeis absent, and key entries withscope + '\u0000' + callId(changes insrc/responses/reasoning-replay-cache.ts)."global"replayCacheScope fallback and propagate only the explicit client thread id (parsed._clientThreadId) through the bridge, adapters, image loop, and web-search loop (changes insrc/bridge.ts,src/adapters/openai-chat.ts,src/images/loop.ts,src/web-search/loop.ts, andsrc/server/responses/core.ts).REPLAY_SCOPE(changes intests/deepseek-reasoning-replay-gaps.test.tsandtests/bridge-raw-reasoning-hidden.test.ts).Testing
bun test tests/deepseek-reasoning-replay-gaps.test.ts tests/bridge-raw-reasoning-hidden.test.tsand the modified tests passed (23/23 in those runs).bun run typecheck,bun run test, andbun run privacy:scan, all of which completed successfully.git diff --checkand a clean working tree after the changes.Summary by CodeRabbit
Bug Fixes
Tests