[WRONG BRANCH] release: promote 2.59.0 to main - #5074
Conversation
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
* docs: remove remote runner access details from historical offload notes * docs(devlog): scope redaction note to this document and qualify remote gate coverage Address CodeRabbit review: state that runner details are not recorded in this document rather than asserting they are absent from the public repository, and state that the remote run was limited to the four listed gates instead of implying full prepush coverage. --------- Co-authored-by: luvs01 <27862058+luvs01@users.noreply.github.com> Co-authored-by: lidge-jun <bitkyc08@gmail.com>
…me (#4753) * fix(server): retain raised body admission through HTTP response lifetime Reimplement the concurrency protection from luvs01#482 at the admitted HTTP boundary. Keep the reservation through downstream work and response consumption, preserve protocol-specific retryable overload responses, and avoid early release during asynchronous stream cancellation. Preserve current request-copy accounting and the configured image/search/count_tokens limits. Add 38 isolated lifecycle regressions, a real-server route integration test, and user/architecture documentation. Validation: 38 Web-stream/reader cases passed under Node v22.16.0 using TypeScript 5.8.3 transpilation of the actual helper and unchanged upstream reader. All four deliberate guard mutations failed. This is not a Bun repository-suite or full-typecheck claim; hosted Bun tests and documentation/structure/privacy checks remain pending. Co-authored-by: luvs01 <luvs01@hanmail.net> * refactor(server): move the admitted body call site out of the server facade * test(server): classify every loopback route for inbound body admission --------- Co-authored-by: luvs01 <27862058+luvs01@users.noreply.github.com> Co-authored-by: lidge-jun <bitkyc08@gmail.com>
…4919) * fix(web-search): replay executed bridge searches to the destination The web-search passthrough bridge runs an intercepted web_search proxy-side and shows the caller a hosted web_search_call cell. The caller replays that cell on every later turn, so the destination received an item type it never produced, carrying a query and sources but no result, and usually searched again. Record each executed search in a process-local memo scoped to the upstream destination and keyed by the cell id, then restore the destination's own function_call and function_call_output in the cell's place before the next turn's first leg is dispatched. The memo stores exactly what a continuation leg would have sent, so a replayed turn and a continued turn show the destination one consistent conversation. A miss leaves the replayed item untouched: no second search is billed and no result text is invented. Closes #4587 * docs(devlog): record the lane's closure evidence for #4429 and #3719 Both issue bodies predate the commits that changed the answer, so each claim is re-judged against the current source with the delivering commit named.
…evidence (#4906) (#4921) A pool holding Plus and Free Codex accounts keeps sending gpt-5.6-sol and gpt-6-astra to a Free account and taking the upstream unsupported-model 400, after a quota refresh and a catalog sync, with no alternate attempt. The ordering rules #4797 added are present and correct; they just have no evidence to act on. Both read cachedDeniedCodexAccountIdsForModel, which is cache-only by contract, and the roster cache it reads expires five minutes after a catalog sync fills it. Nothing on the flagship request path refills it, because resolveCodexModelEntitlements is awaited only for ACCOUNT_GATED_NATIVE_OPENAI_MODELS, which holds Daybreak alone since the 2026-09-04 owner decision. So for most requests the denial set is absent, withoutModelDeniedAccounts and preferModelEntitledAccount are the identity function, and the pool selects on quota alone. The refusal itself was the missing evidence. A 400 whose body is exactly "The '<model>' model is not supported when using Codex with a ChatGPT account." is authenticated, account-specific and model-specific. It was spent on one retry and discarded, so the next request repeated the same selection. It is now recorded per account and model in a bounded six-hour store and unioned into cachedDeniedCodexAccountIdsForModel. It stays evidence rather than a gate: consumers treat it exactly like a roster denial, so restore-on-empty and the pin exemption still hold, no model is hidden from any catalog, and nothing is refused before dispatch. A confirmed roster grant for the same pair outranks it, a successful response clears it, and a credential identity change discards it. Availability is never inferred from a plan name or from remaining quota. Detection now reads the model upstream actually named instead of rebuilding the sentence from route.modelId. applyCodexAccountGatedWireNormalization rewrites Daybreak to gpt-5.6-sol before dispatch, so upstream names Sol while the route still says Daybreak; the comparison never matched, which silently disabled both the alternate-account retry and the eight-rung same-account ladder that exists for exactly that model.
* fix(devin): forward the selected model's input ceiling Resolve the exact account/model UID's input ceiling and preserve smaller configured hints instead of silently serializing the 128k default. Keep output limits, entitlement preflight and unknown-window fallback unchanged. Add cached-catalog-to-wire regression cases and a scoped review record. Validation: 48 isolated Node/TypeScript checks passed; the original adapter reproduced 128000 against a 262000 expectation. Full Bun tests, repository typecheck and live Devin inference were not run in this environment. * docs(structure): cross-link the Devin input-ceiling review in the adapter owner * fix(devin): reuse the turn's single catalog read in the chat preflight getCachedCatalog returns null without caching when fetchCatalog fails, so each same-turn caller retried the request: resolveWireModelUid, the runTurn preflight read, and the streamChatEvents preflight could each pay the catalog fetch timeout before a valid turn started. runTurn now performs the one catalog read and threads the result through resolveWireModelUid (new optional parameter; the test seam still falls back to its own lookup) and streamChatEvents (new optional CloudChatRequest.catalog field). An explicit null is passed through deliberately so a failed lookup is not retried inside the turn; the null fallback and the 499 abort handling are unchanged, and cancellation during the read is now abort-responsive. Regression: devin-prompt-cache asserts exactly one metadata request per turn when the catalog endpoint fails. * docs(structure): clarify the no-evidence wire-field fallback --------- Co-authored-by: luvs01 <27862058+luvs01@users.noreply.github.com> Co-authored-by: luvs01 <luvs01@users.noreply.github.com> Co-authored-by: lidge-jun <bitkyc08@gmail.com>
…dation (#4723) * fix(providers): admit canonical Antigravity fake-IP on config writes * test(routing): cover Antigravity config-write fake-IP scope * test(routing): keep Antigravity negative cases literal Co-authored-by: cubebox <58514883+juzijia@users.noreply.github.com> --------- Co-authored-by: lidge-jun <bitkyc08@gmail.com>
Five opencode-go ids (qwen3.8-max, qwen3.8-flash, deepseek-v4.1-flash, glm-5.3-flash, muse-spark-1.3-contributor) have no jawcode bundle row and no vendor-level fallback, so the Usage estimated-cost column and per-model breakdown rendered an em dash for every request through them. Add exact provider/model expected-price overlays reusing each vendor published list price as a verified-derived estimate (Go itself is subscription-billed). Co-authored-by: lidge-jun <bitkyc08@gmail.com>
…get (#4903) (#4927) A combo target whose gateway cannot serve response_format ends the chain instead of trying the target behind it. comboFailureDecision reaches the generic invalid_request_error stop, because isRequestLocalTargetIncompatibility refuses at its first guard: the gateway reports invalid_parameter_error, which is not in the generic code set, and none of its three accepted shapes describes a response_format refusal. A second blocker explains the reported text. The gateway answers inside a single SSE frame, so normalizeUpstreamErrorText cannot parse it, the raw data: {...} survives as the classification text, and the structured code arrives undefined. Neither obvious option was taken. Hopping on every 400 replays a genuinely malformed request at every remaining target, and dropping response_format changes the output contract the caller asked for. The verdict is narrowed to a capability claim: the message must name response_format and say it is unavailable or unsupported, so an invalid-schema complaint stays terminal. The envelope keeps the existing discipline: HTTP 400, intact provider JSON, type invalid_request_error, a three-envelope depth budget and a 16,384 character bound. Its code set is the shared generic one plus invalid_parameter_error, held separately so the user and image branches are not widened. One data: prefix is unwrapped, and only on a single-line body, so a multi-event body still fails closed. The next target receives the same request with response_format intact. Traversal stays finite because combo excludes each attempted target, and the verdict records no cooldown because a capability gap says the target is healthy.
) * docs(devlog): plan the Lane B provider-reliability round * fix(cursor): seed Muse Spark 1.3 so live discovery can surface it Cursor live discovery filters the configured roster instead of unioning live ids into it, so a family with no capability base stays invisible no matter what GetUsableModels returns. Six advertised muse-spark-1.3 effort variants therefore produced no Cursor row (#4820). Seed the capability base and its ladder. The ladder stops at xhigh although the roster also advertises muse-spark-1.3-max: Meta publishes no max rung for Muse Spark and an independent probe rejected it, both already recorded on META_MUSE_REASONING_EFFORTS, so publishing it would invent a capability against two sources. A request at max clamps to xhigh.
…odels (#4913) Claude Code CLI (v2.1.200+) injects an internal billing header (`x-anthropic-billing-header: cc_version=...`) as the first line of its system prompt. Cloud Code Assist / Google Antigravity inspects `systemInstruction` and rejects requests containing this header with HTTP 429 RESOURCE_EXHAUSTED. Strip `x-anthropic-billing-header:` at the start of Cloud Code Assist system prompt while preserving non-leading occurrences, leading whitespace, all other instructions, and non-CCA requests. Co-authored-by: lidge-jun <bitkyc08@gmail.com>
#4929) * fix(cursor): stop grok-4.6 tool-result echo from poisoning later turns Grok-4.6 through Cursor often writes a real sentence and then pastes the replayed [Tool Result] envelope. The prefix sniffer only watches the opening bytes of a turn, so the echo reaches Codex, is stored as assistant text, and the next turn replays it — which primes the model to echo again. Strip whole-line echo envelopes from assistant root replay, remint the conversation for the next turn after a mid-stream echo on its own bounded allowance, prefer the retained thread remint override over a stale stored conversation id, and name Write as an unavailable neighboring-agent tool. The current send is never retried: the echo has already reached the client, and resending would be an uncertain replay. Conversations already poisoned still need a new task. Carries the work in #4900 onto current dev. That branch holds a pre-squash copy of #4875, which landed as ee28833 with review hardening the copy predates, so dev's version is authoritative for every shared file and only the increment is reapplied here. Co-authored-by: MerryEcho <xx59623633@163.com> * test(cursor): prove the echo strip is reached from root replay The unit test covers the filter; this covers the wiring. It also pins the bounded-strip behaviour end to end: the prose before AND after the echoed envelope survives into rootPromptMessagesJson while the envelope body does not. Placed in cursor-tool-continuation.test.ts because cursor-blob.test.ts sits exactly at its file-size-ratchet cap of 3657 lines and cannot take another line. * test(cursor): include Write in the neighboring-agent guidance assertions Adding Write to NEIGHBOR_AGENT_TOOL_NAMES changes the generated guidance note, and these three assertions pin that note verbatim. Line-neutral replacements; no assertion is weakened and the negative cases still hold. * fix(cursor): keep a compaction turn off the parent thread override Preferring the retained thread override over a stored _cursorConversationId is right for a stale id from a second Responses chain, but a compaction turn also carries a thread owner and its own conversation id while never setting the isolate flag. Unconditionally preferring the override pulled compaction onto the parent conversation, which "compaction storage isolation preserves the stable thread override without relying on the isolate flag" in cursor-adapter.test.ts exists to prevent. Exclude compaction from the override lookup and pin the interaction with its own case. * test(cursor): exercise the echo strip through a real tool continuation Root replay only carries history on a tool-continuation turn, so the previous shape sent a plain user message and produced a system-only root prompt: the assertions could never have seen the assistant text they were checking. Give the turn a tool result so the assistant message is actually replayed. It also now asserts the GENUINE replayed envelope survives. The strip must remove the copy the model pasted into its own text without touching the tool-result envelope the adapter builds. --------- Co-authored-by: MerryEcho <xx59623633@163.com>
…ly (#4676) * feat(oauth): rank Antigravity failover by Gemini vs Claude quota family Co-authored-by: chilung <b0423031@gmail.com> * fix(responses): restore non-replayable replay-refusal guards dropped in rebase The quota-family rebase lost upstream isNonReplayableResponse guards and wire-scoped enforceDeclaredToolNames in the 429 recovery arms (CodeRabbit Major on PR #4676). A replay-refused 429 must never enter same-target retry, key rotation, or OAuth failover. No behavior change besides the restored guards. --------- Co-authored-by: chilung <b0423031@gmail.com> Co-authored-by: jun <bitkyc08@gmail.com>
Add Cache-Control headers when serving static files from the GUI distribution: - Content-hashed bundles under 'assets/' receive 'public, max-age=31536000, immutable' so browsers can cache bundles without redundant re-downloads over slow networks and SSH tunnels. - Unhashed files under 'assets/' (e.g. runtime-config.js) and other static files receive 'no-cache' so updates are picked up promptly. - 'index.html' continues to be served with 'no-store' to guarantee immediate bootstrap and script hash updates. Co-authored-by: lidge-jun <bitkyc08@gmail.com>
…#4805) * fix(gajae): export working loopback credentials without an env prerequisite * docs(clients): record Gajae loopback credential ownership * fix(gajae): address keyless export review findings * docs(gajae): translate loopback credential table entries * docs(gajae): clarify Traditional Chinese placeholder wording --------- Co-authored-by: lidge-jun <bitkyc08@gmail.com>
* fix(streams): cancel response bytes before an aborted read * fix(oauth): bound OrcaRouter key-exchange responses * docs(structure): record bounded OrcaRouter login contract --------- Co-authored-by: luvs01 <27862058+luvs01@users.noreply.github.com>
…ead (#4935) * fix(codex): key pool affinity on the conversation cohort, not the thread (#4780) Upstream keys its prompt cache on something the whole tree shares. prompt_cache_key is responses_metadata.session_id, or {source}:{parent_thread_id} for an internal session, and one AgentControl whose session_id is the root thread id is shared with every sub-agent spawned from that root. The upstream suite asserts root and child carrying different thread ids while sending the same promptCacheKey, and openai/codex#44862 went further on 2026-09-11 by making an ephemeral fork inherit its parent's session id. While the proxy keyed per thread, a tree could split across pool accounts while every member kept sending one cache key. The split member asserted a warm prefix that was deterministically cold on its account, so the prompt was replayed in full. Nothing failed; only tokens burned, which is why no log line reports it. The binding unit is now the cohort. A request carrying session-id resolves to HMAC(session, session) and every root, child and grandchild of that tree lands on it. Without a session the cohort is read from the parent's lineage record, falling back to HMAC(parent, parent) when this scope has not seen that parent, which is the same key that parent derives for itself, so a chain of parent-only turns converges instead of splitting at every depth. Which requests bind at all is unchanged. This is not a revert of #4546 wp8. wp8 fixed a child binding under the RAW parent id, an identity unrelated to the root's own binding, so siblings shared an entry the root was not on and a grandchild landed on a key nobody had bound. A cohort key cannot produce that incoherence, because the root's own binding IS the cohort key. The orphan property is pinned as a regression rather than left as an argument. What wp8 additionally gave each thread, a binding of its own, is what this deliberately gives up. The cost is explicit: a tree gains cache locality and loses per-thread placement independence. All members share one binding, so a fan-out cannot spread across accounts and an exhausted account moves the whole tree. First placement is largely subsumed and its hook is now gated on the parent's key actually differing from the request's own. * fix(codex): repair cohort-keying fixtures that assumed per-thread keys Three CI failures, all in test fixtures rather than in the derivation. The bounded-table test varied only the thread id under one session, so every record landed under a single cohort key and the eviction probe read the newest record back through the oldest key. Each index now gets its own session, so the record cap and the scope cap are probed as before. The preview test asserted that an eligible preview leaves no lineage record, probing through the child's key. That key is now the root's, and the root was recorded earlier in the same test, so the probe answered from the root's entry rather than from anything the preview wrote. It now probes a cohort nothing has recorded, which restores the original meaning. A comment rewrite pushed tests/codex-integration/codex-auth-context.test.ts one line past its file-size cap. The comment is shortened rather than the cap raised, since updateBaseline only lowers caps.
…#4593) * fix(audio): bind stored direct account identity * fix(auth): clear caller account header on async Direct substitution * test(auth): cover Direct identity on both Responses endpoints * docs: link stored Direct identity from all source owners * refactor: split changed contracts to respect the file-size ratchet Co-authored-by: luvs01 <27862058+luvs01@users.noreply.github.com> * test(auth): cover synchronous stored Direct identity isolation Co-authored-by: luvs01 <27862058+luvs01@users.noreply.github.com> --------- Co-authored-by: luvs01 <27862058+luvs01@users.noreply.github.com> Co-authored-by: lidge-jun <bitkyc08@gmail.com>
* fix(config): track recovery backups without suppressing recovery * fix(config): track the OAuth recovery backup without suppressing recovery Splits out the half of this change that could not ship. The src/oauth/store.ts hunk is unchanged: backupLegacyOnce registers the auth.json.pre-multiauth copy it just created, and a registration failure warns and leaves the copy intact, so recovery wins and registration is best-effort. What is removed is the matching registration in src/config/salvage.ts. backupInvalidConfig mints a timestamp-unique path on every invalid-config load, and recordOwnedConfigPath appends without a bound, deduping only on an identical string that a timestamped name never is. isManifest rejects a manifest whose paths exceed MANIFEST_MAX_PATHS (1024) at read time, so past that threshold loadOwnership returns null permanently: createOwnership will not replace it because the directory is not empty, and nothing self-heals. The consequence inverts this change's own goal. removeOwnedConfigState then takes its refusal branch, "config ownership metadata is missing or invalid", so uninstall stops deleting auth.json and config.json and leaves refresh tokens and API keys on disk. A service restarting against a persistently broken config.json reaches that, one manifest slot and one backup file per load. backupLegacyOnce does not have the problem because it is guarded once-only and registers a fixed name, which is also true of every pre-existing caller; salvage.ts would have been the first unbounded one. The two salvage-specific tests are removed with it, and structure/config.md now records why invalid-config copies are not registered, plus the shape that would work: sweep them by name pattern at removal time, so ownership stays a fixed-size manifest and cleanup stays complete. That is a separate change. bun test tests/config/config-ownership-uninstall.test.ts tests/oauth/oauth-store-multi.test.ts: 51 pass / 0 fail. bun run structure:check: pass. Co-authored-by: luvs01 <luvs01@users.noreply.github.com> * fix(oauth): warn when recovery backup registration is refused Co-authored-by: luvs01 <27862058+luvs01@users.noreply.github.com> --------- Co-authored-by: luvs01 <27862058+luvs01@users.noreply.github.com> Co-authored-by: JUN <bitkyc08@gmail.com> Co-authored-by: luvs01 <luvs01@users.noreply.github.com>
* fix: pin Union Alpha wire and namespace Go sessions * fix(tests): correct the Claude-metadata Go session vectors The Claude metadata lane is not the hashed metadata.user_id. claude-messages.ts turns prompt_cache_key into a UUID through uuidFromHex and normalizes THAT, so the lane is sha256(uuidFromHex(sha256(user_id)[:32]))[:32]. The three metadata vectors were computed without that step. Derived independently of the production helpers and validated against the unchanged v1 values these tests carried before the namespace change: the same reconstruction reproduces both of the previous metadata vectors exactly, which is what shows the lane is right rather than merely self-consistent. The other eight vectors were already correct and are untouched. * fix(tests): use the per-model session vectors the loop already supplies The loop lists CHAT_SESSION_VECTORS for glm-5.2 and RESPONSES_SESSION_VECTORS for the Muse model but destructured only [model, url], so every case in it read whichever sessionVectors binding was in scope instead of its own. Muse settles on the Responses wire, and these vectors are now protocol-scoped, so each model needs the table the loop names for it.
…4523) * fix(config): validate provider send paths before management writes * refactor: split changed contracts to respect the file-size ratchet Co-authored-by: luvs01 <27862058+luvs01@users.noreply.github.com> * fix(config): isolate provider send-path validation from schema initialization Move the shared shape validator into an import-free config leaf and retain the schema export. The management boundary no longer enters config initialization through the schema before the facade can initialize its dependencies. A fresh-process regression fails with the runtimeRoleSchema TDZ before this fix. The management, import and layout checks pass: 155 tests, 1649 assertions. Typecheck, structure, privacy and file-size checks pass. Full hosted CI remains a separate publication gate. * docs(providers): add the missing chatCompletionsPath row to localized tables Every localized provider table documented responsesPath but not chatCompletionsPath, although each page's nearby prose already names both keys. Add the row to all seven locales (fr, ja, ko, ru, tr, zh-cn, zh-tw) in each document's own language and table style, matching the English relative-path contract. * docs(providers): document merged-path validation on provider PATCH --------- Co-authored-by: luvs01 <27862058+luvs01@users.noreply.github.com> Co-authored-by: lidge-jun <bitkyc08@gmail.com>
…failures (#4594) * fix(gui): keep failed reauth cancellation retryable * fix(gui): settle failed reauth cancellation responses * docs: link reauth cancellation from GUI source owners * fix(gui): announce reauth cancellation failures * fix(gui): release expired main reauth flows * fix(gui): fence device reauth responses across flows * fix(gui): retain polling after retryable cancellation * fix(gui): retain cancel retry across polling HTTP races * fix(gui): keep cancellation available before DELETE settles Co-authored-by: luvs01 <27862058+luvs01@users.noreply.github.com> * fix(gui): make reauth ownership explicit to React Doctor Capture the accepted flow identity as a const for polling and queued state updaters. Keep intentional API error-body reads at three narrowly documented React Doctor exception sites; HTTP status and flow ownership still gate all successful outcomes. Add POST, GET and DELETE regressions where non-2xx responses contain success-shaped JSON. Focused GUI validation passes 40 tests and 461 assertions, lint, TypeScript and production build. React Doctor 0.9.11 against the exact CI base goes from one compiler error plus three response-body warnings to a complete clean scan with those three local exceptions. Structure, privacy and file-size checks pass. Co-authored-by: luvs01 <27862058+luvs01@users.noreply.github.com> * docs(gui): add the reauth cancellation screenshot for review --------- Co-authored-by: luvs01 <27862058+luvs01@users.noreply.github.com> Co-authored-by: lidge-jun <bitkyc08@gmail.com>
…rough lane (#4925) * fix(responses): honor the provider transient-5xx policy on the passthrough lane (#4893) A provider's transientRetryOn5xx never reached the Responses passthrough lane. Configuring it on a key-auth provider whose adapter is openai-responses changed nothing in either direction, while the same provider on openai-chat honored it. Two things had to change together. transientRetryPolicyFor rejected every adapter but openai-chat, and the lane never called it: core.ts returns into executePassthroughResponse on the adapter's passthrough flag before the three call sites that read the policy are constructed, and passthrough-dispatch.ts passed the constant TRANSIENT_RETRY_MAX_ATTEMPTS at four dispatch sites and asked sendBudgetExhausted at that same constant twice more. Widening the gate alone leaves the reproduction at three sends, which is why PR 4800 is carried here rather than merged on its own. The ladder now resolves from the provider row at every leg: the initial send, the OAuth-401 replay, the same-target 429 replay, and the validated rebuild. sendBudgetExhausted takes the cap as a parameter so it asks at the same value the sends use, defaulted so every other caller is unchanged. The configured value is a cap on the ladder, intersected with the request-wide base allowance by remainingBaseSends. Configuring below that allowance narrows the ladder exactly, so attempts 1 sends once. Configuring above it does not raise the bound that exists to stop per-request amplification. authMode stays fail-closed, so the ChatGPT forward pool still gets a null policy and keeps the ladder it has always had, and isNonReplayableResponse is unaffected by attempts so a higher value cannot obtain a resend that marker forbids. Co-authored-by: Yum-wu <118118663+Yum-wu@users.noreply.github.com> * fix(responses): measure a configured transient ladder against the request total remainingBaseSends(cap) bounds what REMAINS, not what a request may spend in total. That is the right reading for the fixed constant, but transientRetryOn5xx.attempts is documented as the total sends for one request including the first, so passing the configured value straight through turned it into a per-leg ceiling: a provider configured at one send could still reach upstream again on a recovery leg. transientSendCapFor reduces the configured total by what the request has already sent before it is intersected with the base allowance. An absent policy returns the constant unchanged, so a provider that configures nothing is unaffected at every call site. Caught by the regression added in the previous commit, which asserted the intended contract rather than the implemented one. Co-authored-by: Yum-wu <118118663+Yum-wu@users.noreply.github.com> --------- Co-authored-by: Yum-wu <118118663+Yum-wu@users.noreply.github.com> Co-authored-by: lidge-jun <lidge-jun@users.noreply.github.com>
* feat(usage): show API list-price in breakdowns * fix(usage): distinguish unpriced estimates * docs(usage): clarify list-price coverage fields * fix(usage): keep unavailable list prices explicit * fix(usage): omit empty exclusion captions * fix(usage): make exclusion captions count-neutral * fix(usage): pluralize excluded request caption * fix(usage): clarify French exclusion caption * docs(usage): qualify exclusion counts for older proxies Co-authored-by: HarryZhou <2373256746@qq.com> --------- Co-authored-by: lidge-jun <bitkyc08@gmail.com>
Co-authored-by: luvs01 <27862058+luvs01@users.noreply.github.com> Co-authored-by: lidge-jun <lidge-jun@users.noreply.github.com>
…4958) * fix(catalog): explain a suppressed Luna Reserve row in the sync log The Luna Reserve projection is account-qualified, so it cannot be written without a selector that targets the main Codex account. A fresh authless install has an empty codexAccountNamespaces map, so no selector exists, the projection returns undefined, and the operator gets no row and no reason. An omission has no row to carry an explanation, so catalog sync now emits one warn-once line naming the cause and the action that restores it, in the same place and the same shape as the account-gated native suppression warning. Scoped to an install where authless Codex Desktop routing is effective: a default install wants no Reserve row and is never told about one. Also documents codexAccountPickerEnabled and codexAccountNamespaces, which were absent from the configuration reference entirely. * fix(catalog): keep the reserve warning off the catalog facade The facade re-export pushed src/codex/catalog/sync.ts from 52 to 58 lines, past its file-size ratchet cap in tests/fixtures/file-size-baseline.json. The ratchet only moves downward by design, so the cap cannot be raised. The re-export was never load-bearing: retained-sync.ts imports the warning directly from ./reserve-warn, and the reserve tests already import the projection helpers from ./reserve rather than through the facade.
A connected client runs the machine listener, which binds the same address a
standalone proxy would (port ?? config.port ?? 10100) and identifies as
opencodex on /healthz. CLI liveness parsed that body but discarded its
role: "client" discriminator, so every management-backed ocx subcommand on such
a machine resolved a base URL for a listener that serves only /api/machine/*
and died on its opaque JSON 404
({"error":"not_found","method":"PUT","path":"/api/custom-models/<id>"}) — one
character away from the management handler's own unknown-id {"error":"not found"}.
Carry the role through liveness and refuse it where a management plane is
actually required:
- proxy-liveness.ts parses role into HealthzIdentity and carries it on
LiveProxy. isOpencodexHealthz still accepts a client-role body, because stop
and orphan cleanup must keep finding that process.
- runtimeBaseUrl refuses a client-role listener with RuntimeApiError 503 (exit
1, not the misleading exit 4) and names the port, the machine-only route set,
the hub, and the on-machine alternative.
- responseMessage renders a 404 carrying method and path as the route the
listener does not serve.
- ocx models edit narrows a genuine handler 404 to name the id and
ocx models list-custom.
Closes #4662
…alling back to 128k (#4962) * fix(catalog): publish OpenCode Go context windows for live-only ids OpenCode Go's /v1/models returns id/object/created/owned_by and no context field, so a live-discovered row reaches the catalog with no contextWindow and no contextCap. applyCatalogMetadata is the only writer that can still supply a window on that path; it reads the generated registry keyed by provider and model id and returns early when the table has no row. Nine served ids were missing, so ensureStrictCatalogFields stamped its 128000 routed default and 1M-class models silently advertised 128k while chained clients compacted at roughly 121k. Refresh the vendored snapshot with the eight ids models.dev publishes for this provider and regenerate the committed table. Capability fields only: the new records carry no cost block, so every pricing path resolves exactly as before. Closes #4944 * fix(catalog): complete the OpenCode Go context-window snapshot refresh Adds the remaining ids models.dev publishes for this provider that the vendored snapshot still lacked: glm-5.3-flash (1,000,000), muse-spark-1.2-contributor (1,048,576) and ox-alpha-free (1,000,000). Same record shape as the first pass, with the same deliberate omissions: no cost block, so every pricing path in resolveMatchedPriceExact resolves exactly as before, and no thinking block. deepseek-v4.1-flash and deepseek-v4-flash-vision-exp are deliberately left out. models.dev publishes 1,000,000 for both, but entries-core.ts already declares 1_048_576 for each on this provider, citing DeepSeek's official Codex setup script and calling the 1,000,000 figure a rounded approximation. The registry hint wins wherever it applies, so copying the lower number in would fix nothing and would lower the window on any row that receives no provider hints. A test now pins that as an invariant rather than as an absence: these ids may be added later, but only carrying a window at least as large as the registry promises.
…#5040) Issue #5035 reopens #4903 against deepseek/deepseek-v4-pro. The reported build is 2.58.0, which was tagged two hours before #4927 landed, so the capability classifier is in no published release. On dev the reported envelope already hops, in every form the pipeline produces. This vendor spells its code invalid_request_error rather than invalid_parameter_error, at both the outer and the inner level. That is the code the generic terminal list stops on, so the ordering inside comboFailureDecision is load-bearing here in a way the first gateway never exercised. Pin it, and keep a malformed-schema complaint terminal.
A `workflow_dispatch` against `dev` landed in the same concurrency group as the `push` runs on `dev`, because the group was keyed on `github.ref` alone and `cancel-in-progress` was unconditionally true. The next merge therefore cancelled the dispatch. Run 35318264610 was cancelled in the same second its job started, three minutes after it was queued. The lane this hurt is `macos control`, the longest job in the workflow at roughly fifty minutes, so on a branch under active development the odds that no merge landed inside its window were close to zero. A maintainer dispatching it for release evidence usually got nothing back and had no reason to notice, because a cancelled job reports neither pass nor fail. Those cancellations were read as runner capacity for months; raising the budget from 30 to 75 minutes in #5028 did not change them. Supersession is still what `push` and `pull_request` want, so they keep it. A dispatch is keyed on `github.run_id` instead, which is unique per run, so each dispatch is a group of one: it cancels nothing and nothing cancels it, including a second dispatch of the same ref. Closes #5037.
…failover (#4926) Co-authored-by: luvs01 <27862058+luvs01@users.noreply.github.com>
Co-authored-by: luvs01 <27862058+luvs01@users.noreply.github.com>
* test(ci): warm a spawned child's module graph before it is timed Eighteen files spawn a real Bun child and bound it with INTERNAL_DEADLINE_MS, so the first child of a module graph is measured with its cold load inside the assertion. Pay that load once in setup instead, through a shared helper. Refs #4956 (cold-start class only). * test(ci): survive a shebang and a module that will not settle Run 35318878762 failed shards test 2/4 and windows 8/9 identically: the scan prepends module context, which moved src/cli/index.ts's shebang to line 2 and died with a syntax error before warming anything. Strip it first. The warm-up child also keeps its own budget now, so one module that never settles at import cannot consume the whole warm-up.
* fix(service): bind service start to the installed environment * test(service): return service.test.ts to its size cap and keep the start guard The branch added five lines to `tests/service/service.test.ts`, which sits at its committed cap of 4106 in `tests/fixtures/file-size-baseline.json`, so the file-size ratchet failed the run. Caps only move down, so the fix is to move the new assertion rather than to raise the cap. The start-case ordering check now lives in a sibling, `tests/service/service-start-environment.test.ts`, and asserts the same property plus one more: the guard is imported, so a local stub of the same name cannot satisfy it. This also restores three lines the branch had reindented from four spaces to three. They are the `repair`/`restart` assertions and were unrelated to the change. Co-authored-by: luvs01 <luvs01@users.noreply.github.com> --------- Co-authored-by: luvs01 <27862058+luvs01@users.noreply.github.com> Co-authored-by: JUN <bitkyc08@gmail.com> Co-authored-by: luvs01 <luvs01@users.noreply.github.com>
…pable on CommandCode (#5039) Move the id from COMMAND_CODE_TEXT_ONLY_MODELS to COMMAND_CODE_IMAGE_MODELS. The gateway route was unverified when #4505 classified it text-only; the upstream probe that issue asked for is now run end to end: - A 3x3 random-color grid (180x180 PNG, six candidate colors) delivered as a user-message image and as a tool_result image both came back 9/9 correct (~1e-7 by guessing), with no vision-sidecar call in either request window. - Both requests: HTTP 200, adapter command-code, on an existing 2.58.0 install. Update the pinned regression tests for the new classification; opencode-go's route keeps its text-only declaration (no probe evidence) and the text-only mechanism stays in place for the next route that measures text-only. Co-authored-by: xinzhihong-ship-it <254184831+xinzhihong-ship-it@users.noreply.github.com>
…tations (#4994) * fix(client): fence loopback-bootstrapped GUI sessions from machine mutations A connected-client listener mints GUI sessions to any loopback caller, and the bootstrap hands out the session token, browser origin, and CSRF value together. Any local process can replay those values and pass the Origin/CSRF checks, so the credentialless session authorized durable machine mutations (sync, shim install/uninstall, disconnect plus scheduled recycle). Treat the bootstrapped session as proof of possession rather than user presence: keep status and diagnostic GET reads working but refuse non-GET/HEAD calls under /api/machine/* with 403. Machine changes remain available through the explicit CLI commands, which invoke the same operations directly rather than over the listener. * fix(client): route HEAD and unsupported methods through the machine policy --------- Co-authored-by: luvs01 <luvs01@users.noreply.github.com> Co-authored-by: luvs01 <27862058+luvs01@users.noreply.github.com>
A pull request touching only `structure/` ran no test or gate leg at all, so `tests/ci-workflows/structure-ssot.test.ts` never executed — and that test is the only place CI runs `bun run structure:check`. The aggregate `ci` check reported success over a change nothing had read. The cause is the `changes` job's `ci` path filter, which does not list `structure/**`. On #4999, a pure doc split, every expensive leg reported `skipped`: windows, macos, macos control, test 1/4 through 4/4, gates, npm-global, docker smoke, keyring, api usage, storage policy and docs site build. This adds a `structure` filter and a narrow `structure gate` job that runs the check alone, rather than adding `structure/**` to `ci`. Widening `ci` would also close the hole, and would start nine Windows shards and two macOS shards for a check that takes seconds — the cost this workflow's own comments argue against for `docs-site`. The gate is pull-request scope, like `docs-site-build`, because the push trigger's `paths:` is pinned to mirror the `ci` filter exactly. That costs nothing: `dev`, `main` and `preview` are protected to require a pull request. Closes #5001.
…tions (#5042) * fix(local): fail closed DNS bind names for credential-bearing destinations probeHostname returns a DNS bind name unchanged, and the credential-bearing destination composers (localInferenceDestination, localManagementOrigin, resolveApiAccessBaseUrl) then embed that name in a URL the client dials with credentials attached. A hostname that re-resolves at dial time is a DNS-rebinding exfiltration path: the credential leaves for whatever the name resolves to then, not what it resolved to at compose time. localCredentialDestinationHostname keeps literal IPs as-is and fails closed to 127.0.0.1 for DNS names, so credential-bearing destinations only ever target a literal address. Display-only hosts keep the resolved name. Tests pin the fail-closed behavior for inference, management-origin, and API-access base URLs, plus the literal non-loopback IP path. * fix(local): keep localhost out of the DNS fail-closed branch The fail-closed branch treated every non-literal bind name as a DNS bind, including `localhost`, and rewrote it to 127.0.0.1. That failed `ocx claude management discovery destination > a loopback or wildcard install keeps asking 127.0.0.1 on the public port`, which pins that a `localhost` install keeps writing `http://localhost:<port>` into its exported client configuration. The rewrite also bought nothing. RFC 6761 reserves `localhost` to loopback, so a second lookup cannot select a peer off this machine — the only outcome the fail-closed branch exists to prevent. `isLoopbackHostname` is already the encoding of "this name is loopback" in this module, so the carve-out reuses it rather than growing a second list. Also rebased onto current `dev`. Co-authored-by: luvs01 <luvs01@users.noreply.github.com> --------- Co-authored-by: luvs01 <27862058+luvs01@users.noreply.github.com> Co-authored-by: JUN <bitkyc08@gmail.com> Co-authored-by: luvs01 <luvs01@users.noreply.github.com>
) `resolveCodeModeHelperName` decides a code-mode `exec` body is an apply-patch call by reading `unwrapFreeformToolInput` under the wire tool name, which strips an outer Markdown fence and accepts that name's fallback fields (#4983). Compilation kept a narrower unwrap that saw only `input` and `patch`, so a body accepted through a fence or through a field like `content` reached `tools.apply_patch` still wrapped. The host then rejected the JSON text or the fence instead of applying the patch. Both halves now read one canonical body, produced by one unwrap under the name the body actually arrived on. That name is not always the helper: the name-based path arrives as `apply_patch`, whose fallback fields are `patch` and `content`, while the body-based path arrives as `exec`, whose fields are the code-carrying ones. `{"patch": ...}` is meaningful for the first and is correctly refused by the second, so reading it under `exec` would compile a body recognition had declined. Closes #5046.
…n principal (#4928) * fix(responses): scope compact handoff cache to authenticated admission principal The compactHandoffRoutes map is process-global but was keyed only by the caller-supplied lane header, so any two admitted clients sending the same lane header shared one fallback route. Namespace the lane by the admitted principal (contextPrincipalId when minted, configured key id or environment kind otherwise); loopback and missing admissions have no authenticated identity and are ineligible rather than trusted. * fix(responses): fail closed when an admission mints no context principal compactHandoffRouteKey substituted "configured:" plus the key id, or the constant "environment", when contextPrincipalIdOf returned nothing. Both substitutes reintroduce the collision the key exists to prevent: a key id survives rotation, so a replaced secret inherited the previous holder's route, and every identity-less environment admission shared one bucket. Production admission always mints a principal for configured and environment holders, so refusing the cache without one costs no real caller anything. The new test file was also missing from both test-layout registries, which tests/test-layout-tooling.test.ts asserts independently. Register it, and replace the different-key intruder with a same-key-id rotation so the case the old fallback collapsed is the one under test. * test(responses): include the minted principal in the routed-handoff admission fixture --------- Co-authored-by: luvs01 <27862058+luvs01@users.noreply.github.com> Co-authored-by: lidge-jun <lidge-jun@users.noreply.github.com>
…n 429 failover (#4954) Co-authored-by: luvs01 <27862058+luvs01@users.noreply.github.com>
…5041) * fix(devin): wait out a stated rate-limit reset and replay the turn Cognition free-tier caps answer resource_exhausted trailers that name their own recovery delay ("Your limit will reset in 35 seconds"). The delay was never parsed: clients got the synthetic 2s Retry-After and combo cooldowns fell back to the 60s default, so a retry fired straight back into the live cap and the turn died inside a window it could have waited out. parseRetryAfterFromMessage now reads "reset in N <unit>" phrasing plus minute/hour units on the existing hints, so the stated delay drives the client Retry-After and the cooldown metadata. streamChatEventsWithResetRetry waits the stated delay and replays the identical request, but only while the stream has yielded zero events — after any output a replay could double billable side effects, so those failures keep their terminal path. Waits are bounded by a replay cap and a per-wait ceiling; longer stated windows surface with the parsed delay intact. * fix(devin): bound cumulative reset waits and preserve server cooldowns Parse complete and compound retry durations without treating milliseconds as minutes. Keep the errors.ts local parser binding as well as its public export. Bound scheduled waits cumulatively within a stated-reset wrapper invocation; preserve explicit refusal errors, cancellation, zero-wait opt-out and the no-output replay boundary. Store explicit Retry-After cooldowns without truncating them to the local ten-minute fallback. Add regression coverage for parser integration, cumulative waits, abort races, post-output refusals and authoritative numeric/HTTP-date cooldowns. Validation in this environment: 90 offline core checks; 43 proposed regression cases executed via a Node/TypeScript module harness with external routing/state/RPC dependencies stubbed; strict typecheck of errors.ts and retry-delay.ts. Full Bun/repository typecheck and live Devin validation remain pending. Long-lived SSE/preflight and cross-layer send/wait budgeting are not implemented by this commit. * test(layout): register the new Devin and combo reset test files `tests/test-layout-tooling.test.ts` failed its membership oracle: three of the new test files resolve through neither the explicit map nor a regex seed, so nothing placed them. `combo-authoritative-reset.test.ts` collides with the `combo-` seeds, which point at `routing`, and the `devin-stated-reset-*` pair has no seed at all. Registered in both `scripts/test-layout/layout.json` and `tests/fixtures/test-layout-expected.json`, which the oracle compares against each other as well as against the tree. Also rebased onto current `dev`. Co-authored-by: luvs01 <luvs01@users.noreply.github.com> --------- Co-authored-by: luvs01 <27862058+luvs01@users.noreply.github.com> Co-authored-by: JUN <bitkyc08@gmail.com> Co-authored-by: luvs01 <luvs01@users.noreply.github.com>
…5053) #4983 widened what counts as a freeform wrapper at completion. Partial input streaming still knew only the compact `{"input":"` form, so a wrapper such as `{"code":"const x = 1"}` streamed as raw JSON through `response.custom_tool_call_input.delta` and then finished with the unwrapped body. Concatenated deltas no longer equalled the authoritative input, and a client that renders or accumulates tool input mid-stream had to rewind. `input` stays progressive: `unwrapFreeformToolInput` returns it whenever the key is present, whatever else the object carries, so its value is decidable from the prefix and can never be retracted. A fallback key is not decidable that way. It unwraps only when it is the single string field, and a second key can still arrive, so a value emitted early would have to be taken back. Those buffers are held until the object closes and then published once. The routed passthrough in `responses-custom-tool-repair.ts` already holds any object prefix for the same reason. The streaming side also drops the tool name for a namespaced tool that does not own the apply-patch grammar, because `repairFreeformToolInput` drops it at completion; streaming under a vocabulary the completed item does not use is the same disagreement in the other direction. Closes #5047.
) `antigravitySessionId` anchored on `_clientThreadId`, which carries `x-codex-parent-thread-id`. Every parallel child of one parent presents the same parent id, so concurrent Codex children collapsed onto a single upstream Cloud Code Assist session instead of getting one each. `codexConversationIdentity` already keys on parent AND thread for exactly this reason: a thread id is only unique within its parent. This surface needs the narrower half, so the anchor now prefers the request's own `thread-id` and falls back to the parent id for clients that send only that. Deliberately not the general lane key. `codexConversationKeyFor` is an HMAC under a process-random secret, so it changes across a proxy restart — and instability, not sharing, is the failure mode this derivation must avoid, because the replay cache observes thought signatures on turn N and re-injects them on turn N+1. `thread-id` is Codex's own value and survives compaction and restart alike. A root turn presents `thread-id` equal to its parent id, so its anchor is byte-identical to what it was. Only a child's anchor moves, and it moves once. Closes #5033.
* fix(quota): make the account-switch warning agree with routing The manual account-switch warning could report an exhausted account as usable while the router refused it. Three boundaries diverged, each locally plausible: The dashboard compared a stored reset against `Date.now()` without normalizing units. Both Unix seconds and milliseconds reach storage, and read as milliseconds a seconds-form instant lands in 1970, so every future reset looked elapsed there and live in routing. The dashboard accepted a fresh observation even when an elapsed reset was present. Routing treats a reset as authoritative once it exists and uses freshness only for a reading that has none. `providerQuotaFromCodexQuota` dropped `shortObservedAt`, and the Free/Go projection dropped the whole burst window. Routing counts that window on every plan, so a Free/Go account could be refused upstream while the dashboard had neither a governing window nor terminal evidence and returned no opinion. `isTerminalShortWindow` and `resetAtToMs` now live on `src/codex/quota-types`, the dependency-free leaf the dashboard already imports, and both sides call the same function instead of keeping two copies of the rule. Closes #5045. * fix(quota): import the shared resetAtToMs, not only re-export it `export { resetAtToMs } from "./quota-types"` re-exports the binding without introducing the name into this module's scope, and two call sites here use it. Import it and re-export the local binding instead. * fix(quota): keep the Free/Go carve-out on the server DTO The burst-window carve-out for 30-day plans belongs to the server DTO, not to the GUI normalizer — `tests/gui/rate-limit-reset-credits.test.ts` says so in the #1791 case it pins, and `normalizeQuotaForPlan` is not on the account-switch warning's path at all: the modal scores `confirm.quota` directly. So the projection fix stays where it belongs, in `providerQuotaFromCodexQuota`, and the parity test now exercises that DTO instead of the GUI normalizer.
…4751) * fix(responses): bound pending body reads without premature shared abort Reimplement the combined response-body inactivity scope from luvs01#176 and luvs01#339 on current dev. Guard direct native bodies after classification and scope initial/continuation adapter parsers. Preserve native SSE ownership, backpressure, terminal ordering, and nonblocking cancellation cleanup. Add 29 regression cases and document the body-lifetime contract. Node behavior/component/HTTP checks passed; repository Bun, typecheck, full-suite and policy gates remain unrun in this environment. Keep upstream submission in draft. Co-authored-by: luvs01 <27862058+luvs01@users.noreply.github.com> * fix(responses): keep the lease-owner structural check on the wrapped return * fix(responses): classify body stalls before signal state and yield on discarded empty chunks * fix(responses): map the initial stream body stall to a 504 upstream error A ResponseBodyInactivityError thrown by the guarded initial stream reached the bridge catch and surfaced as a 500 proxy_error. Wrap the initial stream so the stall yields the same 504 upstream_error terminal the continuation and buffered paths already emit, and cover the direct passthrough guard with a plain successful body case. --------- Co-authored-by: luvs01 <27862058+luvs01@users.noreply.github.com>
When the send budget refused a retry or an account-rotation hop, the runtime returned the upstream response it already had and recorded nothing. A log with one physical send was therefore ambiguous: it could mean no recovery was eligible, or that one was and the budget withheld it. Those need opposite follow-ups, and only the first was visible. `recoveryWithheld` is a bounded per-attempt vocabulary recorded beside `recoveryKinds` and separately from `sendCount`. Nothing was sent, so the count that means "requests this proxy actually made" does not move. The four states the issue asks to distinguish fall out of the pair: neither field set means nothing was eligible, a withheld reason means something was and the budget refused, and a recovery kind means one was dispatched. Recorded at the four places that previously fell through silently: the Google retry ladder when it holds a retryable response, and the generic OAuth rotation in the run-turn, passthrough and adapter-continuation loops. The adapter seam is `onRecoveryWithheld`, a sibling of the existing `onPhysicalSend` rather than a new argument to it, so an adapter cannot report a refusal as a send. Closes #5044.
…5057) The Anthropic surface published `usage.input_tokens: 0` on `message_start` whenever the upstream had not reported usage before the first content frame. Third-party clients read the prompt size there — Paseo's context meter takes input from that frame and output from `message_delta` — so a turn whose own `/context` reported ~97k rendered as a nearly-empty ring. #4891 fixed the destinations that report usage up front. The internal bridge attaches `usage: null` to its lifecycle frames, so those paths had nothing to publish and kept sending zero. `responsesSseToAnthropicSse` now accepts an input-token floor: the count this proxy made of the prompt it forwarded, published only when no confirmed upstream usage arrived first. `claude-messages.ts` supplies it from `estimateClaudeRequestTokens`, the same estimate the usage log already trusts as a floor, computed at most once per request and shared with the log path. This narrows a recorded decision rather than ignoring it. The pinned case said zero is the honest placeholder and an estimate must not replace it. Zero is not honest about a prompt that exists — it asserts an empty one — and the Anthropic schema makes the field required, so the choice is between a false measurement and a real one of the request side. The second half of that decision stands: the first content frame is still never delayed to await usage, and `message_delta` remains authoritative. Closes #4857.
) #5054 fixed same-parent sibling sharing by anchoring on the request's own thread id, and got two things wrong. A thread id is only unique WITHIN its parent, so keying on the child alone relocated the collision rather than removing it: two parents can each have a child of the same id. The anchor is now the pair, joined by a NUL so the encoding is injective. And a parentless root does not present `thread-id` equal to its parent, which is what #5054 claimed. `src/server/context-history.ts` says the opposite in as many words: root model requests use (session-id=root, thread-id=root) and do not fabricate a parent key. So #5054 moved every root's anchor on upgrade, and durable Antigravity replay state is keyed by model plus session id, which strands the signatures stored under the old root session. A parentless root now keeps the pre-#5054 anchor. Parent-only clients are unchanged in both revisions. Closes #5058.
…d decode SOCKS5 content-codings (#5070) * fix(transport): match freeform streaming to its completion grammar and decode SOCKS5 content-codings The bridge decided whether a freeform argument buffer was a wrapper by comparing it with the compact literals {"input":" and {"code":", while the completion path reads the same text with JSON.parse. A wrapper written with spaces or newlines matched no prefix, so it streamed as raw JSON and then completed as the unwrapped body - the disagreement #5047 closed for compact wrappers, reached through a different spelling. The same applies to a Markdown-fenced body, which stripMarkdownCodeFence removes at completion for exec and apply_patch. socks5Fetch assembles the response from a raw socket, so new Response(body, headers) surfaced a coded body unchanged: .json() threw SyntaxError on the gzip magic number. The request now asks for identity unless the caller chose otherwise, gzip and deflate are decoded, and any other coding is refused by name. The keep-alive socket-close tests asserted that close propagation is observable within a fixed 50ms, which failed in the unsharded control lane and passed everywhere else; they now wait for the state the contract promises under a bounded deadline. * fix(transport): correct the streaming JSON escape set, hold undecidable prefixes, and keep bodyless SOCKS5 responses out of the decoder Typecheck: DecompressionStream declares its writable side as WritableStream<BufferSource>, which TypeScript measures as invariant, so the pair needed an explicit conversion to reach pipeThrough. Review findings: the streaming decoder mapped every unlisted escape to its own suffix, so a valid backslash-b streamed as the letter b; an escape JSON does not define now stops the preview because such a wrapper never parses and completion returns the raw text. A lone high surrogate is held until its pair arrives. A decorated apply_patch envelope is held because normalizeApplyPatchDelimiters rewrites it at completion. A bodyless response no longer has its content-encoding decoded or refused, and the deflate fixture now states zlib framing through node:zlib rather than relying on a runtime default. * test(bridge): pin what a wrapper that turns invalid mid-stream is allowed to do A valid prefix is already published when an undefined escape arrives, and no mechanism takes a delta back. The guarantee is that nothing further is invented: the preview stops at the last decodable character and the completed item carries the raw text. Holding the whole wrapper instead would mean never streaming a canonical wrapper progressively, which the neighbouring test requires.
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
|
Important Review skippedToo many files! This PR contains 586 files, which is 286 over the limit of 300. To get a review, reduce the PR to 300 files or fewer by splitting it into smaller PRs or changing its base branch. Usage-priced reviews support at most 300 files. ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Advanced Run ID: ⛔ Files ignored due to path filters (8)
📒 Files selected for processing (586)
You can disable this status message by setting the Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
✅ Deterministic PR hygiene checks passed. |
⏳ DRAFT
What to do
Its title has been prefixed with |
Summary
Promote the frozen 2.59.0 candidate from
devtomainso it can be published. The candidateis
ecd3adae75, which isdevas it stood before the pre-release version move;devitself hassince gone to 2.60.0 in #5072 so the release gate that requires
devto outrank the publishedversion is satisfied.
This is 116 first-parent commits against 2.58.0. The bulk is provider and transport
stabilization rather than new surface: send-budget accounting that stops counting a withheld
recovery as a physical send (#5056), configured attempts that subtract sends already spent
(#4925, #4947), admission lifetime separated from response-body inactivity (#4753, #4751),
fresh-connection policy applied at the fetch and dispatch boundary that actually runs (#4977,
#5022), and Antigravity session anchoring corrected to the parent and child pair after the first
attempt anchored on the child alone (#5054, then #5062).
The last change in the candidate is #5070, which closed the two compatibility gaps a release
audit found in this tree: freeform tool-argument streaming read its wrapper grammar differently
from the completion path, so a wrapper written with whitespace streamed as raw JSON and then
completed as the unwrapped body; and the SOCKS5 transport surfaced content-coded bodies
unchanged, so a gzip response reached the caller as bytes its parser could not read.
Verification
No local suite was run. The candidate commit
ecd3adae75has a green push-event Cross-platformCI run on
dev: four Linux shards, both macOS shards, keyring on three platforms, npm-global onthree platforms, gates, structure gate, docker smoke, api usage and storage policy, aggregating
to
ci: success.Two things are recorded rather than hidden. The unsharded
macos controllane did not completewithin its 75-minute budget on the preceding tip, and its evidence is on #4997. The sharded
macos 2/2leg failed twice on this candidate before passing, with a different timing-sensitivetest each time; that is the same class, and the newly observed sharded occurrence is recorded on
#4997 with the unhandled-fixture case split out as #5073. Neither is a failure of the candidate:
the third run passed with no code change, and #5070's own head was green on macOS first time.
A repository integrity check at this commit reports GUI locale parity clean across all nine
locales against 2818 keys in
en.ts, and no file over its size-ratchet cap with 37 of 51 entriessitting exactly at theirs.
Checklist